角度单击事件处理程序不触发更改检测

Dan*_*cák 5 javascript angular angular-changedetection

简而言之,我的问题是组件模板中有一个元素。该元素有一个ngIf条件和一个(click)处理程序。它不会从一开始就渲染,因为 ngIf 条件的计算结果为false

现在有趣的部分来了:在角度区域之外运行的代码将该条件更改为true,并且在detectChanges手动执行更改检测器引用后,该元素将被渲染,并且单击处理程序 ofc 将变为活动状态。

到目前为止,一切似乎都正常,但问题是,当(click)用户单击回调时,不会触发组件的更改检测。

这是复制品https://stackblitz.com/edit/angular-kea4wi

在那里重现它的步骤:

  1. 单击米色区域
  2. 出现按钮,也单击它
  3. 尽管消息应该出现在下面,但什么也没发生

描述:

  1. 米色区域有一个通过 addEventListener 注册的点击事件处理程序,并且该事件监听器的回调在角度区域之外运行。在其中,组件的showButton属性设置为从falsetrue,我通过调用 手动触发更改检测detectChanges(),否则属性中的更改showButton将不会被注册。代码如下所示:

    this.zone.runOutsideAngular(() => {
       const el = this.eventTarget.nativeElement as HTMLElement;
       el.addEventListener('click', e => {
         this.showButton = true;
         this.cd.detectChanges();
       })
     })
    
    Run Code Online (Sandbox Code Playgroud)
  2. 现在按钮出现了,这要归功于*ngIf="showButton"最初没有渲染,并且它在模板中声明了一个单击事件处理程序。该处理程序再次更改组件的属性,这次showMessage更改为true.

    <button *ngIf="showButton" (click)="onButtonClick()">Click me!</button>
    
    onButtonClick() {
      this.showMessage = true;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 当我单击它时,处理程序显然会运行并将组件更改showMessagetrue,但它不会触发更改检测,并且不会出现下面的消息。要使示例正常工作,只需从一开始就将 showButton 设置为 true,上面的场景就可以工作。

问题是:这怎么可能?由于我(click)在模板中声明了事件处理程序,它在调用时不应该总是触发更改检测吗?

Dan*_*cák 8

我在 Angular 的存储库中创建了一个问题,事实证明,这种行为是合乎逻辑的,尽管可能是出乎意料的。重新表述 Angular 团队所写的内容:

导致带有处理程序的元素渲染的代码(click)在问题中所述的角度区域之外运行。现在,虽然我detectChanges()在那里手动执行,但这并不意味着代码突然神奇地在角度区域中运行。它可以正常运行变化检测,但它“停留”在不同的区域。因此,当元素即将被渲染时,元素的单击回调将在non-angular zone 中创建并绑定到 non-angular zone。这反过来意味着当用户点击触发它时,它仍然被调用,但不会触发更改检测

解决方案是将代码包装在角度区域之外,但需要在组件中执行一些更改zone.run(() => {...}).

因此,在我的 stackblitz 复制中,在角度区域之外运行的代码将如下所示:

    this.zone.runOutsideAngular(() => {
      const el = this.eventTarget.nativeElement as HTMLElement;
      el.addEventListener('click', e => {
        this.zone.run(() => this.showButton = true);
      })      
    })
Run Code Online (Sandbox Code Playgroud)

与调用不同的是,这detectChanges()使得this.showButton = true在正确的区域中运行,因此通过事件处理程序运行该代码而创建的元素也绑定到角度区域。这样,事件处理程序总是触发更改检测

这一切都归结为以下要点:在模板中声明事件处理程序 并不能自动保证在所有场景中都能检测到更改