ng-repeat中的函数,按原因跟踪无限$digest循环



显然我还不了解ng-repeat$$hashKeystrack by背后的机制。

我目前正在我的项目中使用 AngularJS 1.6。

问题:

我得到了一个复杂对象的数组,我想用它来在我的视图中呈现一个列表。但是要获得所需的结果,我需要先修改(或映射/增强/更改)这些对象:

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都会返回新的对象实例。由于我通过函数将其绑定到 ng-repeat,因此它在每个$digest都会被重新评估,模型不会稳定,并且 Angular 会不断重新运行$digest循环,因为这些对象被标记为$dirty

为什么我很困惑

现在这不是一个新问题,有几种解决方案:

在2012年的Angular-Issue中,Igor Minar本人建议手动设置$$hashKey-Property,以告诉angular生成的对象是相同的。这是他的工作小提琴,但由于当我在项目中使用它时,即使是这个非常微不足道的例子仍然会遇到$digest循环,所以我尝试在小提琴中升级 Angular 版本。由于某种原因,它崩溃了。

好。。。从 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 的数据源?

我不想将修改后的数组存储在控制器上,因为我需要不断更新其数据,然后必须在控制器中手动维护和刷新它,而不是依赖数据绑定。

您提供的"它崩溃"小提琴并没有为我产生无限的摘要。事实上:它甚至没有成功引导 Angular 应用程序(看起来在最新的 Angular 中无法以这种方式引导)。

我重写了它以使用我理解的 Angular 引导机制。就像你说的那样,它再现了崩溃。

我找到了一种方法,可以通过字符串化的 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本身上遇到了一些问题。例如:我的track by stringify()示例不起作用,直到我分叉了小提琴并在新的浏览上下文中再次尝试相同的代码。我相信,只要我得到任何无限的消化:JsFiddle总是无限的消化。似乎有一些状态性在之前的运行中挥之不去。所以,我建议你在 JsFiddle 中看到的任何内容都失败了,你在新的 JsFiddle 中再试一次。

至于为什么你的$$hashKey技巧导致了无限的摘要——我认为 Angular 并不期望$$hashKey成为一个函数。因此,它可能不是调用您的函数,而是对分配给$$hashKey的函数进行了参考比较

由于每次调用getList()时都分配给$$hashKey比较器的新实例:在后续摘要中,引用永远不会相等,因此它将永远尝试摘要。

编辑:更新了StackOverflow嵌入和JsFiddle以使用HTTPS CDN(以避免与混合内容安全性发生冲突)。

只要监视的表达式getPersons()返回一个新数组,即使具有相同的元素,使用===比较的$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>

最新更新