Search code examples
angularionic-frameworkrxjsrxjs-observablesrxjs-pipeable-operators

How do I combine two data streams, filter them based on an objects field, and then merge them in RxJS?


I am getting a little overwhelmed with combining data streams using declarative RxJS and I haven't been able to figure this one out.

I have two data streams like so:

seriesCards$ = this.http.get<SeriesCard[]>(this.seriesCardsUrl)
    .pipe(
      shareReplay(1),
      catchError(CardsService.handleError)
    );

and,

allWatchCards$ = this.http.get<AllWatchCards[]>(this.watchCardsUrl)
    .pipe(
      shareReplay(1),
      catchError(CardsService.handleError)
    );

As you can see, each one returns a different object array. I have a requirement in my app that now I need to combine them to return them all together based on a certain criteria for each one.

Each object has a field called category and that's the one I want to filter on for both.

Ideally I want my service to combine both the data streams and then my page/component to do the filtering and return each object from both object arrays based on that filter.

My service.ts:

watchCardWithSeriesCardsForDevotionals$ = combineLatest([
    this.allWatchCards$,
    this.seriesCards$
  ]).pipe(
    mergeMap(([watchCards, seriesCards]) =>
      merge(watchCards, seriesCards),
    ));

My component.ts

displayBothCards$ = this.cardService.watchCardWithSeriesCardsForDevotionals$
    .pipe(
      map(bothCards => bothCards) <!-- This is where I want to filter but it defaults to only the one objects field? -->
    );

For a reason I am not sure of, the map(bothCards => bothCards) is flattened to an object instead of an array of those objects so.

An example from a single data stream that I have that I am trying to do with two data streams:

displaySomething$ = this.cardService.allWatchCards$
    .pipe(
      map(somethingCards => somethingCards.filter(somethingCard => somethingCard.category === 'something')
        .sort(sortByDate))
    );

Any help and guidance would be appreciated!


Solution

  • You can use forkJoin:

    Service

    watchCardWithSeriesCardsForDevotionals$ = forkJoin({
      watchCards: this.allWatchCards$,
      seriesCards: this.seriesCards$
    });
    

    Component

    displayBothCards$ = this.cardService.watchCardWithSeriesCardsForDevotionals$
     .pipe(
       tap(({ watchCards, seriesCards }) => {
         console.log(watchCards);
         console.log(seriesCards);
       }),
       map(({ watchCards, seriesCards }) => {
         const filteredWatchCards = watchCards.filter((watchCard) => watchCard.category === 'devotional');
         const filteredSeriesCards = seriesCards.filter((seriesCard) => seriesCard.category === 'devotional');
          
        return { 
          myFilterWatchCards: filteredWatchCards as SeriesCards[]
          myFilterSeriesCards: filteredSeriesCards as AllWatchCards[]
        };
       })
     );
    
    <ng-container *ngIf="displayBothCards$ | async as bothCards">
     <div *ngFor="let watch of bothCards.myFilterWatchCards">
       <!-- watch.something -->
      </div>
    
     <div *ngFor="let serie of bothCards.myFilterSeriesCards">
       <!-- serie.something -->
      </div>
    </ng-container>
    

    You can read more about it here