Search code examples
angularjsecmascript-6angular-filtersangular-meteor

Custom filters produces infinit digest loop with angular-meteor


I have the problem, that custom filters are producing an infinit digest loop in angular-meteor. (Angular error description page)

I made a working example in this plunk with pure angular. When I try the same with angular-meteor in es6 style, the script is running in the infinit loop.

This is my controller

class MyController {
  constructor($scope, $reactive) {
    'ngInject';
    $reactive(this).attach($scope);

    this.items = [{ item: 'item1' }, { item: 'item2' }, { item: 'item3' }, { item: 'item4' }];    
  }
}

And this is the template

<div ng-repeat="item in vm.items | testFilter">
  Item: {{item.item}}
</div>

The filter implementation just makes a copy of the original content (so it filters nothing but it's ok for demonstration).

[...]
.filter('testFilter', () => {
  return (items) => {
    var result = angular.copy(items);
    // maybe splice some elements from result
    return result;
  };
})
[...]

I don't understand the reason why this is working in plain angularjs but not working in meteor. Is it because of the es6=>es5 translation? Am I missing something and using filters the wrong way? Or did I find an angular-meteor bug?

I'd be glad for some advice. :)

Update

  • I figured out, that only the last filter in a chain produces this error. So it might happen, when the variable is passed to the scope.
  • The ES6=>ES5 translation seems to be fine for me, when I check the result in the build directory.

Solution

  • I created a generic workaround for this problem, so when migrating an existing angular application to meteor, the logic of custom filters can stay untouched in many cases. (Gist with comments)

        .filter('fixLoopFilter', () => {
            const instanceCache = {};
    
            return (items) => {
                const hash = CryptoJS.SHA1(angular.toJson(items)).toString();
                if (!instanceCache[hash]) instanceCache[hash] = [];
                instanceCache[hash].length = 0;
                items.map((item) => {
                    instanceCache[hash].push(item);
                });
                return instanceCache[hash];
            };
        })
    

    It doesn't really filter any element of the input array but returns a clone of the input. The clone is the same instance for each input configuration. It's not a deep clone. If the input array contains objects, the result array will contain the same instances.

    Usage:

    1. Add this filter to the base module of your project.
    2. Use it in your template as the last filter applied in a chain:

      ng-repeat="vm.items | customFilter | fixLoopFilter"
      

    Drawbacks:

    This filter can be performance and memory consuming for a large input array or a large Array of objets. It is just meant as a workarount till the issue is fixed or there is a better workaround for this issue.