Search code examples
angularobservablebehaviorsubject

Angular observable not updating automatically


I have a service that I pass a value to so the value is available to all components that need it:

  setAnalysisStatus(statuses: AsyncAnalysis[]) {
    this.analysisStatus.next(statuses);
  }

I have a component that's shown using a button click. The component that displays calls another method to subscribe to analysisStatus

  getAnalysisStatus(): Observable<AsyncAnalysis[]> {
    return this.analysisStatus.asObservable();
  }

The component subscribes like this:

ngOnInit(){
this.changeGroupService.getAnalysisStatus()
.subscribe(result => {
  result.forEach(item => {
    this.changeGroupStatus.forEach((changeGroup, index) => {
      if (item.changeGroupId === changeGroup.id) {
        this.changeGroupStatus[index].name = this.changeGroupStatus[index].name;
        this.changeGroupStatus[index].status = item.status;
      }
    });
  });
});
}

When I trigger that component it shows the current state of analysisStatus. However, when the state of that changes, the component doesn't update. If I close then reopen that component it shows the new state. I need it to display the state and update when the state of analysisStatus changes.

analysisStatus is being set like this:

analysisStatus = new BehaviorSubject<AsyncAnalysis[]>([]);

My assumption is subscirbing via this.changeGroupService.getAnalysisStatus() should looks for update the values in this.changeGroupStatus. Am I missing something?

EDIT ---

So checking this.changeGroupService.getAnalysisStatus() in my ngOnInit I can see the value for result is, in fact, updating as needed but the template doesn't update to reflect the changes.


Solution

  • It appears the value rendered in the template does not directly depend on the value from the observable. Additionally the variable changeGroupStatus isn't assigned new values. Only few properties of it is changed. Angular might not detect the partial changes. In these circumstances, you could try to manually trigger change detection using ChangeDetectorRef. Try the following

    import { pipe, Subject } from 'rxjs';
    import { takeUntil } from 'rxjs/operators';
    
    destroyed = new Subject<void>();
    
    ngOnInit(){
      this.changeGroupService.getAnalysisStatus().pipe(
        takeUntil(this.destroyed)
      ).subscribe(result => {
          result.forEach(item => {
            this.changeGroupStatus.forEach((changeGroup, index) => {
              if (item.changeGroupId === changeGroup.id) {
                this.changeGroupStatus[index].name = this.changeGroupStatus[index].name;
                this.changeGroupStatus[index].status = item.status;
              }
            });
          });
          this.cdr.detectChanges();           // <-- trigger change detection here
        });
    }
    
    ngOnDestroy(): void {
      this.destroyed.next();
      this.destroyed.complete();
    }
    

    Update

    You could pipe in the takeUntil operator with an observable (that'll be completed in the ngOnDestroy hook) to avoid memory leak issues. But it could get tedious when there are multiple subscriptions in multiple components. There is a better solution for this issue I saw here: https://stackoverflow.com/a/45709120/6513921.