我有一个父组件,它显示验证错误列表,并使用ngFor
呈现子组件列表。每个子组件在ngOnInit
期间执行其验证,并将结果输出给父组件。父组件侦听此输出并更新验证错误列表,其结果抛出错误:
Expression has changed after it was checked
现在,我明白了为什么会抛出这个错误——在更改检测周期开始时,验证错误处于一种状态,而在结束时,它处于另一种状态——这是不允许的。
我不明白的是如何绕过这个问题。父组件必须在页面顶部显示一个错误列表,每个子组件都会将其验证结果添加到此列表中。如果这违反了单向数据流,请告诉我如何以干净的方式绕过这一点(即,不将验证包装在setTimeout
中,不从不可变列表更改为可变列表,以及在验证后不显式再次调用更改检测器)。
Plunker再现该问题:https://plnkr.co/edit/q52A1DraNOnxZa0qGFDo?p=preview
编辑
我通过构造带有isAsync
标志的EventEmitter
"解决"了这个问题:
new EventEmitter(true)
这意味着值将异步发出,因此发出的值将在下一个更改检测周期中拾取。我想结果与在setTimeout
中封装逻辑相同,但通过这种方式,至少我们不必在任何可能发出值的地方都在超时中封装代码。
Angular不喜欢变化检测本身导致变化,而ngOnInit
是由变化检测调用的。
在devMode中,更改检测在每次常规更改检测运行后都会进行额外的更改检测,以检查模型是否稳定。如果模型在更改检测过程中发生了更改,则会抛出此错误。
您可以将更改延迟到使用完成更改检测之后
ngOnInit() {
setTimeout(() => {
this.output.emit({value: true});
}
}
Plunker示例
更新(请参阅下面Ghetolay的评论)
ngOnInit
不是由更改检测调用的,您可以在其中绑定更改。您不能从子级的ngOnInit
对父级进行更改,因为这会中断单向数据流。
另请参阅https://github.com/angular/angular/issues/10131#issuecomment-233369491
您不允许更改ngOnInit
中的任何内容。Angular 2团队拒绝将其视为一个bug(#1031),所以是的,您必须以不干净的方式进行:setTimeout(..., 0)
或Promise.resolve().then(...)
。
在子组件中一如既往,但在父组件中我是这样做的(只有当传入值不同时才会触发更改检测):
getStatus(status: StatusModel) {
if (this.currentStatus !== status) {
this.currentStatus = status;
this.changeDetector.detectChanges();
}
}