Recursion with ng-repeat in Angular

前端 未结 10 1269
心在旅途
心在旅途 2020-12-15 19:17

I have the following data structure for items in my sidemenu, in an Angular app based on a paid-for web site theme. The data structure is my own, and the menu is derived fro

相关标签:
10条回答
  • 2020-12-15 19:54

    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

    0 讨论(0)
  • 2020-12-15 19:57

    Before using templates with ng-include or writing your own directive, I would suggest that you consider using an existing tree component implementation.
    The reason is that from your description, that's exactly what you need. You have a hierarchical tree-like data structure that you want to display. It sounds obvious to me that you need a tree component.

    Take a look at the following implementations (1st one preferable):
    https://github.com/angular-ui-tree/angular-ui-tree
    https://github.com/wix/angular-tree-control
    http://ngmodules.org/modules/angular.treeview

    All of the above require only that you do a minor adjustment to your model, or alternatively, use a proxy model.

    If you do insist on implementing it by yourself (and no matter how you end up doing it, essentially you still will be implementing a tree component from scratch), I would suggest the directive approach as proposed in previous answers. Here's how I'd do it:

    JS

    var app=angular.module('MyApp', []);
    
    app.controller('MyCtrl', function($scope, $window) {
      $scope.menuItems = [
        {
            "isNavItem": true,
            "href": "#/dashboard.html",
            "text": "Dashboard"
        },
        {
            "isNavItem": true,
            "href": "javascript:;",
            "text": "AngularJS Features",
            "subItems": [
                {
                    "href": "#/ui_bootstrap.html",
                    "text": " UI Bootstrap"
                }
            ]
        },
        {
            "isNavItem": true,
            "href": "javascript:;",
            "text": "jQuery Plugins",
            "subItems": [
                {
                    "href": "#/form-tools",
                    "text": " Form Tools"
                },
                {
                    "isNavItem": true,
                    "href": "javascript:;",
                    "text": " Datatables",
                    "subItems": [
                        {
                            "href": "#/datatables/managed.html",
                            "text": " Managed Datatables"
                        }
                    ]
                }
            ]
        }];
    });
    
    app.directive('myMenu', ['$compile', function($compile) {
      return {
        restrict: 'E',
        scope: {
          menu: '='      
        },
        replace: true,
        link: function(scope, elem, attrs) {
          var items = $compile('<my-menu-item ng-repeat="item in menu" menu-item="item"></my-menu-item>')(scope);
    
          elem.append(items);
        },
        template: '<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}"></ul>'
      };
    }]);
    
    app.directive('myMenuItem', [function() {
      return {
        restrict: 'E',
        scope: {
          menuItem: '='
        },
        replace: true,
        template: '<li ng-class="{\'start\': item.isStart, \'nav-item\': item.isNavItem}"><a href="{{menuItem.href}}" ng-class="{\'nav-link nav-toggle\': menuItem.subItems && menuItem.subItems.length > 0}"> <span class="title">{{menuItem.text}}</span></a><my-menu menu="menuItem.subItems"></my-menu></li>'
    
      };
    }]);
    

    HTML

    <div ng-app="MyApp" ng-controller="MyCtrl">
      <my-menu menu="menuItems"></my-menu>
    </div>
    

    Here's a working CodePen example: http://codepen.io/eitanfar/pen/oxZrpQ

    Some notes

    1. You don't have to use 2 directives ("my-menu", "my-menu-item"), you can use just 1 (simply replace the ng-repeat of "my-menu-item" with its template), however, I think it's more coherent this way
    2. The reason that the directive solution you tried didn't work (an educated guess, as I haven't debugged your attempt), is that it runs into an infinite loop. It does so, since linking happens for internal elements first. What I do in my suggested solution, is to postpone the linking of the sub items till after the linking of the parent menu is finished. Any disadvantages that this might have can be overcome by providing references in the scope (as I provide the 'menuItem' binding).

    Hope this helps.

    0 讨论(0)
  • 2020-12-15 20:00

    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 plunker http://plnkr.co/edit/9HJZzV4cgacK92xxQOr0?p=preview

    0 讨论(0)
  • 2020-12-15 20:05

    Do you mean something like this? http://jsfiddle.net/uXbn6/3639/

    JS

    angular.module("myApp", []).controller("TreeController", ['$scope',function($scope) {
    
    
        $scope.menuItems = [{
          "isNavItem": true,
          "href": "#/dashboard.html",
          "text": "Dashboard"
        }, {
          "isNavItem": true,
          "href": "javascript:;",
          "text": "AngularJS Features",
          "subItems": [{
            "href": "#/ui_bootstrap.html",
            "text": " UI Bootstrap"
          }, {
            "isNavItem": true,
            "href": "javascript:;",
            "text": "AngularJS Features",
            "subItems": [{
              "href": "#/ui_bootstrap.html",
              "text": " UI Bootstrap"
            }]
          }]
        }, {
          "isNavItem": true,
          "href": "javascript:;",
          "text": "jQuery Plugins",
          "subItems": [{
            "href": "#/form-tools",
            "text": " Form Tools"
          }, {
            "isNavItem": true,
            "href": "javascript:;",
            "text": " Datatables",
            "subItems": [{
              "href": "#/datatables/managed.html",
              "text": " Managed Datatables"
            }]
          }]
        }];
      }]);
    

    HTML

      <script type="text/ng-template" id="tree_item_renderer.html">
        <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}" ng-include="'tree_item_renderer.html'"></li>
        </ul>
      </script>
    
      <div ng-app="myApp" ng-controller="TreeController">
        <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}" ng-include="'tree_item_renderer.html'"></li>
        </ul>
      </div>
    
    0 讨论(0)
提交回复
热议问题