Solvedangular docs: LifeCycle - changes in AfterViewChange -> expression has changed after it was checked.

As requested by @vicb,

I'm submitting a ... (check one with "x")
continuation of

[x ] bug report
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

#6005. Let's try to keep this one short so it isn't closed prematurely…

Current behavior
Changing a binding in ngAfterViewInit throws an exception.

Expected/desired behavior
It shouldn't.

Reproduction of the problem
If the current behavior is a bug or you can illustrate your feature request better with an example, please provide the steps to reproduce and if possible a minimal demo of the problem via https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).

http://plnkr.co/edit/v9zf4Txp8tOeUV35DdMD?p=preview

What is the expected behavior?
Still, it shouldn't

What is the motivation / use case for changing the behavior?
To make it so it doesn't throw an exception

Please tell us about your environment:

  • Angular version: 2.0.0-rc.4
  • Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
    I have tested Edge, Firefox and Chrome
  • Language: [all | TypeScript X.X | ES6/7 | ES5 | Dart]

@vicb mentioned "actions" that "have been taken". If these exist, then they are not available in rc.4.

34 Answers

✔️Accepted Answer

I have looked at the (generated) source code for calling ngAfterContentInit.

AFAI can see, it's entirely possible to change the semantics of the hook. Currently, it's called directly guarded by if ((self.numberOfChecks === 0)) as the last step during change detection. Instead of the direct function call within the guard, the compiler could generate scheduling a new task that calls the function and then runs a second (and only a second!) change detection.

There is also the possibility of introducing a new lifecycle hook that has the above, new semantic of scheduling a task and thus is invoked "after the dust has settled".

@pkozlowski-opensource
Reading again your comment, I can see a number of things that don't belong to this discussion given the current implementation or are just exaggerated

  • It's not possible to run into an infinite loop, that's what the numberOfChecks guard is already for
  • Uni-directional data flow has nothing to do with it. I cannot break out of the semantics of data binding
  • It's not about one round of change detection against many, it's one against two
  • Its not about a fundamental design change. It's about the semantics and ease of use of one lifecycle hook

So there is the current implementation of ngAfterViewInit

  • One call (the initial) to detectChanges
  • You cannot do anything that results in changed bindings. Most notably reacting to @ViewChild members, which are only available at that time
  • If you want to, you have to manually trigger another round of change detection. It's likely that you hit a couple of walls on the way when you do that for the first time
  • The documentation needs to call out these limitations carefully and offer guidance how to run a second round of change detection if the need arises

The changed semantics for ngAfterViewInit

  • Two calls (the initial and one together with ngAfterViewInit) to detectChanges
  • There is full freedom of what you can do in the function, comparable to the other lifecycle hooks
  • The documentation needs to call out that implementing this hook has the perf implication of running a second change detection

If a component doesn't implement the hook, then there is no perf implication at all because the compiler would not generate that code.

Needless to say, I'm pro changed semantics, because it's so much easier to develop against and the perf implications are small and local. I've tried to find actual implementations of ngAfterViewInit on Github, but it's mostly trivial implementations (console.log) or wrong implementations (the initialized class member luckily has the same value as what's set through ngAfterViewInit). material2 or ng-bootstrap both succeed in avoiding this hook altogether as does the entire angular repository (besides integration tests).

Other Answers:

@zoechi is right here, you are not supposed to update the context after it has been dirty checked (change detection)

What makes ngAfterViewInit so special? Neither ngOnInit nor ngAfterContentInit have this behavior, so doesn't any kind of property setter.

ngAfterViewInit runs after the change detection. Which is not the case for ngOnInit. ngAfterContentInit is executed after the content has been dirty checked.

You mention the concept of "during change detection". What's that, when do I care and how do I detect that?

That's the error message you have.

I don't think this is an issue but probably deserve better doc.

/cc @wardbell

is there a way to easily find out the name of the offender? as @kemsky noted, there's only the value right now

  1. Message is not clear enough, it reports only top-level component and it reports only value, i.e. true != false and that is all, no name. Today I've spent hours trying to find changing property, by the way i could not reproduce it on local machine (inconsistent behavior for the same build on different pc, chrome). It was caused by NgClassValid in internal component which rendered select options with ngfor.
  2. Users should have listener to do initialization, there is no way to avoid it. Denial is not a solution. I bet everyone will end up using setTimeout because there is NO other way to do that. It makes every performance argument invalid. @Tragetaschen explained it very well. Also i have to mention that amount of breaking changes is already outside of any acceptable range.

I am having issues with this error as well. My issue occurs when adding another element to an ngFor collection, which also instantiates 2 layers of child components. The only way I have found to debug it is to comment out lines of code or lines of html until the error doesn't happen anymore and then surround in a setTimeout. I have not been able to resolve any of these errors with ChangeDetectorRef.detectChanges() but setTimeout seems to work consistently.

This message is what I get from angular. The error is actually in a completely different component, and it can also be caused by an ngIf check so it can be very time consuming to track down.

borrower.component.html:0:0 caused by: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.

+1 for outputting the property name in the error.

Edit: Sometimes the binding can be a combination of multiple properties so it may be harder than expected to output. Ex: *ngIf="model.booleanOne && model.booleanTwo"

More Issues: