从子组件1更新父组件中的值会导致Angular中的ExpressionChangedAfterItHasBeenCheckedError [英] Updating value in parent component from child one causes ExpressionChangedAfterItHasBeenCheckedError in Angular
问题描述
我有两个组成部分:ParentComponent > ChildComponent
和一个服务,例如TitleService
.
I have two component: ParentComponent > ChildComponent
and a service, e.g. TitleService
.
ParentComponent
看起来像这样:
export class ParentComponent implements OnInit, OnDestroy {
title: string;
private titleSubscription: Subscription;
constructor (private titleService: TitleService) {
}
ngOnInit (): void {
// Watching for title change.
this.titleSubscription = this.titleService.onTitleChange()
.subscribe(title => this.title = title)
;
}
ngOnDestroy (): void {
if (this.titleSubscription) {
this.titleSubscription.unsubscribe();
}
}
}
ChildComponent
看起来像这样:
export class ChildComponent implements OnInit {
constructor (
private route: ActivatedRoute,
private titleService: TitleService
) {
}
ngOnInit (): void {
// Updating title.
this.titleService.setTitle(this.route.snapshot.data.title);
}
}
这个想法很简单:ParentController
在屏幕上显示标题.为了始终呈现实际标题,它订阅了TitleService
并侦听事件.更改标题后,将发生事件并更新标题.
The idea is very simple: ParentController
displays the title on screen. In order to always render the actual title it subscribes to the TitleService
and listens for events. When title is changed, the event happens and title is updated.
加载ChildComponent
时,它会从路由器获取数据(动态解析),并告诉TitleService
用新值更新标题.
When ChildComponent
loads, it gets data from the router (which is resolved dynamically) and tells TitleService
to update the title with the new value.
问题是此解决方案导致此错误:
The problem is this solution causes this error:
Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'Dynamic Title'.
该值似乎在变更检测回合中被更新.
It looks like the value is updated in a change detection round.
我是否需要重新安排代码以实现更好的实现,还是必须在某处启动另一轮变更检测?
-
我可以将
setTitle()
和onTitleChange()
调用移至受人尊敬的构造函数,但是我已经读到,除了初始化local之外,在构造函数逻辑中进行任何繁重的工作"都是不明智的做法属性.
I can move the
setTitle()
andonTitleChange()
calls to the respected constructors, but I've read that it's considered a bad practice to do any "heavy-lifting" in the constructor logic, besides initializing local properties.
此外,标题应由子组件确定,因此无法从中提取此逻辑.
Also, the title should be determined by the child component, so this logic couldn't be extracted from it.
我已经实现了一个最小的示例,以更好地演示该问题.您可以在 GitHub存储库中找到它.
I've implemented a minimal example to better demonstrate the issue. You can find it in the GitHub repository.
经过深入研究,仅在ParentComponent
中使用*ngIf="title"
时出现问题:
After thorough investigation the problem only occurred when using *ngIf="title"
in ParentComponent
:
<p>Parent Component</p>
<p>Title: "<span *ngIf="title">{{ title }}</span>"</p>
<hr>
<app-child></app-child>
推荐答案
The article Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError
error explains this behavior in great details.
有两种可能的解决方案:
There are two possible solutions to your problem:
1)将app-child
放在ngIf
之前:
<app-child></app-child>
<p>Title: "<span *ngIf="title">{{ title }}</span>"</p>
2)使用异步事件:
export class TitleService {
private titleChangeEmitter = new EventEmitter<string>(true);
^^^^^^--------------
经过深入调查,问题仅在使用时出现 * ngIf ="title"
After thorough investigation the problem only occurred when using *ngIf="title"
您要描述的问题不是ngIf
特有的,并且可以通过实现依赖于父输入的自定义指令来轻松重现,该父输入在将输入传递给指令后的更改检测期间会同步更新 :
The problem you're describing is not specific to ngIf
and can be easily reproduced by implementing a custom directive that depends on parent input that is synchronously updated during change detection after that input was passed down to a directive:
@Directive({
selector: '[aDir]'
})
export class ADirective {
@Input() aDir;
------------
<div [aDir]="title"></div>
<app-child></app-child> <----------- will throw ExpressionChangedAfterItHasBeenCheckedError
为什么发生这种情况实际上需要对特定于更改检测和组件/指令表示的Angular内部有很好的了解.您可以从以下文章开始:
Why that happens actually requires a good understanding of Angular internals specific to change detection and component/directive representation. You can start with these articles:
- The mechanics of DOM updates in Angular
- Everything you need to know about change detection in Angular
- Angular’s $digest is reborn in the newer version of Angular
尽管无法在此答案中详细解释所有内容,但这是高级解释.在摘要周期期间,Angular执行对子指令的某些操作.这些操作之一是更新输入并在子指令/组件上调用ngOnInit
生命周期挂钩.重要的是这些操作必须严格执行:
Although it's not possible to explain everything in details in this answer, here is the high level explanation. During digest cycle Angular performs certain operations on child directives. One of such operations is updating inputs and calling ngOnInit
lifecycle hook on child directives/components. What's important is that these operations are performed in strict order:
- 更新输入
- 致电ngOnInit
现在您具有以下层次结构:
Now you have the following hierarchy:
parent-component
ng-if
child-component
执行上述操作时,Angular遵循此层次结构.因此,假设当前Angular检查parent-component
:
And Angular follows this hierarchy when performing above operations. So, assume currently Angular checks parent-component
:
- 更新
ng-if
上的title
输入绑定,将其设置为初始值undefined
- 致电
ngOnInit
以获得ng-if
. -
child-component
不需要更新
- 为
child-component
调用ngOnInti
,将标题更改为parent-component
上的Dynamic Title
- Update
title
input binding onng-if
, set it to initial valueundefined
- Call
ngOnInit
forng-if
. - No update is required for
child-component
- Call
ngOnInti
forchild-component
which changes title toDynamic Title
onparent-component
因此,当在ng-if
上更新属性时,我们最终遇到了Angular传递title=undefined
的情况,但是在更改检测完成后,在parent-component
上有了title=Dynamic Title
.现在,Angular运行第二个摘要以验证没有更改.但是,当它与当前值与上一个摘要中传递给ng-if
的当前值进行比较时,它会检测到更改并抛出错误.
So, we end up with a situation where Angular passed down title=undefined
when updating properties on ng-if
but when change detection is finished we have title=Dynamic Title
on parent-component
. Now, Angular runs second digest to verify there's no changes. But when it compares to what was passed down to ng-if
on the previous digest with the current value it detects a change and throws an error.
在parent-component
模板中更改ng-if
和child-component
的顺序将导致以下情况:在ng-if
的角度更新属性之前,parent-component
的属性将被更新.
Changing the order of ng-if
and a child-component
in the parent-component
template will lead to the situation when property on parent-component
will be updated before angular updates properties for a ng-if
.
这篇关于从子组件1更新父组件中的值会导致Angular中的ExpressionChangedAfterItHasBeenCheckedError的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!