NGZone以外のイベントリスナーの追加


我々が角度フレームワークに精通しているならば、我々はデフォルトで、どんな非同期イベントも変化発見プロセスを引き起こすということを知っています.特定の状況では、我々もそれについて心配する必要はありませんそれはちょうど期待通りに動作します.しかし、場合によっては、変更検出処理を実行しすぎても実行効率が悪くなる.

NGZoneにおけるコードの実行


コンソールを呼び出したいと仮定します.ボタンをクリックするとログメソッド
NGZoneのハンドラをクリック
<button>Click me!</button>
import { AfterViewChecked, Component } from "@angular/core";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements AfterViewChecked {
onClick() {
console.log("onClick");
}
ngAfterViewChecked() {
console.log("CD performed");
}
}
ボタンをクリックすると、バインドされたイベントリスナーと変更検出処理の両方がトリガーされます.コンソールを呼び出す代わりに実世界のシナリオで.ログは、バインドを必要としないアクションを更新することができます.

runoutsideangleメソッドの不適切な使用法


問題のメソッドは、変更検出プロセスを選ぶことができますが、イベントリスナーを登録するコードを提供する必要があります.その結果、NGZoneの外側のコールバックを実行する次の解決策は、変更検出プロセスが実行されないようにしません
import { AfterViewChecked, Component, NgZone } from "@angular/core";
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements AfterViewChecked {
constructor(private readonly zone: NgZone) {}
onClick() {
this.zone.runOutsideAngular(() => {
console.log("onClick");
});
}
ngAfterViewChecked() {
console.log("CD performed");
}
}

ngzoneの外でのコードの実行


ViewChild装飾子を使用してDOMノードへの参照を取得し、イベントリスナーを次のいずれかの方法で追加できます.
続きを読む:Introduction To Angular Cli Builders

をクリックします


<button>Click me!</button>import { AfterViewChecked, AfterViewInit, Component, ElementRef, NgZone, Renderer2, ViewChild } from "@angular/core"; import { fromEvent } from "rxjs"; @Component({ selector: "my-app", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"] }) export class AppComponent implements AfterViewInit, AfterViewChecked { @ViewChild("btn") btnEl: ElementRef; constructor( private readonly zone: NgZone, private readonly renderer: Renderer2 ) {} onClick() { console.log("onClick"); } ngAfterViewInit() { this.setupClickListener(); } ngAfterViewChecked() { console.log("CD performed"); } private setupClickListener() { this.zone.runOutsideAngular(() => { this.setupClickListenerViaNativeAPI(); // this.setupClickListenerViaRenderer(); // this.setupClickListenerViaRxJS(); }); } private setupClickListenerViaNativeAPI() { this.btnEl.nativeElement.addEventListener("click", () => { console.log("onClick"); }); } private setupClickListenerViaRenderer() { this.renderer.listen(this.btnEl.nativeElement, "click", () => { console.log("onClick"); }); }

ngzoneの外でのコードの実行


前の段落の解決策はうまく機能しますが、少し冗長です.属性ディレクティブにロジックをカプセル化することができます.これにより、依存関係のインジェクションによって、DOM要素(ElementRefトークン)に簡単にアクセスできます.次に、NGZoneの外側にイベントリスナーを追加し、適切なときにイベントを出力します.

をクリックします


<button>Click me!</button>import { Directive, ElementRef, EventEmitter, NgZone, OnDestroy, OnInit, Output, Renderer2 } from "@angular/core"; @Directive({ selector: "[click.zoneless]" }) export class ClickZonelessDirective implements OnInit, OnDestroy { @Output("click.zoneless") clickZoneless = new EventEmitter(); private teardownLogicFn: Function; constructor( private readonly zone: NgZone, private readonly el: ElementRef, private readonly renderer: Renderer2 ) {} ngOnInit() { this.zone.runOutsideAngular(() => { this.setupClickListener(); }); } ngOnDestroy() { this.teardownLogicFn(); } private setupClickListener() { this.teardownLogicFn = this.renderer.listen( this.el.nativeElement, "click", (event: MouseEvent) => { this.clickZoneless.emit(event); } ); } }

NGZoneの外のコードの実行


ディレクティブベースのアプローチは、イベント型に設定できないという欠点があります.ありがたいことに、角度は私たちのイベントマネージャのプラグインを構築することができます.言い換えると、述語関数(サポートメソッド)に対応する名前のイベントのリスナーを追加する制御を行います.マッチが見つかった場合、AddeventListenerメソッドが呼び出され、ジョブを処理できます.つのメソッドは、イベントマネージャプラグインプロバイダーとして登録されているユーザー定義のサービスの一部です.

をクリックします
<button class="btn btn-primary">
Click me!</button>
import { Injectable } from "@angular/core";
import { EventManager } from "@angular/platform-browser";
@Injectable()
export class ZonelessEventPluginService {
manager: EventManager;
supports(eventName: string): boolean {
return eventName.endsWith(".zoneless");
}
addEventListener(
element: HTMLElement,
eventName: string,
originalHandler: EventListener
): Function {
const [nativeEventName] = eventName.split(".");
this.manager.getZone().runOutsideAngular(() => {
element.addEventListener(nativeEventName, originalHandler);
});
return () => element.removeEventListener(nativeEventName, originalHandler);
}
}
import { NgModule } from "@angular/core";
import {
BrowserModule,
EVENT_MANAGER_PLUGINS
} from "@angular/platform-browser";
import { AppComponent } from "./app.component";
import { ClickZonelessDirective } from "./click-zoneless.directive";
import { ZonelessEventPluginService } from "./zoneless-event-plugin.service";
@NgModule({
imports: [BrowserModule],
declarations: [
AppComponent,
// ClickZonelessDirective
],
bootstrap: [AppComponent],
providers: [
{
provide: EVENT_MANAGER_PLUGINS,
useClass: ZonelessEventPluginService,
multi: true
}
]
})
export class AppModule {}
幸いにも、NGZoneの外部から初期化コードを呼び出すことで、変更検出プロセスの起動を回避することができます.

サードパーティ製のlib


<button>Hover me!</button>import { Directive, ElementRef, NgZone, OnInit } from "@angular/core"; import tippy from "tippy.js"; @Directive({ selector: "[appTooltip]" }) export class TooltipDirective implements OnInit { constructor(private readonly zone: NgZone, private readonly el: ElementRef) {} ngOnInit() { this.zone.runOutsideAngular(() => { this.setupTooltip(); }); } private setupTooltip() { tippy(this.el.nativeElement, { content: "Bazinga!" }); } }

結論



DOMイベントに応答してバインディング更新を必要としないタスクを実行している場合、NGZoneの外で不要な変更検出を起動しないことでアプリケーションのパフォーマンスを向上させることができます.イベントリスナーの登録時には注意しなければなりません.最もエレガントで再利用可能なソリューションは、カスタムイベントプランナープラグインを使用することです.DOMを変更するサードパーティ製のソリューションを使用している場合、NGZoneの外部で初期化コードを実行することについて考えるべきです.