Angular: custom filter infinite digest

I have a custom filter that takes an object, subjectBin, and returns a new result object . The filter is called from ng-repeat . My custom filter works but throws endless $ digest errors. Here's my filter:

   return function(subjectBin, field) {

    var result = {},
        faculty,
        subject;

    angular.forEach(subjectBin, function (value, key) {
        faculty = key;
        angular.forEach(value, function (value, key) {
            subject = key;
            value.forEach(function (course) {
                // Check "field" against some of the object properties
                //
                if (course.asString.toUpperCase().indexOf(field) > -1 ||
                    course.subjectTitle.toUpperCase().indexOf(field) > -1 ||
                    faculty.toUpperCase().indexOf(field) > -1 ) {
                    if (result.hasOwnProperty(faculty)) {
                        if (result[faculty].hasOwnProperty(subject)) {
                            result[faculty][subject].push(course);
                        }
                        else {
                            result[faculty][subject] = [course];
                        }
                    }
                    else {
                        result[faculty] = {};
                        result[faculty][subject] = {};
                        result[faculty][subject] = [course];
                    }
                }
            });
        });
    });

    return result;
    };

      

As I understand it, this gives endless $ digest errors because my filter returns a new Object every time the $ digest loop happens. And it causes the dirty bit to be set over and over and over ...

But then I get confused when I see examples like this:

  .filter('reverse', function() {
return function(input, uppercase) {
  input = input || '';
  var out = "";
  for (var i = 0; i < input.length; i++) {
    out = input.charAt(i) + out;
  }
  // conditional based on optional argument
  if (uppercase) {
    out = out.toUpperCase();
  }
  return out;
};
})

      

This is an example from the Angular doc and it explicitly accepts input and returns a new string out . It also doesn't generate any endless $ digest errors. In my opinion, this is similar to my filter, as it returns a completely new object.

Any insight as to why my filter is throwing infinite $ digest errors , but then this other filter is not working?

+3


source to share


1 answer


In your code snippet, it seems that you are radically changing the structure of your data.

Angular filters are not designed to make deep changes to objects. They mean, as the name says, to filter information. If you need to change the data structure to process the data, you are probably wrong.

In fact, there is a digest loop in it, Angular is smart enough to successfully compare on its own:

  • strings of any type (literals and string objects) - "foo" equals new String("foo")

  • 1 dimensional arrays by their elements ['a', 'b', 'c'] equals new Array(['a', 'b', 'c'])

  • 1 dimensional objects by their elements foo={a:'a', b:'b'} equals bar={a:'a', b:'b'}

However, things are starting to get confused with multidimensional arrays or objects. For example, returning something a new array like this ['a', 'b', ['c', 'd']]

from a filter will cause an infinite loop in the digest loop, because the comparison oldValue

with NewValue

equals ALLWAYS false. You can tell Angular does shallow comparisons.

To summarize, returning new multidimensional objects or arrays to the filter will reach an infinite $ digest error.


So what if I have a complex multidimensional data structure and need to do some deep comparison?

As long as you don't create new objects in each loop, there is nothing stopping you from doing deep comparisons. However, the data must be properly structured.



For example, in your case, it seems that you are using objects in the form of a keyboard layout, for example:

var subjectBin = {
        faculty1: {
            subject1: ['math'],
            subject2: ['science', 'history'],
            subject3: ['foo', 'blabla'],
            subject4: ['unraveling', 'the mistery']
        },
        faculty2: {
            subject1: ['that', 'all'],
            subject2: ['started', 'with a'],
            subject3: ['foo', 'blabla'],
            subject4: ['bigbang', 'BANG!']
        }
    };

      

This is a tricky structure to filter, because there is no general pattern (in fact, you are using 3 loops in your filter !!!).

You can restructure information like this:

var subjectBin = [{
        facultyName: 'faculty1',
        subjects: [{
            subjectName: 'subject1',
            courses: ['math']
        }, {
            subjectName: 'subject2',
            courses: ['science', 'history']
        }]
    }];

      

It will be much easier to use. In fact you don't even need special filters, you can use Angular default filters for simple comparisons

here is an example with data restructuring logic ( fiddle ).

var app = angular.module('app', []);

app.controller('fooCtrl', ['$scope',
  function($scope) {
    var bla = {
      faculty1: {
        subject1: ['math'],
        subject2: ['science', 'history'],
        subject3: ['foo', 'blabla'],
        subject4: ['unraveling', 'the mistery']
      },
      faculty2: {
        subject1: ['that', 'all'],
        subject2: ['started', 'with a'],
        subject3: ['foo', 'blabla'],
        subject4: ['bigbang', 'BANG!']
      }
    };


    $scope.items = [];

    angular.forEach(bla, function(value, key) {
      var faculty = {
        name: key,
        subjects: []
      };

      angular.forEach(value, function(value, key) {
        var subject = {
          name: key,
          courses: value
        };
        faculty.subjects.push(subject);
      });
      $scope.items.push(faculty);
    });

  }
]);
      

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='app'>
  <div ng-controller="fooCtrl">
    <ul>
      <li ng-repeat="faculty in items">Faculty Name: {{faculty.name}}
        <br/>
        <ul>
          <li ng-repeat="subject in faculty.subjects | filter:{courses:['foo']}">Subject name: {{subject.name}}
            <br/>
            <ul>
              <li ng-repeat="course in subject.courses">{{course}}</li>
            </ul>
          </li>
        </ul>Subjects: {{item.subjects}}</li>
    </ul>
  </div>
      

Run codeHide result


+6


source







All Articles