基本按钮禁用时的ExpressionChangedAfterItHasBeenCheckedError

问题描述

我做了一个非常基本的表格。在提交过程中,该按钮被禁用,一旦完成工作,则应将其启用。

但是这不起作用,相反,我得到了臭名昭著的ExpressionChangedAfterItHasBeenCheckedError

我阅读了有关CD和此异常的Max Koretskyi文章,但不明白为什么在这种情况下会发生这种情况?这是对事件的简单反应。有人可以详细解释到底是什么原因造成的,如何在没有setTimeoutPromise的情况下解决它?

这是示例代码

https://stackblitz.com/edit/angular-change-detection-err?file=src/app/app.component.ts

解决方法

我通过触发detectChanges()上的finalize来分叉您的Stack-Blitz,并使错误消失。

Stack-Blitz with detectChanges

我将尝试解释为什么会这样。该错误似乎是在finalize上触发的,因为该错误在async pipe完成后在Observable之后运行。

换句话说,角度触发检测检查(如果您的组件不是async,则在OnPush操作之后总是执行此操作),但是在检测功能完成之后,检查的属性中的值不是相同,这就是您收到该错误的原因。如果您触发手动操作,则finilize上的Angular更改检测将再次检查更改,一切正常。

(于2020-09-14编辑)

另一种解决方案是在delay(0)finalize函数之前将tap添加到管道中。这里有更多详细信息:Angular-Debugging

这是我们示例的示例代码:Stack-Blitz with delay

(2019年9月16日编辑)

调试了一段时间的代码,现在很清楚会发生什么。 事件处理程序update()在提交时运行,将submissionInProgress设置为true,并将submissionResult$设置为observable。 事件后将启动更改检测。如果存在(按模板的顺序)绑定,则submissionInProgress绑定会评估其他绑定,并将其值记录为true。下一个绑定是async pipe的{​​{1}}。 submissionInProgress$注意到其输入不再为null并订阅它。但是async pipe导致可观察到的同步,因此在订阅期间可观察的评估。 OnNext发出,asyn管道的of(true)方法设置其_updateLatestValue字段,可观察的完成并运行_latestValue方法。将方法Finalize方法将finalize字段更新为submissionInProgress。请注意,angular已通过false绑定并将其值记录为true,但我们只是通过submissionInProgress更新了该字段。 CD记录finalize绑定现在的值为true。 在这些之后,第二轮CD将在其中检查是否没有更改。 它评估asyn pipe绑定,并看到该值为true,但现在为false,因此引发异常。 如果我们添加submissionInProgressdelay,将发生订阅,但不会运行任何业务逻辑,代码将保留在setTimeout中。堆栈为空,处理任务队列,将使用发出的值和finalize的副作用task queue?更新modell。将这些插入CD后,就可以了。

有用的调试点:

  • 您的事件处理程序的进入和退出
  • core.js:submissionInProgress = falseview.detectChanges();view.checkNoChanges();
  • function bindingUpdated(lView,bindingIndex,value)一样在HTML中绑定点
  • common.js:*ngIf="async..."个功能
,

您需要编写这样的代码

import { Component,VERSION,ViewRef,ChangeDetectorRef} from '@angular/core';
import {forkJoin,Observable,of} from 'rxjs';
import {ActivatedRoute} from '@angular/router';
import {FormBuilder,FormControl,Validators} from '@angular/forms';
import {finalize,map,tap} from 'rxjs/operators';
@Component({
  selector: 'my-app',templateUrl: './app.component.html',styleUrls: [ './app.component.css' ]
})
export class AppComponent  {

  submissionInProgress = false;
  submissionResult$: Observable<boolean>;

  name = new FormControl('',[Validators.required,Validators.minLength(3)]);

  testForm = this.fb.group({
    name: this.name
  });

  constructor( private fb: FormBuilder,private dtr: ChangeDetectorRef,){}

  update(){
      console.info('updateMeta');
      this.submissionInProgress = true;
    this.submissionResult$ = of(true)
      .pipe(
    
        finalize(() => {
          console.info('finalize submissionInProgress = false');
          this.submissionInProgress = false;
             if (this.dtr && !(this.dtr as ViewRef).destroyed) {
            this.dtr.detectChanges();

          }
        }),);
  }
}