Angular 模板通信延迟一滴

Angular template communication one tick delayed

我一直在使用 ng-template 并发现自己遇到了某种延迟。

我在 stackblitz 中创建了一个简单的示例。 https://stackblitz.com/edit/angular-template-series-v?file=app%2Fparent%2Fparent1.component.ts

A Parent 组件通过模板将 Hello 组件发送给它的 Child

我们的想法是从 Child 使用 ngIf.

Hello 组件到达 ngOnInit 挂钩周期时,它会发出输出。父组件获取此输出并添加一条消息以显示。

但是在创建组件时而不是在一个周期之后显示该消息。在示例中,您需要单击按钮 "Toggle content" 两次才能显示消息 "Template created"。

如何解决此问题才能使消息出现在同一周期中?

如果您 运行 在父项中更改检测器 - 它将从第一次点击开始更新页面。

这里有一个例子:stackblitz

这是因为您打破了 Angular 变更检测周期。

这是您单击第一个切换按钮后发生的情况:

1- 您点击了开关

2- ZoneJS,在点击事件中有钩子得到通知(简单地说)

3- Zone 确保点击事件完成

4- Zone 会让 Angular 知道发生了 async 事件,我们必须检查组件并确保视图 ( DOM ) 反映了模型 ( model/the javascript 世界)。所以change detection会运行,它会检测所有的变化,发现模板中有绑定(ng-if)。

5- 在 ngIf 变为真后, 并注意到目前为止所有同步 ng-container 被创建并且您的 templateOutlet 已创建,您的 hello 组件将被渲染。

再次注意:这都是同步的。

6 - 视图已更新,更改检测现已关闭。

7 - 在您的 hello 组件中,您触发了另一个事件,即 created 并通知父组件,并且在您的父组件中,您正在 改变数组 ,而 Angular 不关心这个,因为没有异步事件,你也没有更新任何输入,你只是在更改检测完成他的工作后触发了另一个同步事件.

8- 数组现在更新了,但是视图没有(在父组件中)

9 - 您再次单击,同样的情况发生并且 Angular 在第一次更改检测中更新父视图 运行 并且您看到数组正在视图中反映出来。

证明我是对的:D :

 onTemplateCreated() {
    console.log('Got the event from chil');
   this.template.push('Template created');
    console.log('this.template',this.template);
  }

您会看到在单击 之后,控制台显示了正确的数组,但是直到您再次单击才显示正确的视图。

现在让我们切换到我的代码

 onTemplateCreated() {
    console.log('Got the event from chil');
   this.template = [...this.template,'Template created'];
  }

因为我没有变异并且引用正在更新,Angular 现在必须关心这个变化,但是 还有一个问题,Angular 的变更检测器已完成,您在没有 运行 任何 async 事件的情况下更新了模型,所以我们有麻烦了,因为 Angular 不喜欢更新模型变化检测完成后,

所以你会被抛出一个错误:

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value

要解决此问题,,其次,您需要告诉 Angular 您知道自己在做什么,并且 运行 手动更改检测:

  this._cd.detectChanges

或者,你可以让它异步:

onTemplateCreated() {
    console.log('Got the event from chil');
   setTimeout(()=>{
      this.template = [...this.template,'Template created'];
   })
  }

这是由于 Angular 中更改检测的工作方式。它没有链接到使用 ng-template,因为这个 modified stackblitz 显示(使用孙子而不使用 ng-template)。

关于变化检测需要知道的 2 件事:

  • 它在事件(点击、提交等)、xhr 请求或计时器后触发。
  • 它从组件树的顶部到底部出现

基本上,当您单击切换按钮时:

  • 处理程序被触发(在您的示例中切换 showContent 的值,但仅此而已。hello 组件是 NOT 在此阶段创建)
  • 更改检测开始,始终从上到下开始。
  • angular首先检查父组件是否需要更新视图。由于尚未发生任何变化,父视图未更新。
  • 子组件随后检测到其 showContentValue 已更改。这会导致创建 hello 组件。
  • hello 组件初始化后,created 事件被触发。父组件收到此事件并相应地更新消息数组。但是父视图此时已经检查过了,所以Angular不会对父视图做任何修改。

当你再次点击切换按钮时,则再次循环运行s,此时显示上一次循环推送到数组中的消息

这是一个 modified stackblitz example 生命周期挂钩日志

备注

强制更改检测再次 运行 将解决您的问题,但如果您不是直接从子级到父级进行通信,您最好使用共享服务(基于可观察对象)在它们之间广播消息不相关的组件