Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Infinite Digest Loop in AngularJS filter

I have written this custom filter for AngularJS, but when it runs, I get the infinite digest loop error. Why does this occur and how can I correct this?

angular.module("app", []).
filter('department', function(filterFilter) {
  return function(items, args) {
    var productMatches;
    var output = [];
    var count = 0;

    if (args.selectedDepartment.Id !== undefined && args.option) {
      for (let i = 0; i < items.length; i++) {

        productMatches = items[i].products.filter(function(el) {
          return el.Order__r.Department__r.Id === args.selectedDepartment.Id;
        });

        if (productMatches.length !== 0) {
          output[count] = {};
          output[count].products = productMatches;
          output[count].firstProduct = items[i].firstProduct;
          count++;
        }

      }
    }
    return output;
  };
}).

This is the relevant HTML:

<tr class='destination' ng-repeat-start='pickupAccount in pickupAccounts | department : {"selectedDepartment": selectedDepartment, "option": displayExclusive }'>
  <!-- td here -->
</tr>

displayExclusive is boolean.

like image 277
shmuli Avatar asked Aug 23 '17 04:08

shmuli


3 Answers

I have written this custom filter for AngularJS, but when it runs, I get the infinite digest loop error.

Keep in mind that filter should return array of the same object structure. When we activate filter, it fires digest cycle that will run over our filter again. If something changed in output list - fires new digest cycle and so on. after 10 attempts it will throw us Infinite Digest Loop Exception


Testing

This empty filter will works (100%). Actually we do nothing here but return the same object that filter receives.

filter('department', function(filterFilter) {
  return function(items, args) {

    var output = items;

    return output;
  };
})

Now the main idea is: write some condition to push to output objects from input list a.e. items based on some if statement, a.e.

var output = [];

if (args.selectedDepartment.Id !== undefined && args.option) {
   angular.forEach(items, function(item) {
       if(<SOME CONDITION>) {
          output.push(item);
        }            
    });
}

By this way it will work too.

our case:

we have this logic:

productMatches = items[i].products.filter(function(el) {
      return el.Order__r.Department__r.Id === args.selectedDepartment.Id;
    });

    if (productMatches.length !== 0) {
      output[count] = {};
      output[count].products = productMatches;
      output[count].firstProduct = items[i].firstProduct;
      count++;
    }

Here we completely modified object that has been stored in output. So next digest cycle our items will change again and again.


Conclusion

The main purpose of filter is to filter list and not modify list object content.

Above mentioned logic you wrote is related to data manipulation and not filter. The department filter returns the same length of items.

To achieve your goal, you can use lodash map or underscorejs map for example.

like image 148
Maxim Shoustin Avatar answered Oct 02 '22 14:10

Maxim Shoustin


This happens when you manipulate the returned array in a way that it does not match the original array. See for example:

.filter("department", function() {
    return function(items, args) {
        var output = [];

        for (var i = 0; i < items.length; i++) {
            output[i] = {};
            output[i] = items[i]; // if you don't do this, the next filter will fail
            output[i].product = items[i];
        }
        return output;
    }
}

You can see it happening in the following simplified jsfiddle: https://jsfiddle.net/u873kevp/1/

If the returned array does have the same 'structure' as the input array, it will cause these errors.

It should work in your case by just assigning the original item to the returned item:

if (productMatches.length !== 0) {
    output[count] = items[i]; // do this
    output[count].products = productMatches;
    output[count].firstProduct = items[i].firstProduct;
    count++;
}
like image 39
devqon Avatar answered Oct 02 '22 15:10

devqon


output[count] = {};

Above line is the main problem. You create a new instance, and ng-repeat will detect that the model is constantly changed indefinitely. (while you think that nothing is changed from the UI perspective)

To avoid the issue, basically you need to ensure that each element in the model remains the 'same', i.e.

firstCallOutput[0] == secondCallOutput[0]
&& firstCallOutput[1] == secondCallOutput[1]
&& firstCallOutput[2] == secondCallOutput[2]
...

This equality should be maintained as long as you don't change the model, thus ng-repeat will not 'wrongly' think that the model has been changed.

Please note that two new instances is not equal, i.e. {} != {}

like image 23
Thariq Nugrohotomo Avatar answered Oct 02 '22 14:10

Thariq Nugrohotomo