0

订阅 QueryList 更改事件以及对事件做出反应并更改视图绑定到的属性时。我得到一个 ExpressionChangedAfterItHasBeenCheckedError 并且该值就像旧值一样。

我做了一个 stackblitz示例来显示错误。

我可以通过在队列中添加一个微任务来解决它。但我只是想了解正在发生的事情。

谢谢

4

1 回答 1

2

这就是发生的事情;

在开发模式下,会发生两个连续的 CD 周期,如本文的相关变更检测操作部分所述;

一个正在运行的 Angular 应用程序是一个组件树。在变更检测期间,Angular 对每个组件进行检查,这些组件包括按指定顺序执行的以下操作:

  1. 更新所有子组件/指令的绑定属性
  2. 在所有子组件/指令上调用 ngOnInit、OnChanges 和 ngDoCheck 生命周期挂钩
  3. 更新当前组件的 DOM
  4. 为子组件运行更改检测
  5. 为所有子组件/指令调用 ngAfterViewInit 生命周期钩子
  • 单击添加按钮后,当单击回调完成执行更改检测时开始。comp有 1 个元素
  • 在第一个 CD 循环期间;DOM 在第 3 步更新。元素comp添加到 DOM 并{{ count.length }}投影到 DOM 为 0
  • @ViewChildren('comp') test: QueryList<ElementRef> 在第 5 步之前更新test: QueryList。有 1 个元素
  • QueryList.changesobservable 在设置查询的同时发出ViewChildren,就在第 5 步之前。this.count.push(this.test.length)被执行并count.length变为 1。
  • ngAfterViewChecked被执行并且第一张CD(我们调查的相关部分)结束
  • 第二个 CD 循环开始。在此周期中,角度检查第一个 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));

于 2019-06-14T23:46:26.453 回答