错误:达到10次$digest()迭代。中止!带有动态sortby谓词。

145
我有以下代码,它会重复显示用户的姓名和分数:
<div ng-controller="AngularCtrl" ng-app>
  <div ng-repeat="user in users | orderBy:predicate:reverse | limitTo:10">
    <div ng-init="user.score=user.id+1">
        {{user.name}} and {{user.score}}
    </div>
  </div>
</div>

对应的Angular控制器如下。

function AngularCtrl($scope) {
    $scope.predicate = 'score';
    $scope.reverse = true;
    $scope.users = [{id: 1, name: 'John'}, {id: 2, name: 'Ken'}, {id: 3, name: 'smith'}, {id: 4, name: 'kevin'}, {id: 5, name: 'bob'}, {id: 6, name: 'Dev'}, {id: 7, name: 'Joe'}, {id: 8, name: 'kevin'}, {id: 9, name: 'John'}, {id: 10, name: 'Ken'}, {id: 11, name: 'John'}, {id: 1, name: 'John'}, {id: 2, name: 'Ken'}, {id: 3, name: 'smith'}, {id: 4, name: 'kevin'}, {id: 5, name: 'bob'}, {id: 6, name: 'Dev'}, {id: 7, name: 'Joe'}, {id: 8, name: 'kevin'}, {id: 9, name: 'John'}, {id: 10, name: 'Ken'}]
}

当我运行上面的代码时,我的控制台会出现“Error: 10 $digest() iterations reached. Aborting!”的错误。
我已经为此创建了 jsfiddle
排序谓词仅在ng-repeat中进行初始化,并且限制应用于对象数量。因此,我认为同时具有sortby和limitTo观察程序是错误的原因。
如果$scope.reverse为false(得分升序),则不会出现错误。
请问有人能帮我理解这里出了什么问题吗?非常感谢您的帮助。

如果你移除掉这个if语句,它还会报错吗? - Mathew Berg
感谢您的回复,马修!我错误地诊断了问题。问题似乎出在sortby和limitTo过滤器上。我已经在JSFiddle中更新了问题。非常感谢您的帮助。 - Chubby Boy
这是一个Angular的问题。你需要记忆化你的函数并让它们记住状态。在https://dev59.com/72Yq5IYBdhLWcg3wkRYo#38257418上查看我的答案。 - Julio Marins
13个回答

119

请查看此jsFiddle。(该代码基本与您发布的相同,但我使用一个元素来绑定滚动事件而不是窗口)。

据我所见,您发布的代码没有问题。您提到的错误通常发生在创建属性更改循环时。例如,当您监听某个属性的更改并在侦听器上更改该属性的值时:

$scope.$watch('users', function(value) {
  $scope.users = [];
});

这将导致错误消息:

未捕获的错误:达到了10次$digest()迭代。 中止!
最近5次迭代中已触发监视器:...

确保您的代码没有此类情况。

更新:

这是您的问题:

<div ng-init="user.score=user.id+1"> 

在渲染期间或其他情况下,不应更改对象/模型,否则将强制进行新的渲染(并因此导致循环,从而引发“错误:达到10个$ digest()迭代。中止!”)。

如果要更新模型,请在控制器或指令上执行此操作,而不是在视图上执行。 angularjs文档建议不要使用 ng-init,以避免这种情况的发生:

在模板中使用ngInit指令(仅适用于玩具/示例应用程序,不建议在真实应用程序中使用)

这里有一个带有可工作示例的jsFiddle


1
请注意,您可以使用element.bind()代替angular.element(w).bind() - Mark Rajcok
非常感谢bmleite和Mark。我错误地诊断了问题。请参见我上面的评论。我已经在JSFiddle中更新了问题。非常感谢您的帮助。 - Chubby Boy
非常感谢@bmleite!只是想更好地理解。但是,如果$scope.reverse=false(按分数升序)不会导致错误,为什么会这样? - Chubby Boy
对于升序和降序,您应该分别使用前缀“+”和“-”(即“+score”和“-score”)。有关此内容的更多信息,请参见这里 - bmleite
1
这个答案解决了我的问题。解释得很好,它描述了问题:当您尝试同时在循环中修改属性时,$digest() 迭代错误就会出现。因此,每次属性更改都会触发视图(UI)的更新,而 Angular 最终会在 10 次循环后达到最大值。jsFiddle 示例将属性更改放在控制器中。在视图中更改属性会导致出现此错误。 - mbokil

10

对我而言,这个错误的原因是...

ng-if="{{myTrustSrc(chat.src)}}"

在我的模板中

这会导致我的控制器中的myTrustSrc函数被无限循环调用。如果我从这行代码中删除ng-if,问题就会解决。

<iframe ng-if="chat.src" id='chat' name='chat' class='chat' ng-src="{{myTrustSrc(chat.src)}}"></iframe>

当未使用ng-if时,该函数仅被调用几次。我仍然想知道为什么会在使用ng-src时被调用多次?

这是控制器中的函数。

$scope.myTrustSrc = function(src) {
    return $sce.trustAsResourceUrl(src);
}

我仍然想知道为什么在ng-src中该函数被调用多次?- 因为Angular会尝试创建HTML几次,直到获得两个相同的结果。这就是错误10 $digest()迭代达到的含义,它尝试了10次,但从未获得两个相同的结果。 - bresleveloper
@bresleveloper,你能提供一下这个信息的链接吗?谢谢。 - Willa
@Willa 看这里 https://docs.angularjs.org/api/ng/type/$rootScope.Scope CTRF+F 搜索 "The rerun iteration limit is 10 to prevent an infinite loop deadlock" - bresleveloper

7

对于我来说,问题出在将函数结果作为双向绑定输入 '=' 传递给指令,而该指令每次都会创建一个新对象。

因此,我的情况类似于以下示例:

<my-dir>
   <div ng-repeat="entity in entities">
      <some-other-dir entity="myDirCtrl.convertToSomeOtherObject(entity)"></some-other-dir>
   </div>
</my-dir>

my-dir的控制器方法为

this.convertToSomeOtherObject(entity) {
   var obj = new Object();
   obj.id = entity.Id;
   obj.value = entity.Value;
   [..]
   return obj;
}

当我制作时,它表示
this.convertToSomeOtherObject(entity) {
   var converted = entity;
   converted.id = entity.Id;
   converted.value = entity.Value;
   [...]
   return converted;
}

问题已经解决!

希望这能对某些人有所帮助 :)


6

我有另一个导致此问题的例子。希望对将来有所帮助。我正在使用 AngularJS 1.4.1。

我有多个自定义指令调用的标记:

<div ng-controller="SomeController">
    <myDirective data="myData.Where('IsOpen',true)"></myDirective>
    <myDirective data="myData.Where('IsOpen',false)"></myDirective>
</div>

myData 是一个数组,Where() 是一个扩展方法,它遍历这个数组并返回一个新的数组,其中包含原始数组中 IsOpen 属性与第二个参数中的布尔值相匹配的任何项。

在控制器中,我像这样设置 $scope.data

DataService.getData().then(function(results){
    $scope.data = results;
});

在上面的标记中从指令调用 Where() 扩展方法是问题所在。为了解决这个问题,我将扩展方法的调用移到控制器中而不是标记中:

<div ng-controller="SomeController">
    <myDirective data="openData"></myDirective>
    <myDirective data="closedData"></myDirective>
</div>

新的控制器代码如下:

DataService.getData().then(function(results){
    $scope.openData = results.Where('IsOpen',true);
    $scope.closedData = results.Where('IsOpen',false);
});

这听起来就是我遇到的问题。一个简单的函数并没有更新任何东西,只是创建了一个本地数组,遍历了一个已经存在的$scope数组,并在本地数组中创建对象,最后返回。通过调用一次并将其存储在$scope成员中,它解决了问题。谢谢!但愿我知道直接使用该方法的问题所在。 - AgilePro

2
我来晚了,但我的问题出在angular 1.5.8中的ui-router存在缺陷。需要注意的是,此错误仅在首次运行应用程序时出现,之后不会再次出现。这篇来自Github的帖子解决了我的问题。基本上,这个错误涉及到$urlRouterProvider.otherwise("/home")。解决方案是像这样的变通方法:
$urlRouterProvider.otherwise(function($injector, $location) {
   var $state = $injector.get("$state");
   $state.go("your-state-for-home");
});

2

首先,忽略所有告诉你使用 $watch 的答案。Angular 已经有一个监听器了。我保证你只是在增加复杂度,仅仅是想着这个方向。

忽略所有告诉你使用 $timeout 的答案。你无法知道页面加载需要多长时间,因此这不是最好的解决方案。

你只需要知道页面渲染完成的时间。

<div ng-app='myApp'>
<div ng-controller="testctrl">
    <label>{{total}}</label>
    <table>
      <tr ng-repeat="item in items track by $index;" ng-init="end($index);">
        <td>{{item.number}}</td>
      </tr>
    </table>
</div>

var app = angular.module('myApp', ["testctrl"]);
var controllers = angular.module("testctrl", []);
 controllers.controller("testctrl", function($scope) {

  $scope.items = [{"number":"one"},{"number":"two"},{"number":"three"}];

  $scope.end = function(index){
  if(index == $scope.items.length -1
        && typeof $scope.endThis == 'undefined'){

            ///  DO STUFF HERE
      $scope.total = index + 1;
      $scop.endThis  = true;
 }
}
});

通过$index跟踪ng-repeat,当数组的长度等于索引时停止循环并执行您的逻辑。

jsfiddle


1

很奇怪...我遇到了完全相同的错误,但是出现在不同的事情上。当我创建控制器时,我传递了$location参数,像这样:

App.controller('MessageController', function ($scope, $http, $log, $location, $attrs, MessageFactory, SocialMessageFactory) {
   // controller code
});

当我们使用第三方库或纯JS来操作某些特定内容(例如window.location),下一个angular的digest就会报出bug

因此,我只需将$location从控制器创建参数中删除,就可以再次正常工作,没有这个错误。 或者如果您绝对需要使用angular的$location,您必须删除模板页面链接中每个单独的<a href="#">link</a>,而是写成href=""。对我有用。

希望有一天它能帮到您。


1
在 Angular 树控件的上下文中,我遇到了这个错误。在我的情况下,是树选项引起的问题。我从一个函数返回 treeOptions() 。它总是返回相同的对象。但是 Angular 神奇地认为它是一个新对象,然后会导致启动循环调用 digest 循环。解决方案是将 treeOptions 绑定到作用域,并只分配一次即可。

0

在我的升级从 angular 1.6 -> 1.7 后,当使用 $sce.trustAsResourceUrl() 作为从 ng-src 调用的函数的返回值时,发生了这种情况。你可以在这里看到这个问题被提到 here

在我的情况下,我不得不改变以下内容。

<source ng-src="{{trustSrc(someUrl)}}" type='video/mp4' />
trustSrc = function(url){
    return $sce.trustAsResourceUrl(url);
};

<source ng-src='{{trustedUrl}} type='video/mp4' />
trustedUrl = $sce.trustAsResourceUrl(someUrl);

0

我在使用AngularJS 1.3.9时遇到了完全相同的错误,当我在自定义排序过滤器中调用Array.reverse()时。

当我将其删除后,一切都很好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接