AngularJs error: "10 $digest() iterations reached. Aborting!" - angularjs

jsFiddle : http://jsfiddle.net/ADukg/4766/
I've written some basic angular code which actually works properly, and I get the desired result and ouput. However, I get some errors in my console which keep looping and eventually crash the browser. I've read some similar problems here, but can't seem to get a solution to work with my code.
Can anyone figure out what is going wrong and how to fix?
Error: 10 $digest() iterations reached. Aborting!

From your function, you must return a stable object (or near stable). Because you var new objects in the getBreakdown function, angular thinks they're new and puts them in the scope with new hashkeys.
Angular then runs this $digest again, to check to make sure nothing has changed... but it sees new objects and assumes that the model is not stabalized. It runs it again... and gets new objects again... and again... and again.
The moral of the story is you shouldn't create new model inside a function assigned to scope.
If you don't need live-binding, just transform this var into a new $scope var just once, don't bind to the function. If you need live binding, I think the solution for you is to use a filter.
btw... add a console.log(breakdown) right before your function returns, inspect each object inside the array and you'll see it outputs 10 times, each $$hashkey value is different. Angular uses the $$hashkey to keep track of objects. I think it'll be easier to understand how you're confusing it.
The example I use to teach people this concept is a function that returns a random number. If you try to bind to that function... angular will digest it 10 times each time getting a new random number and never stablizing. Angular won't know it's done digesting... ever. So thats why they limit it to 10.

Related

AngularJS getting value from function in ng-if and using that value in element

My question lies in the following code. Though the code is not working now. I wan't to rewrite it in proper way so that it works.
<span ng-if="community = vm.getCommunity(invite.community_id)!=null" grv-icon-comm-type="vm.communityViewHelper.getTypeIcon(community)" ng-class="vm.communityViewHelper.getColorClass(community)"></span>
In the above code vm.getCommunity(invite.community_id) returns either null or community object. If community object is returned then I wish to call two more function in the same element where I wish to use the recently receivedcommunity value on those two function.
It's just killing my time. Please help.
Alternatively, you could use a watcher on "invite.community_id" to set community inside a controller function. Could look a bit cleaner depending on the rest of the code.
This function could even set all three values if you like.

How to pass a method to a directive and invoke it to pass data to a nested directive?

I wrote a directive, list, that displays a list of items.
Then I wanted to reuse this component in another directive, wrapped-list that would modify the list (add some items, change some values), and add some cosmetic stuff around it.
The list expects a list of items as parameter, then, wrapped-list expects the same list, plus the method that will change the list values and content.
The issue occurs when I try to invoke the method from wrapped-list in order to pass the data to list.
See fiddle here.
I tried many crazy combinations, and could not make it work.
items="listFactory({items:items})"
items="listFactory(items)"
items="{{listFactory(items)}}"
...
No luck so far.
Any idea?
Note: I could make it work with items="$parent.listBuilder(items)", but I don't want to depend on the parent's scope.
Also, I know this will generate the $digest() iteration reached error, which is not part of this question!
As Ohjay44 commented it, the error is that listFactory should be referenced as list-factory in the HTML side.

Angular avoid function expression from running multiply times

I have multiply functions like count() getTotal(). All those calculation functions have to run if something changes my cart/object.
Currently I'm using them in the view like {{count()}}. So all those functions are running multiply times.
I think those functions are called multiply times, because of the dirty checking. Is this correct?
Now I read about the $watch function and that you should try to avoid it, because of performance issues. Anyway I wanted to test it, and $watchedmy cart. I found out it only logs a single time so wouldn't it be faster to $watch my cart and call all the calculations functions in there and then just bind to the result of the calculations?
I'm a bit confused know, cause i thought it would have the same behaviour. Because the $watch will also go through the digest loop.
I know there are already a lot of similiar questions, but having somebody explaining it directly for my case in a not so hard english, would make it a lot easier.
Example:
What has better performance? and why? Does both variants go through the digest cycle? Why then Variant 2 logs 10 times and Variant 1 only 1 time?
Variant 1
// bind these to scope and show like {{totalGross}}
cartService.totalGross = null;
cartService.totalNet = null;
cartService.totalItems = null;
// watch the cart here and update all variables
$rootScope.$watch(function () {
return cartService.cart;
}, function(){
cartService.totalGross = cartService.getCartTotalGross();
cartService.totalNet = cartService.getCartTotalNet();
cartService.totalItems = cartService.getTotalItems();
}, true);
Variant 2
// bind these to scope and show like {{getCartTotalGross()}}
cartService.getCartTotalGross();
cartService.getCartTotalNet();
cartService.getTotalItems();
Trying to answer my own question, altough i'm not shure if it's correct.
Variant 1 you have 1 more watcher, but we just watch values instead of functions and the 1 more because we watch the cart manually with $watch. But the watchers are less heavy to compute.
Variant 2 we are watching the value that functions are returning, so the functions have to calculate the same value multiply times, witch is more heavy. In each digest Cycle the UI get updated
Is this correct? So Variant 1 is better for performance?
It's generally considered bad practice to use $watch not (only) because it has performance issues, but because there are, most of the time, better alternatives (both clearer, and more efficient).
For example, instead of watching a model value that changes when the user enters something in an input, in order to compute something that depends on this value, you can use ng-change on the input and call the computation from there. This is faster, and the intent is clearer.
Now, to answer your question:
Every time an event is handled by angular, the event handler can modify anything in the scope. Angular has no way to know what has been modified. So it has to call, for example, getTotalItems() because an event handler might have changed something that makes the returned value of getTotalItems() change.
Additionally, changing any value that is watched causes a watcher function to be executed, and this watcher function can, in turn, change other values, that can be watched by other watchers, etc. etc. So angular needs to evaluate all the watched expressions in a loop, until it can be sure that the last evaluation leads to the same results as the previous one. That's what is known as the digest loop.
So, in short, having {{ getTotalItems() }} in the view is not a big deal if that function just returns something simple, like the length of an array, or the sum of a few values. But if it computes the meaning of life, it's a really bad idea, and you should instead compute the meaning of life only when it needs to be computed, and store the result in a variable that is displayed in the view.
You can do that with a watcher, but that should be the last resort. As I said already, there are often better alternatives.

How do I read the message: [$rootScope:infdig] 10 $digest() iterations reached. Aborting! Watchers fired in the last 5 iterations

There are a number of posts here talking about how you fix this problem, but I really just want to know how I read that big crazy list of information that comes along with the message. I've been using angular every day for over a year, but always been too intimidated by that error message to really dig into it and find out how to use it to my advantage.
Now I really need to read the error message, though, because we don't get the error locally. It just happens from time to time in production (possibly related to user using an old browser). So I have the error message from our error logs, but I can't reproduce it or debug it by making changes in the code.
I'm not sure I understand this completely, but here's what I found out on my own.
After the colon there are two brackets. (...'atchers fired in the last 5 iterations: [[{"msg') The first bracket is the beginning of a json block. Copy from the first bracket to the end of the error and find a way to pretty-print that json. (Use your favorite code editor or an online json formatter)
Now you have a pretty-printed array with 5 entries in it. Each entry represents an iteration in the digest cycle, i.e. one pass through all of the active watchers in your app, looking for changes. Angular will repeat iterations until it does one in which no watcher has a changed value, or until it hits 10 iterations, at which point it will error. That's what happened in this case.
There were 10 iterations before the error, and 5 are included in the error message. Presumably that means there are five more iterations that happened earlier than what is included in the error message. The first entry in the error message is the 6th iteration, and the last entry in the message is the 10th iteration.
The entry for each iteration is also an array. In this case it is an array of objects, and each object represents a watcher whose value changed during this iteration. Each object will give you the text or the function that defines the watcher, the old value for the watcher before this iteration and the new value after this iteration.
Read it from top to bottom like a story, adding commentary based on what you know about your app. In my case, I was able to see how the changes in each iteration caused new watchers to be created, requiring yet another iteration. "In the 6th iteration, this watcher changed, causing this new stuff to be rendered on the page, creating new watchers which were assigned values in the 7th iteration, and then ..." There was no infinite loop or anything. In fact, if angular had been willing to do just 1 or two more iterations, it would have finished.
Hope this is helpful to someone else.

AngularJS - ng-grid sliding window - table not updating from array change

I'm trying to use ng-grid with a sliding window of 100 records. The data is coming in realtime via signalR and every message trigger the following method:
onNewTrades(records) {
console.log("onNewRecord", records);
if (connectionStopped) return;
for (var i = 0; i < records.length; i++) {
if ($scope.recordsData.length > maxRecordsInTable)
$scope.recordsData.pop();
$scope.recordsData.unshift({
t: new Date(records[i][0]),
p: records[i][1],
a: records[i][2]
});
}
}
I have a threshold of 100 maxRecordsInTable before I start popping items off the end (before adding the new message to the front)
However, when it reaches my threshold the table simple stops updating. Strangely though, if I set a breakpoint on unshift(), the table does update with every "continue".
I suspect it's some kind of angular timing issue? I tried using $timeout()
Or may when I pop() and unshift() at the same time it doesn't pick up a change in the array? I tried using $apply() (error already in digest cycle)
There are a few things that could be happening here.
First of all, if onNewTrade is using an external, non-angular, library making xhr requests outside of angular's framework (i.e. not using $http or $resource), you have to call $scope.$apply(function(){ }) around the code you want the scope's digest to know about. That part's not clear from what you've provided. edit: Read more about when to use $scope.$apply
Second, angular's digest phase does a minimum of two passes (first to make changes, second to make sure there are no more changes). It does at most 10 passes by default. If angular evaluates the scope 10 times and it is not consistent, it gives up. see documentation. It does this because you can have multiple watch functions where one watch affects the scope higher in the hierarchy, which makes changes and affects the same watch.. basically causing an evented infinite loop. Do you see a console error about '10 $digest iterations, aborting!' or something similar?
There are a couple of other questionable things:
is onNewRecord asynchronous? If so, I would doubt connectionStopped is being done correctly. You could be returning early. Because you say a breakpoint shows values on unshift, its probably not the cause of this issue (and most likely missing $scope.$apply is the problem), but I'd rethink this code.
Your function is onNewTrade(records), but you log onNewRecord(record). If you have nested variables here, make sure you haven't excluded code that may contain typos (e.g. record instead of records). You might be working on an unexpected object.

Resources