I learnt a new trick over the weekend using Angular, how to build a recursive tree of objects using directives. In this post I want to share how to do it.
Let’s say that you have some data that looks like this, it can be as deep as you want:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
And using this data you want to build a tree, e.g.:
Europe
Italy
Rome
Milan
Spain
South America
Brasil
Peru
So to build something like this you will need some kind of recursive code to loop over all the elements and their children.
Let’s start with the html:
1 2 3 4 5 6 7 |
|
First we have a controller ‘IndexCtrl’ which looks like this:
1 2 3 4 5 6 |
|
Here we have $scope.locations
pointing to the array of locations we want to render in our tree.
Then we need a directive for rendering a collection, the html for the collection looks like this:
1 2 3 |
|
This directive takes a collection
parameter which are the locations we want to render.
Our directive definition looks like this:
1 2 3 4 5 6 7 8 9 10 |
|
- restrict: ‘E’ tells angular that we want to apply this directive to any html tags matching
collection
. - replace: true tells angular that we want to replace the tag with the content of specified template in the directive.
- scope: creates a new scope for the directive.
- collection: ‘=’ tells angular to use the
collection
attribute in the directive and create a variable in the directive scope with the same name,=
means that it should be passed as an object. - template: “…” is the new html that will be inserted instead of the original tag.
Notice the following code in the template:
1
|
|
This is another directive, used to render each member of the collection (we use the build-in ng-repeat for looping), in this directive we pass the current member as the member
attribute.
The directive for member looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
This looks a lot like the collection directive, except for the link function. I will explain what is happening here, but before that, let me tell you about my first approach for trying to do this.
The first thing I tried is to simply add the collection directive inside the template:
1
|
|
But this doesn’t work, it throws angular into an endless loop, because it tries to render the collection directive regardless if the member has children or not.
So instead you need to add the collection directive manually only if there are children, thus the link function:
1 2 3 4 5 6 7 8 9 |
|
Note the line $compile(element.contents())(scope);
. As the html is appended manually we need to tell angular to re-render the directive.
That is it, here is the complete example. Thanks.
Update 2013-12-31: Please read the comments, there are some good suggestions on how to do this better.