Recursion in Angular directives Recursion in Angular directives javascript javascript

Recursion in Angular directives


Inspired by the solutions described in the thread mentioned by @dnc253, I abstracted the recursion 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);                    }                }            };        }    };}]);

Which is used as follows:

module.directive("tree", ["RecursionHelper", 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) {            // Use the compile function from the RecursionHelper,            // And return the linking function(s) which it returns            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.

Update: As of Angular 1.5.x, no more tricks are required, but works only with template, not with templateUrl


Manually adding elements and compiling them is definitely a perfect approach. If you use ng-repeat then you will not have to manually remove elements.

Demo: http://jsfiddle.net/KNM4q/113/

.directive('tree', function ($compile) {return {    restrict: 'E',    terminal: true,    scope: { val: '=', parentData:'=' },    link: function (scope, element, attrs) {        var template = '<span>{{val.text}}</span>';        template += '<button ng-click="deleteMe()" ng-show="val.text">delete</button>';        if (angular.isArray(scope.val.items)) {            template += '<ul class="indent"><li ng-repeat="item in val.items"><tree val="item" parent-data="val.items"></tree></li></ul>';        }        scope.deleteMe = function(index) {            if(scope.parentData) {                var itemIndex = scope.parentData.indexOf(scope.val);                scope.parentData.splice(itemIndex,1);            }            scope.val = {};        };        var newElement = angular.element(template);        $compile(newElement)(scope);        element.replaceWith(newElement);    }}});


I don't know for sure if this solution is found in one of the examples you linked or the same basic concept, but I had a need of a recursive directive, and I found a great, easy solution.

module.directive("recursive", function($compile) {    return {        restrict: "EACM",        priority: 100000,        compile: function(tElement, tAttr) {            var contents = tElement.contents().remove();            var compiledContents;            return function(scope, iElement, iAttr) {                if(!compiledContents) {                    compiledContents = $compile(contents);                }                iElement.append(                    compiledContents(scope,                                      function(clone) {                                         return clone; }));            };        }    };});module.directive("tree", function() {    return {        scope: {tree: '='},        template: '<p>{{ tree.text }}</p><ul><li ng-repeat="child in tree.children"><recursive><span tree="child"></span></recursive></li></ul>',        compile: function() {            return  function() {            }        }    };});​

You should create the recursive directive and then wrap it around the element that makes the recursive call.