Looping through deep objects in ng-repeat

angularjs, javascript

Solution

Here you go:

html

<div ng-app="app" ng-controller="test">
   <ul>
       <li nested-item ng-repeat="item in items">{{item.title}}</li>
   </ul>         
</div>

JavaScript

var items = [{
    title: 'Something',
    children: [
        { title: 'Hello World' },
        { title: 'Hello Overflow' },
        { title: 'John Doe', children: [
            { title: 'Amazing title' },
            { title: 'Google it' },
            { title: 'Im a child', children: [
                { title: 'Another ' },
                { title: 'He\'s my brother' },
                { title: 'She\'s my mother.', children: [
                    {title: 'You never know if im going to have children'}
                ]}
            ]}
        ]}
    ]
}];

var app = angular.module('app', []);

app.controller('test', function( $scope ) {
    $scope.items = items;
});


app.directive('nestedItem', ['$compile', function($compile){
    return {
        restrict: 'A',
        link: function(scope, element){
        console.log(element);
            if (scope.item.children){
                var html = $compile('<ul><li nested-item ng-repeat="item in item.children">{{item.title}}</li></ul>')(scope);
                element.append(html);
            }
        }
    };
}]);

I forked your fiddle:

http://jsfiddle.net/c4Kp8/

Actually I must confess that I like Master Morality's approach but you can also go with a custom directive. The key thing to know if you go that route is that you need to intercept on the item level to manually check if the current item has children, and if so, $compile the directive for the node yourself.

UPDATE

However, there is one thing that should bother us in the above code. The duplication of html code (inlined in the directive) is a code smell. If you like, you can get really funky and fix this by introducing a generic `template-code` directive which doesn't do anything else but providing the code of the node where it is applied on as a template for other directives.

So then our solution would look like this:

html

<div ng-app="app" ng-controller="test">
   <ul template-code>
       <li nested-item ng-repeat="item in items">{{item.title}}</li>
   </ul>         
</div>

JavaScript

var items = [{
    title: 'Something',
    children: [
        { title: 'Hello World' },
        { title: 'Hello Overflow' },
        { title: 'John Doe', children: [
            { title: 'Amazing title' },
            { title: 'Google it' },
            { title: 'Im a child', children: [
                { title: 'Another ' },
                { title: 'He\'s my brother' },
                { title: 'She\'s my mother.', children: [
                    {title: 'You never know if im going to have children'}
                ]}
            ]}
        ]}
    ]
}];

var app = angular.module('app', []);

app.controller('test', function( $scope ) {
    $scope.items = items;
});

app.directive('templateCode', function(){
    return {
        restrict: 'A',
        controller: function(){},
        compile: function(element){
            element.removeAttr('template-code');
            //ATTENTION: We need to trim() here. Otherwise AngularJS raises an exception
            //later when we want to use the templateCode in a $compile function. 
            //Be aware that we assume a modern browser 
            //that already ships with a trim function.
            //It's easy to secure that with a polyfill.
            var templateCode = element.parent().html().trim();
            return function(scope, iElement, iAttrs, controller){
                controller.templateCode = templateCode;
            }
        }
    }
});

app.directive('nestedItem', ['$compile', function($compile){
    return {
        restrict: 'A',
        require: '^templateCode',
        link: function(scope, element, iAttr, controller){ 
            if (scope.item.children){
                scope.items = scope.item.children;         
                var html = $compile(controller.templateCode)(scope);
                element.append(html);
            }
        }
    };
}]);

Plunker: http://jsfiddle.net/2rQWf/

Problem

I'm in angular and i have a object like this. ``` var items = [{ title: 'Something', children: [ { title: 'Hello World' }, { title: 'Hello Overflow' }, { title: 'John Doe', children: [ { title: 'Amazing title' }, { title: 'Google it' }, { title: 'I'm a child', children: [ { title: 'Another ' }, { title: 'He\'s my brother' }, { title: 'She\'s my mother.', children: [ {title: 'You never know if I'm going to have children'} ]} ]} ]} ] }]; ``` I wan't to loop through all of these so i have something like this. • Something • Hello World • Hello Overflow • John Doe • Amazing Title • Google it • I'm a child • Another • He's my brother • She's my mother • You never know if I'm going to have children The problem is I wouldn't know how deep this object will go or what's in it. so I wouldn't be able to do it manually. I have done a basic loop with `ng-repeat` in the fiddle provided at the bottom, but i can't figure out how I can automatically loop through these and create nested `<ul>`'s and `<li>`'s. What would be the best way to accomplish this? Demo: http://jsfiddle.net/XtgLM/

Original source