Updating the boolean in AfterViewInit causes the "Expression" to change after it was checked. "

I have a simple alert component that I create dynamically in a view. Since it was created dynamically, I set it up to be able to automatically display a warning after it was initialized.

While it works, I would like to understand why I have to manually trigger change detection in this particular case.

Code:

export class OverlayMessageComponent implements AfterViewInit {
    ...

    ngAfterViewInit() {
        if(this.autoShow) {
            this.show();
        }
        this.changeDetector.detectChanges();
    }

    ...
}

      

Full sample: https://plnkr.co/edit/8NvfhDvLVBd71I7DR0kW

I had to add this.changeDetector.detectChanges();

as I was getting the following error:

EXCEPTION: The expression was changed after it was tested.

I was under the impression that using it AfterViewInit

helps avoid the problem, but I think I am wrong. Is there a better way to structure your code to avoid this error?

I would like to better understand why this error is being returned. I've seen this error a few times before, and I know some say that with setTimeout()

or enableProdMode()

actually solves the problem, but to me this seems like a hacky workaround where the framework itself notifies you that there is a problem.

+3


source to share


3 answers


Correction

In your particular case, there is no need to run change detection or use asynchronous update. The fix is ​​simple, just move this.show

to the ngOnInit

lifecycle hook:

  ngOnInit() {
    if(this.autoShow) {
      this.show();
    }
  }

      

Explanation

Since you are using the component property bringIconToFront

in the template binding:



<div class="icon home" [class.add-z-index]="bringIconToFront"></div>

      

Angular needs to update the DOM of the component App

. Additionally, Angular calls lifecycle bindings for the child component OverlayMessage

. The DOM jumpers udpate and lifecycle are done as shown here :

  • causes OnInit

    and ngDoCheck

    subsidiary component ( OnInit

    called only during the first scan)
  • updates interpolations and DOM bindings for the current view App

    if the properties of a component instance of the current view are changed
  • calls ngAfterViewInit

    and ngAfterViewChecked

    for the child componentOverlayMessage

  • calls ngAfterViewInit

    also ngAfterViewChecked

    for the current componentApp

You can see that the call OnInit

is being called before the DOM bindings are updated for the current component. And it ngAfterViewInit

is called after. This is why it works in one case and doesn't work in another.

This article will help you understand the error better - Everything you need to know about the errorExpressionChangedAfterItHasBeenCheckedError

.

+3


source


I would like to better understand why this error is being returned

The hooks are lifecycle AfterViewInit

and AfterViewChecked

run after change detection and view creation are complete. Thus, any code that is executed in this step should not update the view, or your application and its view will fall out of sync. Take a look at the docs

Angular The unidirectional data flow rule prevents the view from being updated after it has been created. Both of these hooks light up after composing the component view.

Angular throws an error if the hook immediately updates the component's data-bound comment property.

As a result, you must either manually initiate change detection, which is an expensive operation because Angular has to go through the entire application again - or make the change asynchronous so that the view is updated in the next change detection step, with something like:



if(this.autoShow) { setTimeout(()=>this.show,0)}

      

Or, more simply, if you don't need to grab a handle to something in the view, you can run your code in ngOnInit()

or a little later in ngAfterContentInit()

. Because they are executed before the view is drawn up, you can make changes that affect the view without issue.

Docs: Order of Lifecycle Bindings

+2


source


Use detectChanges()

when you updated the model after angular triggered its detection change, or if there was no update at all in the angular world.

0


source







All Articles