有一个组件封装了一些库.为了避免所有这些库的事件监听器的变化检测噩梦,该库的范围在角区域之外:
@Component({ ... }) export class TestComponent { @Output() emitter = new EventEmitter<void>(); constructor(private ngZone: NgZone) {} ngOnInit() { this.ngZone.runOutsideAngular(() => { // ... }); } }
这一切都非常清楚和普遍.现在让我们添加事件来发出动作:
@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(); }); } }
问题是这个发射器不会触发变化检测,因为它是在区域外触发的.那么有可能重新进入该区域:
@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()); }); } }
最后,我提出了这个问题.即使我没有在父组件中监听此事件,this this.ngZone.run也会强制进行更改检测:
<test-component></test-component>
这是不想要的,因为,我没有订阅那个事件=>没有什么可以检测的.
什么可以解决这个问题?
对于那些对现实生活中的例子感兴趣的人,问题的起源是here.
解决方法
首先,感谢cgTag的回答.它引导我进入更好的方向,更易读,使用舒适,而不是getter使用Observable自然懒惰.
这是一个解释清楚的例子:
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()); } } }
这是另一个示例,其中使用了库实例observable(每次重新创建时都会发出库实例,这是一种非常常见的情况):
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>; }