关于Angular的变更检测

首先

在我使用Angular进行开发的过程中,出现了以下类似情况。

    Google Map上でマーカーをクリックした際に、コンポーネントのビューが更新されない

以后会发现这是由Angular的”变更检测”引起的。


首先,Angular的变更检测是什么呢?

主要有以下三点原因会更新Angular应用程序的状态。当这些原因出现时,Angular会认为状态已经更新,并更新视图。

Events – click, change, input, submitのようなユーザーイベント

XMLHttpRequests – fetchなどの非同期処理

Timers – setTimeout(), setInterval()など

这个案例是一个“click”事件,乍看之下似乎没有问题。


Angular的变化检测机制。

Angular的每个组件都配备了一个叫做变更检测器(Change Detector)的东西,它可以检测事件和异步处理的发生,并在每次变更时更新视图。

Change Detector 是一个与组件类似的树状结构,从父级到子级再传递到孙级,以传达变更。


变更检测的要素 – NgZone(Zone.js)

Change Detector的实质是一个名为Zone.js的异步处理实用工具库。

Angular 在其内部配备了 NgZone,将 Zone.js 作为其一部分,并对事件和异步处理进行了 Monkey Patch。然后,Angular 在 NgZone 中执行组件代码。通过这种机制,实现了变更的检测。


当我检查代码时

在ApplicationRef类中有以下的代码,会在启动时调用。

// zone.jsでパッチしているコードの処理が完了した時に通知し、
// 変更検知を行うthis.tick()を呼び出す。
this._zone.onMicrotaskEmpty.subscribe({
  next: () => {
    this._zone.run(() => {
      this.tick();
    });
  }
});

// 全てのコンポーネントに対して変更検知のためのdetectChanges()を呼び出す。
tick(): void {
  this._views.forEach(
    (view) => view.detectChanges()
  );
}

最初的 Google 地图案例是什么?

在谷歌地图上的点击是在Angular之外发生的,并且没有经过NgZone的修复。

因此,组件中的变更检测将被忽略,无论点击多少次也不会更新视图。


有没有解决方案?

通过使用NgZone的run()方法,我们可以显式地在NgZone的内部执行代码,从而使Angular能够进行变更检测。

gMarker.addListener('click', () => {
  this.ngZone.run(() => {
    this.markerClick.next(marker);
  });
});

如果不希望检测到反向变更

在NgZone的runOutsideAngular()方法中执行的代码不会被检测到变化。

this.ngZone.runOutsideAngular(
  () => this.hogeService.fuga()
);

通过这样做,可以在NgZone的外部执行代码,同时避免触发Angular的变更检测。


总结

Angular的变更检测通常情况下不需要过多关注,但当遇到与变更检测相关的问题时常常会遇到困难。

但是如果我们了解实际情况就是Zone.js的话,就没有必要那么害怕。

我认为仔细调查研究后,对Angular的理解将会更加深入。


参考的来源

    • AngularとZone.jsとテストの話 – Qiita

 

    • 日本語訳:Angular 2 Change Detection Explained – Qiita

 

    • Angularでイベントから無駄にChange Detectionを走らせないためにすべきこと

 

    • Zones in Angular by thoughtram

 

    application_ref.ts – angular/angular

非常感谢您的聆听

bannerAds