基本按钮禁用时出现 ExpressionChangedAfterItHasBeenCheckedError

ExpressionChangedAfterItHasBeenCheckedError on basic button disable

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

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

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

示例代码如下:

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

我 fork 你的 Stack-Blitz 并通过在你的 finalize 上触发 detectChanges() 让错误消失。

Stack-Blitz with detectChanges

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

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

(编辑于 2020-09-14)

另一种解决方案是在 finalizetap 函数之前向管道添加 delay(0)。此处有更多详细信息:Angular-Debugging.

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

(编辑于 2019-09-16)

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

调试有用的要点:

  • 您的事件处理程序的进入和退出
  • core.js: view.detectChanges();, view.checkNoChanges();, function bindingUpdated(lView, bindingIndex, value)
  • 你的 html 中的绑定点如 *ngIf="async..."
  • common.js: class AsyncPipe { 函数

你需要这样写代码

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();

          }
        }),
      );
  }
}