订阅 QueryList 更改事件以及对事件做出反应并更改视图绑定到的属性时。我得到一个 ExpressionChangedAfterItHasBeenCheckedError 并且该值就像旧值一样。
我做了一个 stackblitz示例来显示错误。
我可以通过在队列中添加一个微任务来解决它。但我只是想了解正在发生的事情。
谢谢
订阅 QueryList 更改事件以及对事件做出反应并更改视图绑定到的属性时。我得到一个 ExpressionChangedAfterItHasBeenCheckedError 并且该值就像旧值一样。
我做了一个 stackblitz示例来显示错误。
我可以通过在队列中添加一个微任务来解决它。但我只是想了解正在发生的事情。
谢谢
这就是发生的事情;
在开发模式下,会发生两个连续的 CD 周期,如本文的相关变更检测操作部分所述;
一个正在运行的 Angular 应用程序是一个组件树。在变更检测期间,Angular 对每个组件进行检查,这些组件包括按指定顺序执行的以下操作:
- 更新所有子组件/指令的绑定属性
- 在所有子组件/指令上调用 ngOnInit、OnChanges 和 ngDoCheck 生命周期挂钩
- 更新当前组件的 DOM
- 为子组件运行更改检测
- 为所有子组件/指令调用 ngAfterViewInit 生命周期钩子
comp
有 1 个元素 comp
添加到 DOM 并{{ count.length }}
投影到 DOM 为 0@ViewChildren('comp') test: QueryList<ElementRef>
在第 5 步之前更新test: QueryList
。有 1 个元素QueryList.changes
observable 在设置查询的同时发出ViewChildren
,就在第 5 步之前。this.count.push(this.test.length)
被执行并count.length
变为 1。ngAfterViewChecked
被执行并且第一张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));