Search code examples
angularrxjssubscriptionbehaviorsubject

Async Pipe not updating View when observable changes


I have a global service that saves a behaviour subject. The Behaviour subject stores an array. I have a filter component which emits a new version of that array, which I can see changes on the global service. However, where the behaviour subject is being used as async pipe, the values in the view do not change.

Here is my approach:

In my global service, I have a Behaviour Subject

public dataArray$: BehaviorSubject<Data[]> = new BehaviorSubject(someData);

filter component extends the Global service as so:

export class FilterComponent extends GlobalDataService

and then a triggerFilter function alters the original data (as a base point for the filter) and provides a new value to emit for the Behaviour Subject:

public triggerFilter(option: string): void {...

inside the function as so:

const filteredData = someData.filter(item => item.array.includes(option)); this.dataArray$.next(filteredData);

when subscribing inside the global data service as so:

constructor() { this.showcase$.subscribe((res) => console.log('service', res))}

I can see the .next method is being called and printing out the new array. But the async pipe anywhere it is being used is not emitting the new values to the view - thus the filter isn't working...

<app-filter></app-filter>
<app-data-items></app-data-items>

inside app-data-items:

<ng-container *ngIf="dataArray$ | async as dataArray">
     <span *ngFor="let data of dataArray" [ngStyle]="{ 'background-image': 'url(' + data.background + ')' }">
         <app-data-details [data]="data"></app-data-details>
     </span>
</ng-container>

This view doesn't update when the values from the behaviour subject change. My assumption would be that the async pipe once defined on view will automatically open a subscription and alter the values once changed? This is the point of the async pipe if I'm not correct?

I'm using Angular 16 and am currently scratching my head as to why this isn't working. I've considered triggering some form of change detection strategy (cdRef etc), but this seems like an anti measure when using async? This should be handled via the async pipe?

Anyone had this issue before and could offer some guidance?


Solution

  • The problem here is that your component is extending the GlobalDataService, thus creating a local copy of it. In this way its dataArray$ value will be different wrom the one in the service, because they are two different objects.
    If you want to share information across components, you should use your service as a Singleton.

    In an nutshell, you only update and read dataArray$ from the GlobalDataService in all your components, and inject the same instance of GlobalDataService in all your interested components.

    GlobalDataService

    export class GlobalDataService {
        public dataArray$: BehaviorSubject<Data[]> = new BehaviorSubject(someData);
    
        public filterData(option: string): void {
            const filteredData = doStuffWithOptions(options);
            this.dataArray$.next(filteredData);
        }}
    

    FilterComponent

    export class FilterComponent {
        triggerFilter(option: string): void {
            // You modify the BehaviourSubject in the service
            this.publicDataService.filterData(option)
        }
    }
    

    DataItemsComponent

    export class DataItemsComponent  {
        // Here you are doing a shallow copy of the BehaviourSubject
        public dataArray$ = this.publicDataService.dataArray$
    }
    
    <ng-container *ngIf="dataArray$ | async as dataArray">
         <span *ngFor="let data of dataArray" [ngStyle]="{ 'background-image': 'url(' + data.background + ')' }">
             <app-data-details [data]="data"></app-data-details>
         </span>
    </ng-container>