如何处理 angularjs 中的树状结构?
How to handle tree like structure in angularjs?
我正在尝试在复杂的 angular.js 网络应用程序中以树的形式实现层次结构。我正在使用嵌套的 ng-repeats 来渲染结构,但我在 IE 10 中遇到了与性能相关的重大问题,在 chrome 中遇到了小的性能问题。将使用的数据在最终级别将包含多达 5,000 个条目。
根据我的研究,我认为以下可能是其背后的原因:
- 大量观察者元素。
为了解决这个问题,我已经实施了一次数据绑定,观察者的数量并不多。
- 浏览器重绘时间:
ng-repeat 将元素一个一个地添加到 DOM 中。这可能会导致浏览器引擎过载多次呈现复杂的 HTML,从而导致大量滞后。
为了解决这个问题,我应用了一种延迟加载技术,仅在一个节点折叠时才渲染子节点。我仍然在渲染节点时遇到明显的延迟,其中要渲染的节点数量很大。
- CSS 类:
我尝试通过从节点元素中剥离所有 类 来实现树结构。这带来了显着的改进,但删除 类 并不是一个真正的选择。此外,如果我为元素提供内联样式,那么它也会带来更好的性能。
- 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>
我正在尝试在复杂的 angular.js 网络应用程序中以树的形式实现层次结构。我正在使用嵌套的 ng-repeats 来渲染结构,但我在 IE 10 中遇到了与性能相关的重大问题,在 chrome 中遇到了小的性能问题。将使用的数据在最终级别将包含多达 5,000 个条目。 根据我的研究,我认为以下可能是其背后的原因:
- 大量观察者元素。 为了解决这个问题,我已经实施了一次数据绑定,观察者的数量并不多。
- 浏览器重绘时间: ng-repeat 将元素一个一个地添加到 DOM 中。这可能会导致浏览器引擎过载多次呈现复杂的 HTML,从而导致大量滞后。 为了解决这个问题,我应用了一种延迟加载技术,仅在一个节点折叠时才渲染子节点。我仍然在渲染节点时遇到明显的延迟,其中要渲染的节点数量很大。
- CSS 类: 我尝试通过从节点元素中剥离所有 类 来实现树结构。这带来了显着的改进,但删除 类 并不是一个真正的选择。此外,如果我为元素提供内联样式,那么它也会带来更好的性能。
- 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>