Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxJS combineLatest even if one of the sources is not emitting values

I need data from two sources to combine into one res: {data1: any, data2: any} object, and I need to achieve this even if one of the sources doesn't emit any values

Here's the structure I expect:

xxx (
    source1,
    source2,
    (data1, data2) => ({data1: data1, data2: data2})
).subscribe(res => {
    doSomething1(res.data1)
    doSomething2(res.data2)
})

Is there any rxjs operators that can do this?

currently I can solve this with combination of startWith and combineLatest- I emit null value so combineLatest can start emitting values - any better way to solve this without startWith(null)?

combineLatest (
    source1.pipe(startWith(null)),
    source2.pipe(startWith(null)),
    (data1, data2) => ({data1: data1, data2: data2})
).subscribe(res => {
    if(res.data1) {
        doSomething1(res.data1)
    }
    if(res.data2) {
        doSomething2(res.data2)
    }
})
like image 654
godblessstrawberry Avatar asked May 08 '19 08:05

godblessstrawberry


3 Answers

As @ritaj already mentioned, you seem to do it fine already, though:

  1. I would substitute startWith with defaultIfEmpty operator to proceed only if any of the observables is empty. Heres a marble diagram to compare the two approaches:

startWith vs defaultIfEmpty with combineLatest operator

* Note that stream with startWith emits twice

  1. Use a unique object (or Symbol) instead of simple null, just in case of a source stream really emits null. This will ensure that we filter out only our markers, but not real results

  2. Use a filter instead of if-else in the subscription — should look just a bit cleaner. This is a general good practice to keep .subscribe as thin as possible

Heres an example:

const NO_VALUE = {};

const result$ = combineLatest(
  a$.pipe(  defaultIfEmpty(NO_VALUE)  ),
  b$.pipe(  defaultIfEmpty(NO_VALUE)  )
)
.pipe(filter(([a, b]) => a !== NO_VALUE || b !== NO_VALUE))

^ Run this code with a marble diagram.

Hope this helps

like image 96
kos Avatar answered Oct 16 '22 21:10

kos


You could use a BehaviorSubject. This provides you with a stream that emits a default value when subscribed to, if no more recent value is available (in this case null).

const subject1 = new BehaviorSubject(null),
      subject2 = new BehaviorSubject(null);

source1.subscribe(subject1);
source2.subscribe(subject2);

combineLatest(subject1, subject2).subscribe(res => {
    if(res.data1) {
        doSomething1(res.data1)
    }
    if(res.data2) {
        doSomething2(res.data2)
    }
});
like image 3
thijsfranck Avatar answered Oct 16 '22 19:10

thijsfranck


i know this an old thread. however, i recently faced the same issue and solved as @godblessstrawberry did

  const source1 = new Subject(),
              source2 = new Subject();
        
        const vm$ = combineLatest([
            source1.pipe(startWith([{id: 1}])),
            source2.pipe(startWith(['abc'])),
        ])
            .pipe(map(([profile, page]) => ({profile, page})));
        
        vm$.subscribe(stream => {
            console.log({stream});
        })
like image 3
mamadou jallow Avatar answered Oct 16 '22 20:10

mamadou jallow