Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular: custom filter infinite digest [duplicate]

I have a custom filter that takes in an Object, subjectBin, and returns a brand new Object, result. The filter is invoked from a ng-repeat. My custom filter works, but throws infinite $digest errors. Here is 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's 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;
    };

To my understanding, this is giving infinite $digest errors because my filter is returning a brand new Object every time the $digest cycle happens. And this causes the dirty-bit to be set again, and again and again...

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's, and it clearly takes in input and returns a brand new string out. It also does not throw any infinite $digest errors. In my eyes, this is analogous to my filter in that it returns a brand new Object.

Any insight on why my filter is throwing infinite $digest errors, but then this other filter does not?

like image 934
arhoskins Avatar asked May 19 '26 04:05

arhoskins


1 Answers

In your snippet of code, it seems you're radically changing the structure of your data.

Angular filters are not meant to perform deep changes in objects. They are meant, as the name says, to filter information. If your data manipulation requires you to change the data structure, you're probably doing it wrong.

In fact, in it's cycle digest, Angular is smart enough to successfully compare, by itself:

  • strings of any kind (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 start get messy with multidimensional arrays or objects. For instance, returning something a NEW array like this one ['a', 'b', ['c', 'd']] from a filter will cause an infinite loop in the digest cycle, because comparing oldValue with NewValue is ALWAYS false. You can say Angular performs a shallow comparisons.

To sum up, returning NEW multidimensional objects or arrays in a filter will reach the infinite $digest error.


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

If you don't create new objects in each cycle, then nothing prevents you from doing deep comparisons. However, data should be correctly structured .

For instance, in your case it seems you're using objects as a keymap, like this:

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 hard structure to filter because there is no common pattern (in fact, in your filter you're using 3 loops!!!).

You can restructure the information like this:

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

This will be much easier to use. In fact, you don't even need custom filters, you can use the default angular filters for simple comparisons

here's an example with data restructuration logic too(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>
like image 130
Tivie Avatar answered May 20 '26 17:05

Tivie