有一个组件封装了一些库.为了避免所有这些库的事件监听器的变化检测噩梦,该库的范围在角区域之外:
@Component({ ... })
export class TestComponent {
@Output()
emitter = new EventEmitter<void>();
constructor(private ngZone: NgZone) {}
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
// ...
});
}
}
Run Code Online (Sandbox Code Playgroud)
这一切都非常清楚和普遍.现在让我们添加事件来发出动作:
@Component({ ... })
export class TestComponent {
@Output()
emitter = new EventEmitter<void>();
private lib: Lib;
constructor(private ngZone: NgZone) {}
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
this.lib = new Lib();
});
this.lib.on('click', () => {
this.emitter.emit();
});
}
}
Run Code Online (Sandbox Code Playgroud)
问题是这个发射器不会触发变化检测,因为它是在区域外触发的.那么有可能重新进入该区域:
@Component({ ... })
export class TestComponent {
@Output()
emitter = new EventEmitter<void>();
private lib: Lib;
constructor(private ngZone: NgZone) {}
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
this.lib = new Lib();
});
this.lib.on('click', () => {
this.ngZone.run(() => this.emitter.emit());
});
}
}
Run Code Online (Sandbox Code Playgroud)
最后,我提出了这个问题.this.ngZone.run即使我没有在父组件中监听此事件,这也会强制进行更改检测:
<test-component></test-component>
Run Code Online (Sandbox Code Playgroud)
这是不想要的,因为,好吧,我没有订阅那个事件=>没有什么可以检测.
什么可以解决这个问题?
对于那些对现实生活中的例子感兴趣的人,问题的根源就在这里.
首先感谢 的cgTag回答。它引导我走向更好的方向,即更具可读性,使用起来更舒适,并且使用 Observable 的自然惰性而不是 getter。
这是一个很好解释的例子:
export class Component {
private lib: any;
@Output() event1 = this.createLazyEvent('event1');
@Output() event2 = this.createLazyEvent<{ eventData: string; }>('event2');
constructor(private el: ElementRef, private ngZone: NgZone) { }
// creates an event emitter that binds to the library event
// only when somebody explicitly calls for it: `<my-component (event1)="..."></my-component>`
private createLazyEvent<T>(eventName: string): EventEmitter<T> {
// return an Observable that is treated like EventEmitter
// because EventEmitter extends Subject, Subject extends Observable
return new Observable(observer => {
// this is mostly required because Angular subscribes to the emitter earlier than most of the lifecycle hooks
// so the chance library is not created yet is quite high
this.ensureLibraryIsCreated();
// here we bind to the event. Observables are lazy by their nature, and we fully use it here
// in fact, the event is getting bound only when Observable will be subscribed by Angular
// and it will be subscribed only when gets called by the ()-binding
this.lib.on(eventName, (data: T) => this.ngZone.run(() => observer.next(data)));
// important what we return here
// it is quite useful to unsubscribe from particular events right here
// so, when Angular will destroy the component, it will also unsubscribe from this Observable
// and this line will get called
return () => this.lib.off(eventName);
}) as EventEmitter<T>;
}
private ensureLibraryIsCreated() {
if (!this.lib) {
this.ngZone.runOutsideAngular(() => this.lib = new MyLib());
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是另一个例子,其中使用了可观察的库实例(每次重新创建库实例时都会发出库实例,这是很常见的场景):
private createLazyEvent<T>(eventName: string): EventEmitter<T> {
return this.chartInit.pipe(
switchMap((chart: ECharts) => new Observable(observer => {
chart.on(eventName, (data: T) => this.ngZone.run(() => observer.next(data)));
return null; // no need to react on unsubscribe as long as the `dispose()` is called in ngOnDestroy
}))
) as EventEmitter<T>;
}
Run Code Online (Sandbox Code Playgroud)