Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

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

Closed
idosela opened this issue Jan 5, 2012 · 13 comments
Closed

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

idosela opened this issue Jan 5, 2012 · 13 comments

Comments

@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
Copy link
Contributor

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
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
Copy link
Contributor

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
Copy link
Author

idosela commented Jan 10, 2012

Got it. thanks!

@idosela idosela closed this as completed Jan 10, 2012
@steve-taylor
Copy link

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
Copy link

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
Copy link

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

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

@MaestroJurko
Copy link

i have the same proble as tdierks

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

@xtofl
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
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
Copy link
Member

gkalpak commented Mar 5, 2018

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

@veerabiz1
Copy link

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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests