3

I tried to implement a recursively nested directive in angularjs.

I simply made a <tree-dir> directive and obtained this jsFiddle.

In my tree_item_renderer.html template, if I replace the ng-include by the <tree-dir>, angular will enter in an infinite loop when compiling (be careful if you try it, it will slow down or stop your browser for a while).

Source: this question about recursive trees in angular.

Community
  • 1
  • 1
arthur.sw
  • 11,052
  • 9
  • 47
  • 104

2 Answers2

4

This answer explains and solves everything, here are the most important parts:

"When making a tree view the best solution is a 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:

  1. You don't need an special directive which makes your html less clean.
  2. The recursion logic is abstracted away into the RecursionHelper service, so you keep your directives clean."
Community
  • 1
  • 1
arthur.sw
  • 11,052
  • 9
  • 47
  • 104
  • Have a look at http://benfoster.io/blog/angularjs-recursive-templates I don't think any of what you suggest is necessary. It's really easy to build nested directives. – orange Aug 04 '14 at 14:02
  • That solution worked perfectly, thanks! I really like how little you need to do to consume it - just supply the compile function - although you should pass in the link function as well (the compile method had good comments so that was easy enough to figure out). Well done. :) – Allan Nienhuis Jul 15 '15 at 23:45
0

I think you need to pass the child node to the child directive (via a scope variable). In your example the root is always taken and rendered over and over again.

Or follow this example. The template logic is perfectly capable of rendering nested directives.

orange
  • 7,755
  • 14
  • 75
  • 139