ng-repeat 中的函数导致无限 $digest-loop
Function in ng-repeat with track by causes Infinite $digest-loop
显然我还不了解 ng-repeat
、$$hashKeys
和 track by
背后的机制。
我目前在我的项目中使用 AngularJS 1.6。
问题:
我得到了一组复杂的对象,我想用它来在我的视图中呈现一个列表。但要获得所需的结果,我需要先修改(或 map/enhance/change)这些对象:
const sourceArray = [{id: 1, name: 'Dave'}, {id:2, name: Steve}]
const persons = sourceArray.map((e) => ({enhancedName: e.name + e.id}))
//Thus the content of persons is:
//[{enhancedName: 'Dave_1'}, {enhancedName: 'Steve_2'}]
将它绑定到视图应该像这样工作:
<div ng-repeat="person in ctrl.getPersons()">
{{person.enhancedName}}
</div>
然而,这显然会进入 $digest()
循环,因为每次调用时都会 .map
returns 新的对象实例。由于我通过一个函数将它绑定到 ng-repeat,它在每个 $digest
中都会重新评估,模型不稳定并且 Angular 不断重新运行 $digest
-cycles 因为这些对象被标记为 $dirty
.
为什么我很困惑
现在这不是一个新问题,有几种解决方案:
在an Angular-Issue from 2012 Igor Minar himself suggested to set the $$hashKey-Property manually to tell angular that the generated objects are the same. This is his working fiddle, but since even this very trivial example still ran into a $digest
-loop when i used it in my project, i tried upgrading the Angular-Version in the fiddle. For some reason it crashes.
好吧...因为 Angular 1.3 我们有 track by
应该基本上解决这个确切的问题。但是
<div ng-repeat="person in ctrl.getPersons() track by $index">
和
<div ng-repeat="person in ctrl.getPersons() track by person.enhancedName">
因 $digest
循环而崩溃。我的印象是 track by
语句应该让 angular 相信它适用于相同的对象,但显然情况并非如此,因为它只是不断检查它们的变化。老实说,我不知道如何正确调试这个问题的原因。
问题:
是否可以使用过滤/修改后的数组作为 ng-repeat 的数据源?
我不想将修改后的数组存储在我的控制器上,因为我需要不断更新它的数据,然后必须在控制器中手动维护和刷新它,而不是依赖数据绑定。
您提供的“it crashes”fiddle 没有为我生成无限摘要。事实上:它甚至没有成功 bootstrap Angular 应用程序(看起来 bootstrapping 在最新的 Angular 中无法以这种方式完成)。
我rewrote it使用我理解的Angularbootstrap机制。它重现了崩溃,就像你说的那样。
我找到了成功的方法track by stringified JSON。
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<script>
angular.module('myApp',[])
.controller('Ctrl', ['$scope', function($scope) {
angular.extend($scope, {
stringify: function(x) { return JSON.stringify(x) },
getList: function() {
return [
{name:'John', age:25},
{name:'Mary', age:28}
];
}
});
}]);
</script>
<div ng-app="myApp">
<div ng-controller="Ctrl">
I have {{getList().length}} friends. They are:
<ul>
<li ng-repeat="friend in getList() track by stringify(friend)">
[{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
</li>
</ul>
</div>
</div>
即我们提供跟踪功能,stringify()
。也可能有一个内置的 Angular。
track by $index
也有效——与您的发现相反。我认为 JsFiddle 稍微破坏了实验*
*以下为轶事。我相信我遇到了 JsFiddle 本身 的一些问题。例如:在我分叉 Fiddle 并在新的浏览上下文中再次尝试相同的代码之前,我的 track by stringify()
示例不起作用。我相信只要我得到 any 无限摘要:JsFiddle 就会 always 无限摘要。似乎有一些从以前的运行中挥之不去的状态。所以,我建议 你在 JsFiddle 中看到的任何失败,你再试一次 在新的 JsFiddle .
至于为什么你的 $$hashKey
技巧会导致无限摘要 — 我认为 Angular 不期望 $$hashKey
是一个函数。因此,它可能没有调用您的函数,而是对分配给 $$hashKey
.
的函数进行了 参考比较
由于您每次调用 getList()
时都将比较器的 新实例 分配给 $$hashKey
:引用可能 永远不会 在后续的摘要中是相等的,所以它会一直尝试摘要。
编辑:更新 Whosebug 嵌入和 JsFiddle 以使用 HTTPS CDN(以避免与混合内容安全冲突)。
只要看表达式getPersons()
returns一个new数组,即使元素相同,$digest
循环,这用===
比较,停不下来;不管 track by
表达式,在 ngRepeat
.
的 change detection 之后,对 rendering 节点起作用
(function() {
angular
.module('app', [])
.controller('AppController', AppController)
function AppController($interval) {
// you may have more performant options here
const hashFn = angular.toJson.bind(angular)
// your mapping logic for presentation
const mapFn = (e) => ({
enhancedName: e.name + e.id
})
// initialization of data
let sourceArray = [{
id: 1,
name: 'Dave'
}, {
id: 2,
name: 'Steve'
}]
// initialization of "cache"
let personList = sourceArray.map(mapFn),
lastListHash = hashFn(sourceArray)
Object.defineProperty(this, 'personList', {
get: function() {
const hash = hashFn(sourceArray)
if (hash !== lastListHash) {
personList = sourceArray.map(mapFn)
lastListHash = hash
}
// you need to return **the same** array
// if the source has not been updated
// to make `$digest` cycle happy
return personList
}
})
// test of changes
$interval(() => sourceArray.push({
id: Date.now(),
name: 'a'
}), 1000)
}
})()
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="AppController as ctrl">
There are {{ctrl.personList.length}} persons.
<ul>
<li ng-repeat="person in ctrl.personList track by $index">
[{{$index + 1}}] {{ person.enhancedName }}
</li>
</ul>
</div>
</div>
显然我还不了解 ng-repeat
、$$hashKeys
和 track by
背后的机制。
我目前在我的项目中使用 AngularJS 1.6。
问题:
我得到了一组复杂的对象,我想用它来在我的视图中呈现一个列表。但要获得所需的结果,我需要先修改(或 map/enhance/change)这些对象:
const sourceArray = [{id: 1, name: 'Dave'}, {id:2, name: Steve}]
const persons = sourceArray.map((e) => ({enhancedName: e.name + e.id}))
//Thus the content of persons is:
//[{enhancedName: 'Dave_1'}, {enhancedName: 'Steve_2'}]
将它绑定到视图应该像这样工作:
<div ng-repeat="person in ctrl.getPersons()">
{{person.enhancedName}}
</div>
然而,这显然会进入 $digest()
循环,因为每次调用时都会 .map
returns 新的对象实例。由于我通过一个函数将它绑定到 ng-repeat,它在每个 $digest
中都会重新评估,模型不稳定并且 Angular 不断重新运行 $digest
-cycles 因为这些对象被标记为 $dirty
.
为什么我很困惑
现在这不是一个新问题,有几种解决方案:
在an Angular-Issue from 2012 Igor Minar himself suggested to set the $$hashKey-Property manually to tell angular that the generated objects are the same. This is his working fiddle, but since even this very trivial example still ran into a $digest
-loop when i used it in my project, i tried upgrading the Angular-Version in the fiddle. For some reason it crashes.
好吧...因为 Angular 1.3 我们有 track by
应该基本上解决这个确切的问题。但是
<div ng-repeat="person in ctrl.getPersons() track by $index">
和
<div ng-repeat="person in ctrl.getPersons() track by person.enhancedName">
因 $digest
循环而崩溃。我的印象是 track by
语句应该让 angular 相信它适用于相同的对象,但显然情况并非如此,因为它只是不断检查它们的变化。老实说,我不知道如何正确调试这个问题的原因。
问题:
是否可以使用过滤/修改后的数组作为 ng-repeat 的数据源?
我不想将修改后的数组存储在我的控制器上,因为我需要不断更新它的数据,然后必须在控制器中手动维护和刷新它,而不是依赖数据绑定。
您提供的“it crashes”fiddle 没有为我生成无限摘要。事实上:它甚至没有成功 bootstrap Angular 应用程序(看起来 bootstrapping 在最新的 Angular 中无法以这种方式完成)。
我rewrote it使用我理解的Angularbootstrap机制。它重现了崩溃,就像你说的那样。
我找到了成功的方法track by stringified JSON。
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<script>
angular.module('myApp',[])
.controller('Ctrl', ['$scope', function($scope) {
angular.extend($scope, {
stringify: function(x) { return JSON.stringify(x) },
getList: function() {
return [
{name:'John', age:25},
{name:'Mary', age:28}
];
}
});
}]);
</script>
<div ng-app="myApp">
<div ng-controller="Ctrl">
I have {{getList().length}} friends. They are:
<ul>
<li ng-repeat="friend in getList() track by stringify(friend)">
[{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
</li>
</ul>
</div>
</div>
即我们提供跟踪功能,stringify()
。也可能有一个内置的 Angular。
track by $index
也有效——与您的发现相反。我认为 JsFiddle 稍微破坏了实验*
*以下为轶事。我相信我遇到了 JsFiddle 本身 的一些问题。例如:在我分叉 Fiddle 并在新的浏览上下文中再次尝试相同的代码之前,我的 track by stringify()
示例不起作用。我相信只要我得到 any 无限摘要:JsFiddle 就会 always 无限摘要。似乎有一些从以前的运行中挥之不去的状态。所以,我建议 你在 JsFiddle 中看到的任何失败,你再试一次 在新的 JsFiddle .
至于为什么你的 $$hashKey
技巧会导致无限摘要 — 我认为 Angular 不期望 $$hashKey
是一个函数。因此,它可能没有调用您的函数,而是对分配给 $$hashKey
.
由于您每次调用 getList()
时都将比较器的 新实例 分配给 $$hashKey
:引用可能 永远不会 在后续的摘要中是相等的,所以它会一直尝试摘要。
编辑:更新 Whosebug 嵌入和 JsFiddle 以使用 HTTPS CDN(以避免与混合内容安全冲突)。
只要看表达式getPersons()
returns一个new数组,即使元素相同,$digest
循环,这用===
比较,停不下来;不管 track by
表达式,在 ngRepeat
.
(function() {
angular
.module('app', [])
.controller('AppController', AppController)
function AppController($interval) {
// you may have more performant options here
const hashFn = angular.toJson.bind(angular)
// your mapping logic for presentation
const mapFn = (e) => ({
enhancedName: e.name + e.id
})
// initialization of data
let sourceArray = [{
id: 1,
name: 'Dave'
}, {
id: 2,
name: 'Steve'
}]
// initialization of "cache"
let personList = sourceArray.map(mapFn),
lastListHash = hashFn(sourceArray)
Object.defineProperty(this, 'personList', {
get: function() {
const hash = hashFn(sourceArray)
if (hash !== lastListHash) {
personList = sourceArray.map(mapFn)
lastListHash = hash
}
// you need to return **the same** array
// if the source has not been updated
// to make `$digest` cycle happy
return personList
}
})
// test of changes
$interval(() => sourceArray.push({
id: Date.now(),
name: 'a'
}), 1000)
}
})()
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="AppController as ctrl">
There are {{ctrl.personList.length}} persons.
<ul>
<li ng-repeat="person in ctrl.personList track by $index">
[{{$index + 1}}] {{ person.enhancedName }}
</li>
</ul>
</div>
</div>