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

时间:2015-11-04 14:57:41

标签: angularjs performance angularjs-ng-repeat internet-explorer-10

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

  1. 观众人数众多。 为了解决这个问题,我已经实现了一次数据绑定,并且观察者的数量并不高。
  2. 浏览器重绘时间 ng-repeat将元素逐个添加到DOM中。这可能导致浏览器引擎过载以多次渲染复杂的HTML,从而导致大量滞后。 为了解决这个问题,我通过仅在折叠一个节点时渲染子项来应用延迟加载技术。我仍然在渲染节点时遇到可观察到的延迟,其中要渲染的节点数量很大。
  3. CSS类: 我尝试通过从节点元素中删除所有类来实现树结构。这导致了显着的改进,但删除类不是一个真正的选择。此外,如果我为元素提供内联样式,那么它也会带来更好的性能。
  4. Angular材料的性能问题: 角度材料是我的网络应用程序的组成部分。在查看角度材料用户提交的大量ng-repeats已经推出修复的问题之后。但升级到最新版本也没有任何帮助。
  5. 请为此图片提供refer表设计。用于创建树的模板如下:

    <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>
    

    请求您为此问题提出任何可能的解决方案。

3 个答案:

答案 0 :(得分:0)

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

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

有所帮助

答案 1 :(得分:0)

stick一起使用potion的时间,并根据black magic完成!

答案 2 :(得分:0)

你可以尝试我为你做的这个递归样本。 使用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>