-
Notifications
You must be signed in to change notification settings - Fork 27.4k
Angular lacks support for recursive directives #8536
Comments
I've been writing tree directives in angular lately and you can pretty much do whatever you want, given you understand Angular well enough. Anyhow, if I understand your issues correctly, I believe the following blog (Building Nested Recursive Directives in Angular) is demonstrating how to do exactly what you want. Make sure you also read the comments. |
this is a valid issue, recursive templates are not really possible in angular without hacks currently. It's not that the hacks aren't possible, but they really, really suck, both in terms of code smell and performance, and should not be necessary. Unfortunately, it's not a super simple problem to solve on top of the current compiler. |
@Izhaki scope is not respected by the transcluded HTML in link 2 in my original post, which is the real thorn in my side... and I adamantly refuse to put my entire view template inside my tree directive :P @caitp At least I know I'm not crazy... perhaps the compiler should check the current stack and see if it's recursively compiling a directive that it is already trying to compile? Then it would defer compilation..? |
It's not quite that simple --- if we defer compilation, we need to have machinery for doing the compilation later, and it's not clear what would trigger that. It could be element transclusion, since the most common use case for this would be |
@caitp what about adding a new rule to the compiler, like |
@callmeStriking - I believe that your scope issue may be resolved in a later version of Angular - your link is to a fiddle that is using 1.2.0-rc2. We did some work recently on tightening up what scopes are passed to nested transclude directives. |
@petebacondarwin Close, but no cigar. The failing case (3, on Plnkr) uses Angular 1.3.0-beta.5 Edit 2: Tried again. 1 After #, the local index of the element should be shown. Instead, I got nothin'. (Still 1.3.0-beta.5. Inside the link part of the compile function, I do have access to the scope but the elements will never be aware of that). Even if adjusting the scope did work, it still wouldn't fix the fact that this is a hack... |
For reference: https://github.com/dotJEM/angular-tree I know that that solution uses two directives, but that brings some advantages with it, and when we have accepted that, then what are we really missing?... |
@jeme What advantages? |
@callmeStriking In this case it's a common directive for all recursion, having the two seperates directives means you can embed recursions in recursions of different types. Not that that may be a very common usecase, regardless of that I think the Syntax is elegant enough to be acceptable. Obivously you can do that if you go and make a new recursive directive again, and again, and again with different names... but why should we do that if we can make one simple solution all can just use in their directive templates? |
@jeme Why not be able to create your own, native recursion? Also, does your solution mangle the scope like above ones do? |
@callmeStriking Although this has nothing to do with Angular, I feel obliged to share my experience and suggest you are approaching this problem with the wrong strategy. Indeed, most drag-and-drop (DnD) frameworks use 'drop zones', but if you really come to think of what should be the correct abstraction for the problem, you should distinguish between:
A tree (or a table) DnD is a classic drop-in-between. As such, instead of throwing more drop zones than tree nodes, you should define the whole tree as a single drop zone, and work out the 'above' and 'below' containers of the drop, then work out the legal drop positions based on that. Lastly, do remember that users may want to drop below the last node, where, in theory at least, there can be as many drop positions as the levels of the last node (so if your illustration would only go as low as item E, users can drop below A, C and E). Implementing this type of drop will be substantially easier if the whole tree is the drop zone. |
@Izhaki That makes quite a bit of sense, but: drop-in-between, in this case, can't be done using existing hacks because using them mangles the scope. |
Is this plunk of any help? It uses the contents of the tree directive as a template:
With the directives simply being:
|
@callmeStriking I am not saying the compiler couldn't do it, but if it adds allot of complexity, then I think it's worth looking to other solutions first... And why not solve this with a directive we can use on par with e.g. The solution doesn't mangle the scope, each node (defined by start-with or connect) will create a child scope. And then it will put some values on that (much like On a further ittiration of things, we/I might add the ability to "name" the main value like in It basically does the same thing as recursive templates, or close to what @Izhaki's example does. I am sure there are room for improvements... But I haven't found a tree structure I can't render with it yet. Edit: Just adding a sample use directly here for previously linked module:https://github.com/dotJEM/angular-tree <ul dx-start-with="rootNode">
<li ng-repeat="node in $dxPrior.nodes">
{{ node.name }}
<ul dx-connect="node" />
</li>
</ul> |
I have a directive called |
Angular has to compile the directives by traversing the tree fully. It doesn't matter if there aren't any objects because the compilation doesn't check the number of objects, it's filled in on render. The unfortunate thing about this is, recursive objects can't be compiled, and that object is recursive no matter what. There's no way to make elements render like this out-of-the-box (or without compile function hacks). |
Why does it compile directives by traversing the tree fully then? Why not compile it on demand, which I assume what the various hacks/workarounds do. It seems a bit of waste to be compiling something that possibly isn't needed. Or adding an option you can set in the directive to have to it compiled lazily (effectively building the hacks into angular) |
@Jon889 It's more efficient for usecases that support it. Recompiling everything on-demand could cause code complexity, and would likely be slower. The hacks do indeed compile on-demand... although they work, they cause code smell and really just aren't "the Angular way." |
It's not that there is "no way" to do it, but it requires some refactoring, and is complicated (nobody likes adding complicated stuff to the compiler) |
@callmeStriking I won't consider the concept @Izhaki posts, nor will I consider https://github.com/dotJEM/angular-tree a hack... Doing that means that ng-include is a hack IMO...
<script type="text/ng-template" id="node_template.html">
{{node.label}}
<ul><li ng-repeat="node in node.nodes" ng-include = "'node_template.html'" ></li></ul>
</script>
<div ng-include="'node_template.html'"></div> But in a single go (dx-start-with first functions as the template collector, then as a regular node) I wondered if there was a way to use transclusion (like ng-repeat does it) or other means of optimizations where the template is already compiled, but just requires linking down the tree. But I have not been able to implement it in such a way yet. |
With the directive ng-repeat the repeated element is compiled only once and then linked for each repetition, so why isn't each directive compiled only once across the entire page? And then linked for each instance? |
Folks, is there a consensus on this ? Whats appropriate to commit to. Whats available for commercial ? Specifically for implementing a fast super node/branched tree that will support different KINDS of nodes and branches and click drag drop open crud... etc... thank you |
Angular 1 will not have native support for recursion. Use any of the previously mentioned solutions to do it, or use a framework more suitable for your project.
|
Well I have implemented several trees in the past recursive ones. Doesn't what this gentleman is doing jar anyone's mind as to the possibilities here ? https://www.youtube.com/watch?v=vJqJ-0U74IY I am thinking not outside of the box (angular) but thinking deeper inside the box (javascript). ?? I havent seen anything yet that is causing me to jump up and start doing back flips. I did a struts-tiles tree years back that I recall qualifies similarly structurally to whats available this moment in angular and javascript that would provide a sound commercial grade load and erect tree-widget based on some json structure and could be implemented to do-all...click, click-open, click-drag, click-close... on nodes and branches. And add/delete too. I am still looking to do it myself but wondering if anyone has done this already. |
Few people have done anything of merit that you can use commercially. Good luck with your custom solution, hope you open-source it. |
Is there any solution on the roadmap for this? It's pretty glaring that this is still an issue. |
To some extend, this will be solved once #12078 lands, and there is a reasonable chance that it will make it for 1.5. Given that this is a breaking change, it is not possible for it to land before. |
That's fantastic, and I doubt that there's a solution to this problem that wouldn't involve a breaking change. This issue has been a thorn in my side for way too long -- angular's supposed to be good at building things like tree-views. |
Just to add an update on this from the https://github.com/dotJEM/angular-tree perspective. Which have been moved to use Transclude instead of compile, which speeds it up a high degree. |
So, does Angular 1.5 now provide a reasonable path for creating trees without using My intuition seems to suggest that this is possible, but I'm not quite sure what the recommended path for doing it would be. |
@schmod it depends on the specific case, but most simple solutions work. Eg if the recursive reference in the directive template is inside an |
@schmod that depends on preference, but I would think the following example is reasonable and very simple? https://plnkr.co/edit/5Qv1pN4ISbRxvGnsUVgN?p=preview app.controller('appController', function() {
this.data = {
name: 'TEST',
nodes: [
{ name: 'Child1',
nodes: [
{ name: 'GrandChild',
nodes: [
{ name: 'GrandGrandChild' }
]
}
]
},
{ name: 'Child1',
nodes: [
{ name: 'GrandChild' }
]
},
{ name: 'Child1',
nodes: [
{ name: 'GrandChild' }
]
}
]
};
});
app.directive('recurseMe', function() {
return {
scope: {
tree: '='
},
template: '<ul>'
+ '<li ng-repeat="node in tree.nodes">'
+ 'NAME: {{ node.name }}'
+ '<recurse-me tree="node" />'
+ '</li>'
+ '<ul>'
}
}); |
@jeme To get this sort of thing to work we must squeeze some asynchronicity into the template. Some people have used The key is that we need the child nodes to be rendered in a subsequent digest and not the current one. |
@petebacondarwin the example that @jeme posted works well as the recursion is inside an |
This is a pretty major issue in terms of being able to do things "the Angular way."
My model is a tree. Trees require recursion. I'm making an app that depends on a directive that allows movement of nodes inside a tree. In order to be able to move nodes inside this tree, I need to place "drop zones" which will detect elements that are dropped onto them.
Unfortunately, with the "Angular way" of implementing this (2 is a JSFiddle that uses similar concepts; recursive directive with transcluded template for each node of the tree) the transcluded template is rendered totally unaware of where it is located relative to where its data comes from, because the transcluded template has to be removed from its parent element before compilation.
If it's removed, it loses its scope; if it's not removed, 3 happens. (Open a console, you'll see the infinite recursion. It's the same as 2 but with the compile function commented out.) Angular tries to compile the directive while it's already compiling the directive, which fails hopelessly.
Now, there are workarounds, which is why it is possible that angular-ui-tree 4 exists. However, the only way to use it is through a recursive template (5 is a good starting point) which misses out on all the cool directive support and parametrization and is generally not the intended use of templates. The implementation here becomes hairier than it needs to be because of the lack of support for recursive directives.
The text was updated successfully, but these errors were encountered: