Definition and Usage. The ng-repeat directive repeats a set of HTML, a given number of times. The set of HTML will be repeated once per item in a collection. The collection must be an array or an object.
To avoid this problem, you can use "track by" with ng-repeat. In track by you have to use angular expression that will be used to uniquely identify the each items in collection or model. "track by" tells the angular js that how angular js will track the association between DOM and the model (i.e. collection).
Note: The $index variable is used to get the Index of the Row created by ng-repeat directive. Each row of the HTML Table consists of a Button which has been assigned ng-click directive. The $index variable is passed as parameter to the GetRowIndex function.
Directives that Create Scopes In most cases, directives and scopes interact but do not create new instances of scope. However, some directives, such as ng-controller and ng-repeat, create new child scopes and attach the child scope to the corresponding DOM element.
Short answer: do you really need such function or you can use property? http://jsfiddle.net/awnqm/1/
Long answer
For simplicity I will describe only your case - ngRepeat for array of objects. Also, I'll omit some details.
AngularJS uses dirty checking for detecting changes. When application is started it runs $digest
for $rootScope
. $digest
will do depth-first traversal for scope's hierarchy. All scopes have list of watches. Each watch has last value (initially initWatchVal
). For each scope for all watches $digest
runs it, gets current value (watch.get(scope)
) and compares it to watch.last
. If current value is not equal to watch.last
(always for first compare) $digest
sets dirty
to true
. When all scopes are processed if dirty == true
$digest
starts another depth-first traversal from $rootScope
. $digest
ends when dirty == false or number of traversals == 10. In the latter case, the error "10 $digest() iterations reached." will be logged.
Now about ngRepeat
. For each watch.get
call it stores objects from collection (returning value of getEntities
) with additional information in cache (HashQueueMap
by hashKey
). For every watch.get
call ngRepeat
tries to get object by its hashKey
from cache. If it does not exist in cache, ngRepeat
stores it in cache, creates new scope, puts object on it, creates DOM element, etc.
Now about hashKey
. Usually hashKey
is unique number generated by nextUid()
. But it can be function. hashKey
is stored in object after generating for future use.
Why your example generates error: function getEntities()
always returns array with new object. This object doesn't have hashKey
and doesn't exist in ngRepeat
cache. So ngRepeat
on each watch.get
generates new scope for it with new watch for {{entity.id}}
. This watch on first watch.get
has watch.last == initWatchVal
. So watch.get() != watch.last
. So $digest
starts new traverse. So ngRepeat
creates new scope with new watch. So ... after 10 traverses you get error.
How you can fix it
getEntities()
call.hashKey
method for them. See this topic for examples.Hope people who know AngularJS internals will correct me if I'm wrong in something.
Initialise the array outside of the repeat
<body ng-app>
<div ng-init="entities = getEntities()">
<div ng-repeat="entity in entities">
Hello {{entity.id}}!
</div>
</div>
</body>
This was reported here and got this response:
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.
You can see this behavior go away if you move the array outside the Main controller:
var array = [{id:'angularjs'}];
function Main($scope) {
$scope.getEntities = function(){return array;};
};
because now it is returning the same object each time. You may need to re-architect your model to use a property on the scope instead of a function:
We worked around it by assigning the result of the controller's method to a property, and doing ng:repeat against it.
Based on @przno comment
<body ng-app>
<div ng-repeat="item in t = angular.equals(t, getEntities()) ? t : getEntities()">
Hello {{item.id}}!
</div>
</body>
BTW second solution @Artem Andreev suggests is not working in Angular 1.1.4 and greater, while first one does not solve the problem. So, I'm afraid for now this is the less spiky solution without disadvantages in functionality
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With