NgZone 외부에 이벤트 리스너 추가

Angular 프레임워크에 익숙하다면 기본적으로 모든 비동기 이벤트가 변경 감지 프로세스를 트리거한다는 것을 알 수 있습니다. 특정 상황에서는 걱정할 필요조차 없습니다. 예상대로 작동합니다. 그러나 경우에 따라 변경 감지 프로세스를 너무 자주 실행하면 런타임 효율성이 저하될 수 있습니다.

NgZone에서 코드 실행



버튼을 클릭할 때 console.log 메서드를 호출한다고 가정합니다.

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");
}
}


버튼을 클릭하면 바인딩된 이벤트 리스너와 변경 감지 프로세스가 모두 트리거됩니다. 실제 시나리오에서는 console.log를 호출하는 대신 바인딩을 업데이트할 필요가 없는 작업을 수행할 수 있습니다.

runOutsideAngular 메서드의 잘못된 사용



문제의 메서드를 사용하면 변경 감지 프로세스를 옵트아웃할 수 있지만 이벤트 리스너를 등록하는 코드를 제공해야 합니다. 결과적으로 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 사용



ViewChild 데코레이터를 사용하여 DOM 노드에 대한 참조를 얻고 다음 방법 중 하나로 이벤트 리스너를 추가할 수 있습니다.

자세히 보기: Introduction To Angular Cli Builders

NgZone 외부의 클릭 핸들러




<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 외부에서 코드 실행 - using 지시문



이전 단락의 솔루션은 잘 작동하지만 약간 장황합니다. 종속성 주입을 통해 기본 DOM 요소(ElementRef 토큰)에 쉽게 액세스할 수 있도록 하는 특성 지시문에 논리를 캡슐화할 수 있습니다. 그런 다음 NgZone 외부에서 이벤트 리스너를 추가하고 적절할 때 이벤트를 내보낼 수 있습니다.

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 외부에서 코드 실행 - Event Manager Plugin 사용



지시문 기반 접근 방식은 이벤트 유형에 대해 구성할 수 없다는 단점이 있습니다. 고맙게도 Angular를 사용하면 이벤트 관리자 플러그인을 구축할 수 있습니다. 즉, 이름이 조건자 함수(supports 메서드)에 해당하는 이벤트에 대한 리스너 추가를 제어합니다. 일치하는 항목이 발견되면 addEventListener 메서드가 호출되어 작업을 처리할 수 있습니다. 두 가지 방법은 EVENT MANAGER PLUGINS 토큰 공급자로 등록된 사용자 정의 서비스의 일부입니다.


NgZone 외부의 클릭 핸들러



<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 외부에서 초기화 코드를 호출하면 변경 감지 프로세스가 트리거되는 것을 피할 수 있습니다.

타사 라이브러리가 NgZone 외부에서 초기화됨




<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 외부에서 초기화 코드를 실행하는 것을 고려해야 합니다.

좋은 웹페이지 즐겨찾기