I'm creating a Chrome extension with Angular to render a pop-up window. The view binding is not updating when a callback comes from the Chrome API and a property used in the view binding is altered as of a result of that callback. Adding additional complication, the callback isn't called directly but wrapped in an Observable.
If I call ChangeDetectorRef.detectChanges() then the view is updating appropriately. I tried using NgZone but I may be using it wrong - it has no effect if I include it or exclude it from the method that creates my observable.
ngOnInit() {
this.port = chrome.runtime.connect({ name: 'PopupToBackground' });
// ChromeUtil is an injected service wrapping chrome API calls.
this.chromeUtil.portOnMessage$(this.port).pipe(
tap((x: string) => {
// text is the bound property
this.text = x || 'no data';
// Uncomment line below and everything works.
//this.cd.detectChanges();
})
).subscribe();
}
@Injectable({ providedIn: 'root' })
export class ChromeUtil {
constructor(private zone: NgZone) { }
portOnMessage$(port: chrome.runtime.Port) {
return new Observable<any>(subscriber => {
return this.zone.run(() => {
const listener = (msg: any) => subscriber.next(msg);
port.onMessage.addListener(listener);
return {
unsubscribe() {
port.onMessage.removeListener(listener);
}
};
});
});
}
}
The onMessage events are executing outside of Angular, and you need to call zone.run() to trigger change detection. You almost have it, but are calling run() at the wrong point.
portOnMessage$(port: chrome.runtime.Port) {
return new Observable<any>(subscriber => {
const listener = (msg: any) => this.zone.run(() => subscriber.next(msg));
port.onMessage.addListener(listener);
return {
unsubscribe() {
port.onMessage.removeListener(listener);
}
};
});
}
The callback passed to port.onMessage.addListener() is executed outside of Angular. It's this callback that must be run inside the zone.
// Uncomment line below and everything works.
//this.cd.detectChanges();
If your component is using OnPush change detection, then you'll have to call this.cd.markForCheck() as you would with any external observable for a component. The alternative is to use the async pipe in the template.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With