godblessstrawberry
godblessstrawberry

Reputation: 5088

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)
    }
})

Upvotes: 10

Views: 15473

Answers (3)

mamadou jallow
mamadou jallow

Reputation: 411

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});
        })

Upvotes: 3

kos
kos

Reputation: 5384

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

Upvotes: 7

thijsfranck
thijsfranck

Reputation: 808

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)
    }
});

Upvotes: 3

Related Questions