Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Infinite $digest() loop when using a function in ng:repeat #705

Closed
idosela opened this Issue · 10 comments

8 participants

@idosela

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!
...

@IgorMinar
Owner

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?

@idosela

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.

@IgorMinar
Owner

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.

@idosela

Got it. thanks!

@idosela idosela closed this
@steve-taylor

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.

@smatthews1999

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.

@vendethiel

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

@tdierks

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>
@mato75

i have the same proble as tdierks

<li ng-repeat="opt in [1,2,3]" class="custom-select" >
   <a href="#">{{opt}}</a>
</li>
@xtofl xtofl referenced this issue from a commit in xtofl/growsery
xtofl fix infinite $digest cycle
make 'allGroceries()' idempotent
angular/angular.js#705
d8139a4
@xtofl

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
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.