Getting ExpressionChangedAfterItHasBeenCheckedError error I update property of a Component from another Component

 ̄綄美尐妖づ 提交于 2019-12-25 03:14:37

问题


The demo is available here - https://stackblitz.com/edit/angular-a9wcee

Please open the console window to see the error.

Description I have a Bootstrap component ComponentA which should start up in two different ways depending on the application's startup process. Say if the application is started using url home.com then ComponentA should not show a pop up dialog on startup but if the application is started using home.com;signup then the application should show a pop up.

I have read that Input doesn't work for bootstrap components so I am passing an attribute to ComponentA from my index.html as a "startup context".

<comp-a someAttr="someValue"></comp-a> <!--depending on whether someAttr is empty ("") or not, the pop should not be shown or shown respectively -->

and ComponentA's uses a DialogComponent in its template as follows

<comp-dialog [message]=""></comp-dialog> <!-- I want the input to ComponentB to be empty to begin with -->

ComponentB is a Bootstrap css dialog box and takes an Input message which it shows when the dialog becomes visible. The tricky bit is that ComponentB is in ComponentA's template and thus Angular initialised it as part of ComponentA's start up but before ComponentA's start up finished, ComponentA tries to change ComponentB (the message property) if it determines that it has to show the dialog box (ComponentB) (by checking the attribute). I think this is creating issues with Change Detection and I am Angular is throwing ExpressionChangedAfterItHasBeenCheckedError error. How could I redesign the component interaction

The code snippets of ComponentA are

  • Check how I was started.

     ngAfterViewChecked(){
        this.signup = this.el.nativeElement.getAttribute('signup'); //get the attribute
        this.isSignupProcess();//check if ComponentB needs to be shown and update its message property
      }
    
      isSignupProcess(){
        console.log("sign up is "+this.signup)
        if(this.signup!==""){ //show ComponentB
          if(this.signup === "success") {
            this.showDialog("Signup was successful",new DialogContext("","")) //set message property of ComponentB
          }else if(this.signup === "error") {
            this.showDialog("Error: Signup wasn't successful",new DialogContext("",""))
          } else {
            this.showDialog("Unrecognised message: "+this.signup,new DialogContext("",""))
          }
          this.signup = ""; //no need to show ComponentB
    
        } else {
    
        }
    

    }

the logic to update message property of ComponentB and show it is

showDialog(message:string,context:DialogContext) {

    this.dialogComponent.dialogShow(message,context);
  }

ComponentB simply calls Bootstrap's modal function

dialogShow(message:string, context:DialogContext){
    this.dialogContext = context;
    this.dialogMessage = message;
    console.log("dialog got context ",this.dialogContext);
    $(this.dialogRef.nativeElement).modal('show');

  }

the html of ComponentB is

<div #modal class="modal fade" tabindex="-1" role="dialog" id={{dialogID}}>
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">CodingJedi</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="dialogHide()">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <p>{{dialogMessage}}</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal" (click)="dialogHide()">Close</button>
      </div>
    </div>
  </div>
</div>

回答1:


The article explains well what causes the error - https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

Quoting from the link, Change detection in Angular goes through the following steps 1) update bound properties for all child components/directives i.e. and interpolation values {{}} 2) call ngOnInit, OnChanges and ngDoCheck lifecycle hooks on all child components/directives 3) update DOM for the current component 4) run change detection for a child component 5) call ngAfterViewInit lifecycle hook for all child components/directives

After each operation Angular remembers what values it used to perform an operation. They are stored in the oldValues property of the component view.

After the checks have been done for all components, Angular compares the current values with the ones it remembers from the previous digest cycle: 1) check that values passed down to the child components are the same as the values that would be used to update properties of these components now 2) check that values used to update the DOM elements are the same as the values that would be used to update these elements now 3) perform the same checks for all child components

After this, Angular runs a verification cycle to check that the values used to create Child Components and in DOM are still the same otherwise the change detection will not stabalise after one cycle and could end in infinite loop. If Angular finds that the value used to create a child component or used on DOM has changed before the current change detection cycle has finished then it will throw ExpressionChangedAfterItHasBeenCheckedError

In my code, DialogComponent is created with initial values of dialogMessage and dialogContext. Now, depending on how the app got started, I want to update the message and show the dialog box. If I just update the dialogMessage, Angular will give error ExpressionChangedAfterItHasBeenCheckedError because the value of dialogMessage had changed before the current change detection cycle finished.

There are two ways to fix the issue. Use setTimeout to create the next task which will be executed once the current change detection task finishes OR after changing the dialogMessage of DialogBoxComponent, run change detection again using detectChanges()

ngAfterViewChecked(){
    setTimeout(()=>this.isSignupProcess());

    }

    OR
ngAfterViewChecked(){
    this.isSignupProcess(); 
    this.cd.detectChanges();
    }


来源:https://stackoverflow.com/questions/52246410/getting-expressionchangedafterithasbeencheckederror-error-i-update-property-of-a

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!