Search code examples
javascriptrxjspipelineredux-observablecombinelatest

How to use combineLatest with filter in certain observable?


This is a simplification of a complex case, where some observables in array could be filtered if the values are not valid. The problem is that the filtered observable is not allowing to the other complete the combine. What operator or approach could handle this case allowing the valid data log in the subscription?

// RxJS v6+
import { fromEvent, combineLatest, of } from 'rxjs';
import { mapTo, startWith, scan, tap, map, filter } from 'rxjs/operators';

const userData$ = [
   of({ name: 'Joseph', age: 23}), 
   of({ name: 'Mario', age: 33}), 
   of({ name: 'Robert', age: 24}), 
   of({ name: 'Alonso', age: 25})
];

const names = ['Joseph', 'Mario', 'Robert', 'Alonso'];

combineLatest(
  names.map((name, i) => {
     return userData$[i].pipe(
         map(({name, age})=> { return{ name, age: age * 2} }),
         filter(({age}) => age < 67),
         map(({name, age})=> { return{ name: name.toLocaleUpperCase(), age} }),
     )
 })
)
   .pipe(
     tap(console.log),
   )
   .subscribe();

Sample in stackblitz

If we change the value to 67 all the observables will show the data.


Solution

  • A typical problem with combineLatest is that it requires all source Observables to emit at least once so if you use filter to discard its only value then combineLatest will never emit anything.

    An easy solution is to make sure it always emits with defaultIfEmpty:

    combineLatest(
      names.map((name, i) => {
        return userData$[i].pipe(
          map(({name, age})=> { return { name, age: age * 2} }),
          filter(({age}) => age < 66),
          map(({name, age})=> { return { name: name.toLocaleUpperCase(), age} }),
          defaultIfEmpty(null),
        )
      })
    )
    

    Live demo: https://stackblitz.com/edit/typescript-rsffbs?file=index.ts

    If your real use-case uses other source Observable than of() that doesn't complete immediatelly you might want to use startWith instead.