Infinite Digest Loop in AngularJS filter
Asked Answered
S

3

18

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.

Stall answered 23/8, 2017 at 4:16 Comment(3)
Can you post your html code also?Ultramicrochemistry
Do you have an ng-repeat-end tag?Luis
Yes @Phil, I have ng-repeat-end.Stall
R
10

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.

Rici answered 27/8, 2017 at 11:3 Comment(0)
G
2

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++;
}
Galvano answered 25/8, 2017 at 9:1 Comment(0)
D
1

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. {} != {}

Dreher answered 1/9, 2017 at 6:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.