Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding not updating when component property changed in callback

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.

Relevant Component Code

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

Relevant ChromeUtil Code

@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); 
          } 
        };
      });
    });
  }
}
like image 348
Daniel Gimenez Avatar asked Apr 01 '26 16:04

Daniel Gimenez


1 Answers

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.


Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!