New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
idosela opened this Issue Jan 5, 2012 · 12 comments

Comments

Projects
None yet
10 participants
@idosela
Copy link

idosela commented Jan 5, 2012

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

This comment has been minimized.

Copy link
Member

IgorMinar commented Jan 6, 2012

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

This comment has been minimized.

Copy link
Author

idosela commented Jan 6, 2012

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

This comment has been minimized.

Copy link
Member

IgorMinar commented Jan 6, 2012

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

This comment has been minimized.

Copy link
Author

idosela commented Jan 10, 2012

Got it. thanks!

@idosela idosela closed this Jan 10, 2012

@steve-taylor

This comment has been minimized.

Copy link

steve-taylor commented Apr 29, 2013

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

This comment has been minimized.

Copy link

smatthews1999 commented May 7, 2013

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 comment has been minimized.

Copy link

vendethiel commented May 7, 2013

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

@tdierks

This comment has been minimized.

Copy link

tdierks commented May 8, 2013

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

This comment has been minimized.

Copy link

puppeteer701 commented Aug 1, 2013

i have the same proble as tdierks

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

xtofl pushed a commit to xtofl/growsery that referenced this issue Mar 5, 2014

xtofl
fix infinite $digest cycle
make 'allGroceries()' idempotent
angular/angular.js#705
@xtofl

This comment has been minimized.

Copy link

xtofl commented Mar 5, 2014

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

@blowsie

This comment has been minimized.

Copy link

blowsie commented Mar 5, 2018

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

@gkalpak

This comment has been minimized.

Copy link
Member

gkalpak commented Mar 5, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment