Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Expression has changed after it was checked" when child component emits on ngOnInit

Tags:

angular

I have a parent component which displays a list of validation errors and renders a list of child components with ngFor. Each child component performs its validation during ngOnInit and outputs the result to the parent component. The parent component listens for this output and updates the list of validation errors, the result of which throws an error:

Expression has changed after it was checked

Now, I understand why this error is being thrown - at the start of the change detection cycle the validation errors were in one state, and at the end it was in another state, and this is not allowed.

What I do not understand is how to get around this issue. The parent component has to display a list of errors at the top of the page, and each child component will add their validation results to this list. If this is a breach of the uni-directional dataflow then please tell me how to get around this in a clean manner (i.e, not wrapping the validation in a setTimeout, not changing from an immutable list to a mutable one, and not explicitly invoking the change detector again after validation).

Plunker reproducing the issue: https://plnkr.co/edit/q52A1DraNOnxZa0qGFDo?p=preview


EDIT

I've "solved" the issue by constructing the EventEmitter with the isAsync flag:

new EventEmitter(true)

This means that values will be emitted asynchronously, so emitted values will be picked up on the next change detection cycle. I guess the result is the same as wrapping the logic in a setTimeout but this way at least we don't have to wrap our code in a timeout everywhere we may emit a value.

like image 726
Cristian Avatar asked Feb 12 '17 16:02

Cristian


1 Answers

Angular doesn't like when change detection itself causes changes and ngOnInit is called by change detection.

In devMode change detection does an additional change detection run after each regular change detection run, to check if the model is stable. If the model changed during change detection, it throws this error.

You can delay the change until after change detection is completed using

  ngOnInit() {
    setTimeout(() => {
      this.output.emit({value: true});
    }
  }

Plunker example

Update (see Ghetolay's comment below)

ngOnInit is not called by change detection and you can do bind changes inside it. What you cannot do is making change on a parent from a child's ngOnInit cause this breaks unidirectional data flow.

See also https://github.com/angular/angular/issues/10131#issuecomment-233369491

like image 152
Günter Zöchbauer Avatar answered Nov 07 '22 20:11

Günter Zöchbauer