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:

  1. update bound properties for all child components/directives
  2. call ngOnInit, OnChanges and ngDoCheck lifecycle hooks on all child components/directives
  3. update DOM for the current component
  4. run change detection for a child component
  5. 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));