I'm looking to display data in a tree structure in a web app. I was hoping to use Angular for this task.
Looks like ng-repeat will allow me to iterate through a list of nodes, but how can I then do nesting when a given node's depth increases?
I tried the following code, but the auto-escaping of the HTML is preventing this from working. Plus, the end ul tag is in the wrong place.
I'm pretty sure that I'm going about this problem entirely the wrong way.
Any ideas?
Have a look at this fiddle http://jsfiddle.net/brendanowen/uXbn6/8/
This should give you a good idea of how to display a tree like structure
using angular. It is kind of using recursion in html!
If you are using Bootstrap CSS...
I have created a simple re-usable tree control (directive) for AngularJS based on a Bootstrap "nav" list. I added extra indentation, icons, and animation. HTML attributes are used for configuration.
It does not use recursion.
I called it angular-bootstrap-nav-tree ( catchy name, don't you think? )
When making something like this the best solution is an recursive directive. However, when you make such an directive you find out that AngularJS gets into an endless loop.
The solution for this is to let the directive remove the element during the compile event, and manually compile and add them in the link events.
I found out about this in this thread, and abstracted this functionality into a service.
module.factory('RecursionHelper', ['$compile', function($compile){
return {
/**
* Manually compiles the element, fixing the recursion loop.
* @param element
* @param [link] A post-link function, or an object with function(s) registered via pre and post properties.
* @returns An object containing the linking functions.
*/
compile: function(element, link){
// Normalize the link parameter
if(angular.isFunction(link)){
link = { post: link };
}
// Break the recursion loop by removing the contents
var contents = element.contents().remove();
var compiledContents;
return {
pre: (link && link.pre) ? link.pre : null,
/**
* Compiles and re-adds the contents
*/
post: function(scope, element){
// Compile the contents
if(!compiledContents){
compiledContents = $compile(contents);
}
// Re-add the compiled contents to the element
compiledContents(scope, function(clone){
element.append(clone);
});
// Call the post-linking function, if any
if(link && link.post){
link.post.apply(null, arguments);
}
}
};
}
};
}]);
With this service you can easily make a tree directive (or other recursive directives). Here is an example of an tree directive:
module.directive("tree", function(RecursionHelper) {
return {
restrict: "E",
scope: {family: '='},
template:
'<p>{{ family.name }}</p>'+
'<ul>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child"></tree>' +
'</li>' +
'</ul>',
compile: function(element) {
return RecursionHelper.compile(element);
}
};
});
See this Plunker for a demo. I like this solution best because:
Update: Added support for a custom linking functions.
This one seems a bit more complete: https://github.com/dump247/angular.tree
Here is an example using a recursive directive: http://jsfiddle.net/n8dPm/ Taken from https://groups.google.com/forum/#!topic/angular/vswXTes_FtM
module.directive("tree", function($compile) {
return {
restrict: "E",
scope: {family: '='},
template:
'<p>{{ family.name }}</p>'+
'<ul>' +
'<li ng-repeat="child in family.children">' +
'<tree family="child"></tree>' +
'</li>' +
'</ul>',
compile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function(clone, scope) {
iElement.append(clone);
});
};
}
};
});
angular-ui-tree seems to do a good job for me
Another example based off the original source, with a sample tree structure already in place (easier to see how it works IMO) and a filter to search the tree:
So many great solutions, but I feel they all in one way or another over-complicate things a bit.
I wanted to create something that recreated the simplicity of @Mark Lagendijk's awnser, but without it defining a template in the directive, but rather would let the "user" create the template in HTML...
With ideas taken from https://github.com/stackfull/angular-tree-repeat etc... I ended up with creating the project: https://github.com/dotJEM/angular-tree
Which allows you to build your tree like:
<ul dx-start-with="rootNode">
<li ng-repeat="node in $dxPrior.nodes">
{{ node.name }}
<ul dx-connect="node"/>
</li>
</ul>
Which to me is cleaner than having to create multiple directives for differently structured trees.... In essence calling the above a tree is a bit false, it picks much more off from @ganaraj's awnser of "recursive templates", but allows us to define the template where we need the tree.
(you could do that with a script tag based template, but it still has to sit right outside the actual tree node, and it still just feels a bit yuk...)
Left here for just another choice...
You can try with Angular-Tree-DnD sample with Angular-Ui-Tree, but i edited, compatibility with table, grid, list.
Yes it definitely possible. The question here probably assumes Angular 1.x, but for future reference I am including an Angular 2 example:
Conceptually all you have to do is create a recursive template:
<ul>
<li *for="#dir of directories">
<span><input type="checkbox" [checked]="dir.checked" (click)="dir.check()" /></span>
<span (click)="dir.toggle()">{{ dir.name }}</span>
<div *if="dir.expanded">
<ul *for="#file of dir.files">
{{file}}
</ul>
<tree-view [directories]="dir.directories"></tree-view>
</div>
</li>
</ul>
You then bind a tree object to the template and let Angular work its magic. This concept is obviously applicable to Angular 1.x as well.
Here is a complete example: http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0
You can use angular-recursion-injector for that: https://github.com/knyga/angular-recursion-injector
Allows you to do unlimited depth nesting with conditioning. Does recompilation only if needed and compiles only right elements. No magic in code.
<div class="node">
<span>{{name}}</span>
<node--recursion recursion-if="subNode" ng-model="subNode"></node--recursion>
</div>
One of the things that allows it to work faster and simpler then the other solutions is "--recursion" suffix.