Fixing Angular infinite digest loops with Underscore's memoize


Fixing Angular infinite digest loops with Underscore's memoize

2017/01/21

Tags: angular digest infinite loop underscore memoize

A quote I created for this scenario:

When stranded in a place without a mechanic, you will learn to repair the bike on your own.

I was getting errors like this:

Error: $rootScope:infdig
Infinite $digest Loop
10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"fn ....."

Then, I learned about Angular’s $digest. Quoting official docs:

Angular enters the $digest loop. The loop is made up of two smaller loops which process $evalAsync queue and the $watch list. The $digest loop keeps iterating until the model stabilizes, which means that the $evalAsync queue is empty and the $watch list does not detect any changes.

Let me explain this with my scenario and how it related to $digest loop. Consider this snippet:

<button class="list-group-item" ng-repeat="d in data|naturalSort" ui-sref=".data-detail(d)">

naturalSort would sort data using its code such as G1, G2, OP3.2, etc. If I were to use the normal sorting functions, I would get G1, G10, G2, … which is not what is desired.

This sorting filter was working great until two data came up with the same code. Now, this algorithm was doing an unstable sort i.e. the ordering of items with same code was not being preserved in the ordered data. This time, I got this infinite digest loop. So, what happened?

Angular’s $digest loop would rerun each assignments such as data|naturalSort until the value returned is same multiple times. Say, I have these [{code:G5, id:3}, {code:G5, id:4}] in the original data. The naturalSort would sometimes give [{code:G5, id:3}, {code:G5, id:4}] or [{code:G5, id:4}, {code:G5, id:3}] sometimes. This is the case of unstable sorting. Recalling that $digest loop checks for new output values until it stabalizes i.e. the output is same everytime, it never happened in this case. Hence, the code ran to infinite digest loop.

Searching for similar cases and some readings, I found _.memoize(). It’s an underscore.js’s function that caches output for each unique input and returns output directly if the input matches to the one in the cache. Using it was just the matter of wrapping that existing filter with _.memoize(.....). Problem solved :+)))+0(()))+)))

Not only it solved my problem, it also slightly improved the performance because that expensive filter was not running multiple times.

In the end, I would try to make that sorting function stable by sorting on ID fields if codes are same. But for now, I am just too happy.

A bit of background

I started using Angular.JS on a project while working for Ministry of Health, Nepal. We were assigned to develop a platform used for monitoring and performance evaluation. The complexity of this system was that we had to generate a complex set of tables with rowspans and colspans in almost all cells and those cells will be editable. What a complexity for a newbie !!!

I thought it was a perfect opportunity to try out Angular because I had some idea on what it was and its template system was a perfect match for our scenario. After all, who don’t want HTML with if (ng-if) powers. Just as we expected, Angular worked out well for us. We were loving it. Following is a sample table generated using Angular.JS:

Angular.JS generated complex table