Angular 查询列表更改事件 ExpressionChangedAfterItHasBeenCheckedError
Angular QueryList change event ExpressionChangedAfterItHasBeenCheckedError
订阅 QueryList 更改事件时以及对事件做出反应并更改视图绑定到的 属性 时。我得到一个 ExpressionChangedAfterItHasBeenCheckedError 并且值就像旧值一样。
我做了一个 stackblitz example 显示错误。
我可以通过向队列中添加一个微任务来解决它。但我只是想了解正在发生的事情。
谢谢
事情是这样的;
在开发模式下,会发生两个连续的 CD 周期,如本文 Relevant change detection operations 部分所述;
A running Angular application is a tree of components. During change detection Angular performs checks for each component which consists of the following operations performed in the specified order:
- update bound properties for all child components/directives
- call ngOnInit, OnChanges and ngDoCheck lifecycle hooks on all child components/directives
- update DOM for the current component
- run change detection for a child component
- call ngAfterViewInit lifecycle hook for all child components/directives
- 单击添加按钮后,当单击回调完成时,更改检测开始。
comp
有 1 个元素
- 在第一个CD周期; DOM 在第 3 步更新。
comp
中的元素添加到 DOM 并且 {{ count.length }}
投影到 DOM 为 0
@ViewChildren('comp') test: QueryList<ElementRef>
is updated just before 步骤 5. test: QueryList
有 1 个元素
QueryList.changes
observable 在设置 ViewChildren
查询的同时发出,就在第 5 步之前。this.count.push(this.test.length)
被执行并且 count.length
变为 1.
ngAfterViewChecked
被执行,第一个 CD(我们调查的相关部分)结束
- 第二个CD周期开始。在此循环中 angular 用当前值检查第一个 CD 循环的先前值。
- 在此检查期间,它遇到
{{ count.length }}
被投影到 DOM 为 0 但现在 count.length
为 1。此时它抛出异常。
解释更多;将 {{ count.length }}
更改为 {{ count }}
不会引发错误,因为对象引用没有更改。
同样;将 {{ count.length }}
更改为 {{ comps.length }}
也不会引发错误,因为 comps.length
在第一个 CD 周期开始之前也是 1。
我还创建了一个演示,打印了与我们的调查相关的组件生命周期中的相关步骤。您可以看到在第 4 个 CD 周期开始之前抛出了异常。 (第 1 和第 2 个 CD 周期发生在按钮点击之前,因此在演示中应该观察第 3 和第 4 个 CD 周期)。还要注意 QueryList.changes
发出值的地方。
https://stackblitz.com/edit/angular-tmcttm
最后,正如您所说,这个特定问题的解决方案是通过更改此行向队列添加一个微任务
this.count.push(this.test.length);
进入这个
setTimeout(_ => this.count.push(this.test.length));
订阅 QueryList 更改事件时以及对事件做出反应并更改视图绑定到的 属性 时。我得到一个 ExpressionChangedAfterItHasBeenCheckedError 并且值就像旧值一样。
我做了一个 stackblitz example 显示错误。
我可以通过向队列中添加一个微任务来解决它。但我只是想了解正在发生的事情。
谢谢
事情是这样的;
在开发模式下,会发生两个连续的 CD 周期,如本文 Relevant change detection operations 部分所述;
A running Angular application is a tree of components. During change detection Angular performs checks for each component which consists of the following operations performed in the specified order:
- update bound properties for all child components/directives
- call ngOnInit, OnChanges and ngDoCheck lifecycle hooks on all child components/directives
- update DOM for the current component
- run change detection for a child component
- call ngAfterViewInit lifecycle hook for all child components/directives
- 单击添加按钮后,当单击回调完成时,更改检测开始。
comp
有 1 个元素 - 在第一个CD周期; DOM 在第 3 步更新。
comp
中的元素添加到 DOM 并且{{ count.length }}
投影到 DOM 为 0 @ViewChildren('comp') test: QueryList<ElementRef>
is updated just before 步骤 5.test: QueryList
有 1 个元素QueryList.changes
observable 在设置ViewChildren
查询的同时发出,就在第 5 步之前。this.count.push(this.test.length)
被执行并且count.length
变为 1.ngAfterViewChecked
被执行,第一个 CD(我们调查的相关部分)结束- 第二个CD周期开始。在此循环中 angular 用当前值检查第一个 CD 循环的先前值。
- 在此检查期间,它遇到
{{ count.length }}
被投影到 DOM 为 0 但现在count.length
为 1。此时它抛出异常。
解释更多;将 {{ count.length }}
更改为 {{ count }}
不会引发错误,因为对象引用没有更改。
同样;将 {{ count.length }}
更改为 {{ comps.length }}
也不会引发错误,因为 comps.length
在第一个 CD 周期开始之前也是 1。
我还创建了一个演示,打印了与我们的调查相关的组件生命周期中的相关步骤。您可以看到在第 4 个 CD 周期开始之前抛出了异常。 (第 1 和第 2 个 CD 周期发生在按钮点击之前,因此在演示中应该观察第 3 和第 4 个 CD 周期)。还要注意 QueryList.changes
发出值的地方。
https://stackblitz.com/edit/angular-tmcttm
最后,正如您所说,这个特定问题的解决方案是通过更改此行向队列添加一个微任务
this.count.push(this.test.length);
进入这个
setTimeout(_ => this.count.push(this.test.length));