如何处理 angularjs 中的树状结构?

How to handle tree like structure in angularjs?

我正在尝试在复杂的 angular.js 网络应用程序中以树的形式实现层次结构。我正在使用嵌套的 ng-repeats 来渲染结构,但我在 IE 10 中遇到了与性能相关的重大问题,在 chrome 中遇到了小的性能问题。将使用的数据在最终级别将包含多达 5,000 个条目。 根据我的研究,我认为以下可能是其背后的原因:

  1. 大量观察者元素。 为了解决这个问题,我已经实施了一次数据绑定,观察者的数量并不多。
  2. 浏览器重绘时间: ng-repeat 将元素一个一个地添加到 DOM 中。这可能会导致浏览器引擎过载多次呈现复杂的 HTML,从而导致大量滞后。 为了解决这个问题,我应用了一种延迟加载技术,仅在一个节点折叠时才渲染子节点。我仍然在渲染节点时遇到明显的延迟,其中要渲染的节点数量很大。
  3. CSS 类: 我尝试通过从节点元素中剥离所有 类 来实现树结构。这带来了显着的改进,但删除 类 并不是一个真正的选择。此外,如果我为元素提供内联样式,那么它也会带来更好的性能。
  4. Angular material的性能问题: Angular material 是我的网络应用程序的组成部分。在调查了 angular material 用户针对大量 ng-repeats 提交的问题后,已经推出了修复程序。但是升级到最新版本也没有帮助。

refer给这张图片进行table设计。用于创建树的模板如下:

<li ng-repeat="item in ::item.childLevelDetails" >
 <div >
    <a ng-click="toggle(this)" class="icon icon-stream-add-2">
      <span></span>
    </a>
    <div class="unitTextDiv">{{::item.title}}</div>
 </div>
 <ol ng-include= "'tree_node'">
</li>

请您提出任何可能的解决方案。

angular-ui-grid可以作为一种选择。它使用虚拟化并仅呈现可见的行。因此,它在处理大量行时也表现良好。它附带了很棒的文档和示例 http://ui-grid.info/docs/#/tutorial

请参阅分组示例以确保这对您的用例有所帮助http://ui-grid.info/docs/#/tutorial/209_grouping

使用时间stick with this potion and finalize according to black magic

你可以试试我为你做的这个递归示例。 对 showing/hidding 元素使用 ng-if 将减少监视的数量。

在这里找到 Fiddler

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

 myApp.controller('MyCtrl', ['$scope','$timeout', 'getWatchCount' , function ($scope ,$timeout, getWatchCount ){

 $scope.tree = [
 {title:'Element level 1',
     elements: [
      { title: 'Element level 1.1'},
        { title: 'Element level 1.2',
          elements: [
   { title: 'Element level 1.2.2'},
   { title: 'Element level 1.2.2'},
          ]}
    ]},
     {title:'Element level 2'}
  ]
 
 
 //NEXT CODE ONLY USED FOR COUNTING WATCHES//
 $scope.countWatches = function(){
  $scope.numberOfWatches = getWatchCount();
 }
 
 $timeout(function(){$scope.countWatches()} , 0 );
 
     
 // I return the count of watchers on the current page.
function getWatchCount() {

    // Keep track of the total number of watch bindings on the page.
    var total = 0;

    // There are cases in which two different ng-scope markers will actually be referencing
    // the same scope, such as with transclusion into an existing scope (ie, cloning a node
    // and then linking it with an existing scope, not a new one). As such, we need to make
    // sure that we don't double-count scopes.
    var scopeIds = {};

    // AngularJS denotes new scopes in the HTML markup by appending the classes "ng-scope"
    // and "ng-isolate-scope" to appropriate elements. As such, rather than attempting to
    // navigate the hierarchical Scope tree, we can simply query the DOM for the individual
    // scopes. Then, we can pluck the watcher-count from each scope.
    // --
    // NOTE: Ordinarily, it would be a HUGE SIN for an AngularJS service to access the DOM
    // (Document Object Model). But, in this case, we're not really building a true AngularJS
    // service, so we can break the rules a bit.
    angular.forEach(
        document.querySelectorAll( ".ng-scope , .ng-isolate-scope" ),
        countWatchersInNode
    );

    return( total );


    // ---
    // PRIVATE METHODS.
    // ---


    // I count the $watchers in to the scopes (regular and isolate) associated with the given
    // element node, and add the count to the running total.
    function countWatchersInNode( node ) {

        // Get the current, wrapped element.
        var element = angular.element( node );

        // It seems that in earlier versions of AngularJS, the separation between the regular
        // scope and the isolate scope where not as strong. The element was flagged as having
        // an isolate scope (using the ng-isolate-scope class); but, there was no .isolateScope()
        // method before AngularJS 1.2. As such, in earlier versions of AngularJS, we have to
        // fall back to using the .scope() method for both regular and isolate scopes.
        if ( element.hasClass( "ng-isolate-scope" ) && element.isolateScope ) {

            countWatchersInScope( element.isolateScope() );

        }

        // This class denotes a non-isolate scope in later versions of AngularJS; but,
        // possibly an isolate-scope in earlier versions of AngularJS (1.0.8).
        if ( element.hasClass( "ng-scope" ) ) {

            countWatchersInScope( element.scope() );

        }

    }


    // I count the $$watchers in the given scope and add the count to the running total.
    function countWatchersInScope( scope ) {

        // Make sure we're not double-counting this scope.
        if ( scopeIds.hasOwnProperty( scope.$id ) ) {

            return;

        }

        scopeIds[ scope.$id ] = true;

        // The $$watchers value starts out as NULL until the first watcher is bound. As such,
        // the $$watchers collection may not exist yet on this scope.
        if ( scope.$$watchers ) {

            total += scope.$$watchers.length;

        }

    }

}
 
 }]);

myApp.factory(
   "getWatchCount",
   function() {

    // I return the count of watchers on the current page.
    function getWatchCount() {

     var total = 0;

     // AngularJS denotes new scopes in the HTML markup by appending the
     // class "ng-scope" to appropriate elements. As such, rather than 
     // attempting to navigate the hierarchical Scope tree, we can simply
     // query the DOM for the individual scopes. Then, we can pluck the 
     // watcher-count from each scope.
     // --
     // NOTE: Ordinarily, it would be a HUGE SIN for an AngularJS service
     // to access the DOM (Document Object Model). But, in this case,
     // we're not really building a true AngularJS service, so we can 
     // break the rules a bit.
     angular.element( ".ng-scope" ).each(
      function ngScopeIterator() {

       // Get the scope associated with this element node.
       var scope = $( this ).scope();

       // The $$watchers value starts out as NULL. 
       total += scope.$$watchers
        ? scope.$$watchers.length
        : 0
       ;

      }
     );
     
     return( total );

    }

    // For convenience, let's serialize the above method and convert it to 
    // a bookmarklet that can easily be run on ANY AngularJS page. 
    getWatchCount.bookmarklet = ( 
     "javascript:alert('Watchers:'+(" + 
     getWatchCount.toString()
      .replace( /\/\/.*/g, " " )
      .replace( /\s+/g, " " ) +
     ")());void(0);" 
    );

    return( getWatchCount );

   }
  );
ul{
    list-style-type: none;
}

li{
    font-size:13px;
}
.arrow{
     width: 0;
    height: 0;
    border-top: 7px solid transparent;
    border-bottom: 7px solid transparent;
    border-right: 7px solid transparent;
    cursor: pointer;
    margin-left: 5px;
    border-left: 7px solid #000;
    display: inline-block;
    transition:all 0.3s; 
}

.arrow.expand {
    transform: rotate(45deg);
    transform-origin: 20% 50%;
    margin-top: 0;
}

.arrow.none {
    border-left: 7px solid #ccc;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl" >

    <p>
  <strong>Watch Count:</strong> {{ numberOfWatches }}
    </p>
    
    <script type="text/ng-template" id="elementTree">
        <li>
            <div class="arrow"
                 
                 ng-class="{expand:element.isOpen,none:!element.elements}"
                 ng-click="$apply(element.isOpen = !element.isOpen) ; countWatches()">
            </div>
            {{element.title}}
        </li>
        <div ng-if="element.isOpen">
            <ul
                ng-repeat="element in element.elements"
                ng-include="'elementTree'">
             </ul
        </div>
        
    </script>
    
    <ul ng-repeat="element in tree"
         ng-include="'elementTree'">
    </ul>
    
    
</div>