关于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