Recursion with ng-repeat in Angular Recursion with ng-repeat in Angular angularjs angularjs

Recursion with ng-repeat in Angular


You can simply use ng-include to make a partial and call it recursively:Partial should be something like this:

<ul>    <li ng-repeat="item in item.subItems">      <a href="{{item.href}}" ng-class="{'nav-link nav-toggle': item.subItems && item.subItems.length > 0}">          <span class="title">{{item.text}}</span>      </a>      <div ng-switch on="item.subItems.length > 0">        <div ng-switch-when="true">          <div ng-init="subItems = item.subItems;" ng-include="'partialSubItems.html'"></div>          </div>      </div>    </li></ul>

And your html:

<ul class="page-sidebar-menu" data-keep-expanded="false" data-auto-scroll="true" data-slide-speed="200" ng-class="{'page-sidebar-menu-closed': settings.layout.pageSidebarClosed}">    <li ng-repeat="item in menuItems" ng-class="{'start': item.isStart, 'nav-item': item.isNavItem}">        <a href="{{item.href}}" ng-class="{'nav-link nav-toggle': item.subItems && item.subItems.length > 0}">            <span class="title">{{item.text}}</span>        </a>        <ul ng-if="item.subItems && item.subItems.length > 0" class="sub-menu">            <li ng-repeat="item in item.subItems" ng-class="{'start': item.isStart, 'nav-item': item.isNavItem}">                 <a href="{{item.href}}" ng-class="{'nav-link nav-toggle': item.subItems && item.subItems.length > 0}">                    <span class="title">{{item.text}}</span>                </a>                 <div ng-switch on="item.subItems.length > 0">                    <div ng-switch-when="true">                      <div ng-init="subItems = item.subItems;" ng-include="'newpartial.html'"></div>                      </div>                </div>            </li>        </ul>    </li></ul>

Here is the working plunkerhttp://plnkr.co/edit/9HJZzV4cgacK92xxQOr0?p=preview


You can achieve this simply by including a javascript template and template include using ng-include

define javascript template

<script type="text/ng-template" id="menu.html">...</script>

and include it like:

<div ng-if="item.subItems.length" ng-include="'menu.html'"></div>

Example: In this example I used basic html without any class you can use classes as you need. I just shown basic recursion structure.

In html:

<ul>    <li ng-repeat="item in menuItems">      <a href="{{item.href}}">        <span>{{item.text}}</span>      </a>      <div ng-if="item.subItems.length" ng-include="'menu.html'"></div>    </li></ul><script type="text/ng-template" id="menu.html">   <ul>      <li ng-repeat="item in item.subItems">        <a href="{{item.href}}">          <span>{{item.text}}</span>        </a>        <div ng-if="item.subItems.length" ng-include="'menu.html'"></div>      </li>   </ul></script>

PLUNKER DEMO


If your intention is to draw an menu with indefinite level of sub items, probably a good implementation is to make a directive.

With a directive you will be able to assume more control over your menu.

I created a basic exepmle with full recursion, on with you can see an easy implementation of more than one menu on the same page and more than 3 levels on one of the menus, see this plunker.

Code:

.directive('myMenu', ['$parse', function($parse) {    return {      restrict: 'A',      scope: true,      template:        '<li ng-repeat="item in List" ' +        'ng-class="{\'start\': item.isStart, \'nav-item\': item.isNavItem}">' +        '<a href="{{item.href}}" ng-class="{\'nav-link nav-toggle\': item.subItems && item.subItems.length > 0}">'+        '<span class="title"> {{item.text}}</span> ' +        '</a>'+        '<ul my-menu="item.subItems" class="sub-menu"> </ul>' +        '</li>',      link: function(scope,element,attrs){        //this List will be scope invariant so if you do multiple directive         //menus all of them wil now what list to use        scope.List = $parse(attrs.myMenu)(scope);      }    }}])

Markup:

<ul class="page-sidebar-menu"     data-keep-expanded="false"     data-auto-scroll="true"     data-slide-speed="200"     ng-class="{'page-sidebar-menu-closed': settings.layout.pageSidebarClosed}"    my-menu="menuItems"></ul>

Edit

Some notes

When it comes to make the decision about ng-include(that i think is a fair enough solution) or .directive, you have to ask yourself at least two questions first. Does my code fragment going to need some logic? If not you could as well just go for the ng-include.But if you are going to put more logic in the fragment making it customizable, make changes to the element or attrs (DOM) manipulation, you should go for directive.As well one point that make me fell more confortable with the directive is reusability of the code you write, since in my example you can give controll more and make a more generic one i assume you should go for that if your project is big and needs to grow. So the second question does my code going to be reusable?

A reminder for a clean directive is that instead of template you could use the templateUrl, and you can give a file to feed the html code that is currently in the template.

If you are using 1.5+ you can now choose to use .component. Component is a wrapper arroud .direcitve that has a lot less boilerplate code. Here you can see the diference.

                  Directive                Componentbindings          No                       Yes (binds to controller)bindToController  Yes (default: false)     No (use bindings instead)compile function  Yes                      Nocontroller        Yes                      Yes (default function() {})controllerAs      Yes (default: false)     Yes (default: $ctrl)link functions    Yes                      NomultiElement      Yes                      Nopriority          Yes                      Norequire           Yes                      Yesrestrict          Yes                      No (restricted to elements only)scope             Yes (default: false)     No (scope is always isolate)template          Yes                      Yes, injectabletemplateNamespace Yes                      NotemplateUrl       Yes                      Yes, injectableterminal          Yes                      Notransclude        Yes (default: false)     Yes (default: false)

Source angular guide for component

Edit

As sugested by Mathew Berg if you want not to include the ul element if the list of subItems is empty you can change the ul to this something like this <ul ng-if="item.subItems.length>0" my-menu="item.subItems" class="sub-menu"> </ul>