Angular.js: Infinite $digest() loop AngularJS with filter expression on html attribute

Created on 31 Mar 2017  路  23Comments  路  Source: angular/angular.js

I'm submitting a ...

  • [x] bug report
  • [ ] feature request
  • [x] other (Please do not submit support requests here (see above))

Current behavior:

When I create a filter expression on an html attribute, an attribute that is later bounded incoming in a component and used inside an ng-repeat, it triggers an infinite digest cycle/loop. The output comes up fine on the screen, but there are errors in the console suggesting that it triggers and infinite digest cycle. Code to reproduce is on a plnkr: http://plnkr.co/edit/JdiLEIyji2pHd3eeNMUL?p=preview.
This also happens when the array I'm passing is an array of literals such as number or string (http://plnkr.co/edit/mfjPgZjBro39Nb7WYpjU?p=preview).

Expected / new behavior:

  1. If it is a bug, then the behaviour without the bug would not result in an infinite digest cycle, and it would improve readability / seperation of concerns. As the component I'm passing in the filtered array only needs to take care of how it's showing the items in the array, not extra filter params.
  2. If it isn't a bug, then I feel documentation needs to be changed as now I feel the error message is unclear to me, and the docs now ways it is possible to use filter expressions inside those places..

Minimal reproduction of the problem with instructions:

Here's a plnkr with the code in action http://plnkr.co/edit/JdiLEIyji2pHd3eeNMUL?p=preview - open the devtools in your browser to see the errors appear.

Angular version: 1.5.* and 1.6.1/2/3

Browser: [all]

Anything else:

Error message

Error: [$rootScope:infdig] http://errors.angularjs.org/1.6.3/$rootScope/infdig?p0=10&p1=%5B%5B%7B%22ms鈥%2C%22type%22%3A1%2C%22%24%24hashKey%22%3A%22object%3A5%22%7D%5D%7D%5D%5D
    at eval (angular.js:38)
    at m.$digest (angular.js:18048)
    at m.$apply (angular.js:18280)
    at eval (angular.js:1912)
    at Object.invoke (angular.js:5003)
    at c (angular.js:1910)
    at Object.Pc [as bootstrap] (angular.js:1930)
    at execute (VM877 main.ts!transpiled:21)
    at f (system.js:5)
    at Object.execute (system.js:5)

Possible solutions

  1. In the repo where I had the problem at first: I did <todo-list todo-items="$ctrl.todoItems" filter-by="{completed:true}"></todo-list>. For full source see here: https://github.com/aredfox/todo-angularjs-typescript/commit/e71900b96173b63ebcebb8e6c1fba00fe3997971. But I feel it's working around the problem, plus I don't understand why this triggers a $digest() cycle and why it shouldn't just work.

Related issue

  1. StackOverflow post of this issue: http://stackoverflow.com/questions/43119977/infinite-digest-loop-angularjs-with-filter-expression-on-html-attribute
$compile filters moderate broken expected use bug

Most helpful comment

@gkalpak - for sure, it would need tweaking for production :-)

@aredfox - There is nothing wrong with using =* as long as you don't want to ensure that changes inside the component do not appear outside the component.

I guess we are getting close to enough use cases to implement <*

All 23 comments

FWIW: I've edited your plunkr to reproduce the issue with an easier filter syntax. I guess it doesn't matter, but I wanted to drop it here just to be sure:
https://plnkr.co/edit/mnamqTtsogXZj8inB3L5?p=preview

plus I don't understand why this triggers a $digest() cycle and why it shouldn't just work.

A $digest cycle get's triggered whenever something changes (and angularjs is aware of these changes), filtering your array results in a new array hence a changed input binding and a need for the digest cycle to trigger in order for angular to know what has to be updated and what does not.

The issue with filtered arrays in component bindings is discussed here https://github.com/angular/angular.js/issues/14039
Implementing $doCheck is recommended, but I don't actually know how you would do that. :o

I'm interested to see how to implement a workaround using $doCheck, as mentioned in the issue you linked!

One quick workaround is to use =* binding rather than < since the former will not continue to trigger digests. See https://plnkr.co/edit/fk9SXfRQa3cE8KGfGWqt?p=preview

Yep, that did solve the plunkr I added: https://plnkr.co/edit/4czmVoTGz1YvM63IrPqs?p=preview

Out of curiosity: Anything available regarding the $doCheck you mentioned @petebacondarwin ?

Thinking about it now :-)

I think this is actually a bug - we are getting stuck in a state where it is testing ['a'] === ['a'] which is continually false.

Isn't this intended behavior?

If this is intended, I think it's odd to see the UI work but the console error out.

This is also expected behavior when there is an infinite digest error. We exit the digest loop to avoid freesing the browser. The error doesn't mean that there is something wrong with your app, except that some binding/watcher is constantly changing. In this case, the change doesn't matter, since the two array are identical (even if not pointing to the same reference), so it is expected that your app continues to work as expected.

But I don't think we should hit an infinite loop here. Why is the array identity changing?

Because of the filter, no?

OK, so the filter always returns a new object and the one-way binding only tests for object identity equivalence. So it is expected behaviour.

Here is a reasonable workaround (IMO), which could be tailored to the specific needs of the situation for best performance: https://plnkr.co/edit/g0AzLUAbwfiiIl8PNgcE?p=preview

<comp items="$ctrl.items | filter: $ctrl.filterBy | stable"></comp>
  .filter('stable', function() {
    var oldValue = NaN;
    return function(value) {
      oldValue = angular.equals(value, oldValue) ? oldValue : value;
      return oldValue;
    };

We should document this.

I am pretty sure this will break if you have more than one | stable in your template.

Thanks, @petebacondarwin, would using =* not be a viable option too? Or are there huge perf issues involved? Because this setup, it works of course, does make it a rather big workaround imho. Is there a possibility of ever having a <* binding?

@gkalpak - for sure, it would need tweaking for production :-)

@aredfox - There is nothing wrong with using =* as long as you don't want to ensure that changes inside the component do not appear outside the component.

I guess we are getting close to enough use cases to implement <*

@jbedard previously wondered if this could be handled in $parse directly: https://github.com/angular/angular.js/issues/14039#issuecomment-184002618

This should also be fixed by #16553 which adds one-way collection watching (<*) similar to the bidirectional version (=*).

Would be nice if we have one-time bindings in component and directive definition too, like "::<" or "<::" like we can use inside templates {{::$ctrl.variable}}

That would be cool indeed :smiley:

Right now, only the user of the component/directive has control over whether the binding is normal or one-time (which makes sense since they know whether the value can change). But it would be cool if the component/directive author could also have control, because oftentimes components/directives do to react to binding value changes, so there is no point in keeping the watcher active (but the user doesn't have to know about this).

@wilker7ribeiro, could you create a separate issue about this.
(We are aiming to have a feature freeze before the end of the month (as AngularJS is entering its LTS period), so we can't promise anything :wink:)

Two ways you could do this today:

link: function(scope, element, attr) {
  var oneTimeValue = $parse(attr.oneTimeAttr)(scope.$parent);
});
link: function(scope, element, attr) {
  var oneTimeValue = undefined;
  var watchRemover = scope.$parent.$watch(attr.oneTimeAttr, function(newValue, oldValue) {
    if (newValue !== undefined) {
      oneTimeValue = newValue;
      watchRemover();
  });
});

Indeed. And I'm afraid I'll have to agree with myself :grin:

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ceymard picture ceymard  路  3Comments

brijesh1ec picture brijesh1ec  路  3Comments

nosideeffects picture nosideeffects  路  3Comments

WesleyKapow picture WesleyKapow  路  3Comments

ashclarke picture ashclarke  路  3Comments