Angular.js: Infinite $digest() loop when using a function in ng:repeat

Created on 5 Jan 2012  路  13Comments  路  Source: angular/angular.js

To reproduce, open the following link and look at the console:
http://jsfiddle.net/LE6Ay/

You should see:
Uncaught Error: 100 $digest() iterations reached. Aborting!
...

Most helpful comment

We worked around it by assigning the result of the controller's method to a property, and doing ng:repeat against it.

I find it a little strange that ng:repeat evaluates the collection portion of the expression more than once.
I expected the collection to be evaluated once at the beginning, and I expected the result to be used in all the iterations of the ng:repeat.

All 13 comments

Your getter is not idempotent and changes the model (by generating a new array each time it is called). This is forcing angular to keep on calling it in hope that the model will eventually stabilize, but it never does so angular gives up and throws an exception.

The values the getter return are equal but not identical and that's the problem.

Do you need to generate the data model on the fly?

We worked around it by assigning the result of the controller's method to a property, and doing ng:repeat against it.

I find it a little strange that ng:repeat evaluates the collection portion of the expression more than once.
I expected the collection to be evaluated once at the beginning, and I expected the result to be used in all the iterations of the ng:repeat.

That's exactly what is happening. We evaluate the right hand side just once per digest loop, but one apply/digest call can and during the initial rendering typically does consist of multiple digest loops which are necessary to verify that the model is stable (this is why you don't need $eval() all over the place any more).

There is another solution however. since javascript doesn't have a weakmap (yet), we internally use hash keys in order to be able to look up objects quickly. you could use this mechanism to tell angular that even though the getter returns array with objects that have different identity, they are equivalent.

See: http://jsfiddle.net/LE6Ay/1/

Having said that the proper solution is to use stable model, which means assigning the array to the scope/controller instead of using a getter.

Got it. thanks!

Having to assign the array to the scope/controller means that UI details get leaked to the scope/controller. For example, if I have a dataset that I want to present in a 4 column grid layout from left to right then top to bottom, I will have to put the list into a grid of four columns in the controller itself. This is a very common use case with Bootstrap.

The behavior is also very confusing because it only happens when iterating through an object. A string array does not cause this behavior at all.

This comes from "a" === "a" being true.

I believe I understand the underlying cause, but I don't think it's reasonable that the following code would fail and throw with an infinite loop, it's pretty user-hostile:

<div ng-app>
    <div ng-repeat="person in [{'name':'Alice'},{'name':'Bob'}]">
        {{person.name}}
    </div>
</div>

i have the same proble as tdierks

<li ng-repeat="opt in [1,2,3]" class="custom-select" >
   <a href="#">{{opt}}</a>
</li>

Indeed, for the trivial case that @tdierks shows, I think it's not so silly to let the model decide what it sees as 'stable'.

I worked around it by checking for equal-ness with JSON.stringify on the return value:

var previousReturn;
$scope.unstableFunction = function(){
     var ret;
     .... calculate calculate
     if (JSON.stringify(ret) == JSON.stringify(previousRet)) {
        ret = previousRet;
     }
     previousRet = ret;
     return ret;
};

In fact, this pattern can be generalized:

var idempotentialize = function(f){
    var previous;
    var f_idempotent = function(){
       var ret = f();
       if (JSON.stringify(ret)==JSON.stringify(previous))
          ret = previous;
       previous = ret;
       return ret;
    }
    return f_idempotent;
};

Usage:
$scope.unstableFunction = idempotentialize(function(){
... calculate calculate
});

Based on this information, is it fair to assume that AngularJS doesn't play nice with ES6 get/set on a class?

No (unless get has side effects that affect the model) :smiley:

This link realy Helps me and i fixed using lodash memoize function https://railsguides.net/fix-angular-digest-iteration-errors-with-memoization/

Was this page helpful?
0 / 5 - 0 ratings