How exactly error ExpressionChangedAfterItHasBeenCheckedError works with angular detection?

51 views Asked by At

I read a lot of articles but still don't understand some cases with angular change detection. I got following example : Angular app with OnInit, AfterViewInit, DoCheck, AfterViewChecked implemented in all components:

  1. Parent
  2. Child
  3. DeepChild

Only in parent I got popular loading example implemented (and *ngIf="isLoading" in HTML):

export class AppComponent
  implements OnInit, AfterViewInit, DoCheck, AfterViewChecked
{
  isLoading = false;
  constructor(private changeDetectorRef: ChangeDetectorRef) {}

  ngAfterViewInit(): void {
    this.isLoading = true;
    console.log('parent - after view init');
  }

  ngAfterViewChecked(): void {
    console.log('AFTER CHECK');
  }

  ngDoCheck(): void {
    console.log('parent - do check');
  }
  ngOnInit(): void {
    console.log('parent - init');
    
  }
}

Output (without loading this.isLoading = true;) is :

parent - init
parent - do check
Child - init
Child - do check
DeepChild - init
DeepChild - do check
DeepChild - after view init
DeepChild - after check
Child - after view init
Child - after check
parent - after view init
parent - after view check
<with assigning loading value as in code above here I got expression changed error>
<and after that I got some more checks generated>
parent - do check
Child - do check
DeepChild - do check
DeepChild - after check
Child - after check
parent - after check

I got following questions :

  1. Why I got this additional checks?

  2. If I add loading I got error ExpressionChangedAfterItHasBeenCheckedError, I understand that this is after view is checked but I got this additional checks so why issue still exist?

  3. If I will add changeDetectorRef and manually do detect changes, problem is resolved, but why? Okey, I trigger next detection, but in privies one expression has still been changed after check, so why trigerring next one just resolve it?

  4. This detectChanges only triggers doCheck in child components.. despite of fact that I trigger it in parent component it's not calling detection in parent?

1

There are 1 answers

3
Andrew Allen On

You want uni-directional data flow in your applications. During the component life cycle angular renders data from parent down to child. Angular has a development check so that after the whole view is rendered in that cycle it goes ahead and runs the whole rendering of data from parent down to child to ensure nothing changed. In production this 2nd check won't run and the data that changed won't be rendered as having changed in that detection cycle.

If you set isLoading = false; and the parent is rendered and then you change that value just after, you've broken this uni-directional data flow. The children will get rendered with isLoading = false; despite you changing this after. Forcing change detection to run twice on the parent "solves" the issue because the parent will be rendered with the updated value which will work in production. But it's an anti-pattern to throw in detection cycles to fix bad data flow.

I've never used ngDoCheck in any code over 4 years of Angular, I wouldn't recommend spending a long trying to understand it.