问题描述
我做了一个非常基本的表格。在提交过程中,该按钮被禁用,一旦完成工作,则应将其启用。
但是这不起作用,相反,我得到了臭名昭著的ExpressionChangedAfterItHasBeenCheckedError
。
我阅读了有关CD和此异常的Max Koretskyi文章,但不明白为什么在这种情况下会发生这种情况?这是对事件的简单反应。有人可以详细解释到底是什么原因造成的,如何在没有setTimeout
或Promise
的情况下解决它?
这是示例代码:
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,因此引发异常。
如果我们添加submissionInProgress
或delay
,将发生订阅,但不会运行任何业务逻辑,代码将保留在setTimeout
中。堆栈为空,处理任务队列,将使用发出的值和finalize的副作用task queue?
更新modell。将这些插入CD后,就可以了。
有用的调试点:
- 您的事件处理程序的进入和退出
- core.js:
submissionInProgress = false
,view.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();
}
}),);
}
}