10

I'm trying to create a treeview using AngularJS.

Here's my code:

module.directive('treeview', function () {
    return {
        restrict: 'E',
        templateUrl: "/templates/ui/controls/treeview.htm",
        replace: true,
        transclude: true,
        scope: {},
        link: function (scope, element, attrs) {
            console.log("treeview directive loaded");
        },
        controller: function ($scope, $rootScope) {
            $rootScope.depth = 0;
            $scope.items = [
                { text: "face" },
                { text: "palm" },
                {
                    text: "cake",
                    childitems: [
                        { text: "1 face" },
                        { text: "1 palm" },
                        { text: "1 cake" }
                    ]
                }
            ];
        }
    };
});

module.directive('treeviewItem', function () {
    return {
        restrict: 'E',
        templateUrl: "/templates/ui/controls/treeview-item.htm",
        replace: true,
        scope: {
            item: "="
        },
        link: function (scope, element, attrs) {
            console.log("treeview item directive loaded");
        }
    };
});

Treeview template:

<div class="sl-treeview">
    <ul class="clear" ng-transclude>
        <treeview-item ng-repeat="item in items" item="item"></treeview-item>
    </ul>
</div>

Treeview Item template:

<li>
    <i class="icon-plus-sign"></i>
    <a href="/">
        <i class="icon-folder-close"></i>
        {{item.text}}
    </a>
    <!-- This ul is the issue - it crashes the page -->
    <ul>
        <treeview-item ng-repeat="childitem in item.childitems" item="childitem"></treeview-item>
    </ul>
</li>

In the treeview directive $scope.items is hardcoded for development - eventually I hope this will come from a controller/service pulling data from the server. However it represents the sort of basic structure I'm looking for.

When I run this without the nested ul in treeviewItem it gives me the first three items just fine. When i add the ul in to try and get the controls to bind with child items it hands the page and stops working.

JSFiddle without nested ul - working:

http://jsfiddle.net/BdmV3/

JSFiddle with nested ul - not working (and may hang your browser!):

http://jsfiddle.net/SKPpv/

How should I go about making a control that uses a custom directive and ngRepeat to create potentially infinite levels of recursion? Why isn't my approach working?

Jon
  • 3,173
  • 3
  • 39
  • 66

1 Answers1

15

The problem is that you're trying to define your directive recursively, when angular was trying to compile the template, it saw treeview directive, it called treeview's compile function, then it saw treeviewItem directive, it called treeviewItem's compile function, then it saw treeviewItem directive, it called treeviewItem's compile function,then it saw treeviewItem directive, it called treeviewItem's compile function...

See the problem? The calls to compile functions couldn't stop. So, you need to pull the recursive definition out of your template, but use $compile to build DOM manually:

module.directive('treeviewItem', function ($compile) {
    return {
        restrict: 'E',
        template: '<li><i class="icon-plus-sign"></i><a href="/"><i class="icon-folder-close"></i>{{item.text}}</a></li>',
        replace: true,
        scope: {
            item: "="
        },
        link: function (scope, element, attrs) {
            element.append($compile('<ul><treeview-item ng-repeat="childitem in item.childitems" item="childitem"></treeview-item></ul>')(scope));

            console.log("treeview item directive loaded");
        }
    };
});

http://jsfiddle.net/SKPpv/3/

Alternatively, I found a solution to display tree-like data on SO https://stackoverflow.com/a/11861030/69172. The solution however uses ngInclude instead of directives.

Community
  • 1
  • 1
Ye Liu
  • 8,946
  • 1
  • 38
  • 34
  • I came across $compile looking at other examples of treeviews and that was the option I took. It works beautifully. That you for the explanation of why my original approach doesn't work, it's obvious now! Out of interest, why are you watching `item.childitems`? Won't that add a ul for each child item? – Jon Aug 12 '13 at 10:34
  • @Jon I shouldn't use `$watch` like that in my original answer, it'll simple append another `ul` whenever `childitems` changes. I updated my answer without using `$watch`. Plus, can you share the treeview example you've found in your question so everyone can benefit more from your question? – Ye Liu Aug 12 '13 at 14:45
  • @JamesKyle why does it no longer work? What error are you getting? – EnigmaRM Feb 18 '14 at 18:48
  • 1
    @EnigmaRM Updated angular in the jsFiddle posted above: http://jsfiddle.net/SKPpv/6/ – James Kyle Feb 20 '14 at 22:08
  • @ye-liu If treeview cannot be inside a treeview, why ng-repeat inside a ng-repeat has no same problem? And all the offical directive. Is there some trick in it to avoid the endless recursion problem? – Amio Apr 29 '14 at 07:44