Search code examples
angularelectronangular-changedetection

Can't get view to update using observables with ngClass binding


I have an Electron/Angular application that starts external applications and I am monitoring whether those services are running or not and showing green/red indicators in a footer to indicate that. I'm using the Electron IPC API to communicate the status of the devices from Electron to Angular. This is all working and I'm getting emitted events that I can listen to in Angular.

I have a service that listens for IPC status updates

this.electronService.ipcRenderer.on(IpcChannels.SERVICE_STATUSES_UPDATE, (event, { deviceStatus }) => {
  // sending the new values through a private subject. There is a public
  // observable for the subject which consumers use. This works, i
  // receive updates when the services are stopped/started
  this.deviceStatus.next(deviceStatus);
});

In my component template

<span class="bottom-icon dot"
  [ngClass]="{'inactive': !(deviceStatusService.deviceStatus$ | async)}"></span>

<span>{{'FooterBar.deviceLabel' | translate}}</span>

This is where it fails. The class is never updated to have the 'inactive' class. I fiddled with it for hours before finding accidentally that it is actually working. Angular just doesn't re-render or get changes I guess because I found that with a delay of starting up the services, when I my app would make a net request AFTER the status had changed but before it changed back to the original value then angular would re-render. and it would indeed update. But then after the service restarted, again, it wouldn't update until something else triggered angular to update.

I don't have the OnPush change detection strategy. I tried injecting a ChangeDetectionRef and withing the component file listening to the observables myself and running detectChanges() on the ChangeDetectionRef.

If I add this to my service on initialization and just manually/randomly update the observables, it actually works instantly, but when i switch back to only emitting new values from within the ipc renderer handler, then it does not.

interval(5000)
  .subscribe(_ => {
    this.posPrintStatus.next(Math.random() > .5 ? true : false);
    this.cePosStatus.next(Math.random() > .5 ? true : false);
  });

I'm at a loss as to if this a programmatic issue, or an angular change detection issue that I'm not understanding.

If there is any other code/context around this that I can provide please let me know.


Solution

  • The answer was fairly simply, but I had never run into the issue and never thought about it.

    It was because the IPC communication was happening outside of the Angular zone. I had to inject an instance of NgZone into my service and use NgZone.run() in order update the observable values inside the angular zone.