Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RXJS How do I use the result of one observable in another (and then process those two results together)

A very common problem when using RxJs seems to be to want the result of one or more observables to then use them in subsequent ones.

e.g. in pseudo-code (This is not rx or valid js syntax deliberately)

var someResult = $observable-A; // wait to complete
var finalResult = $observable-B(someResult.aValueINeed);

This can be done in an ugly way where you could subscribe to both and call one inside the other.. however, this is very messy and doesn't afford you much flexibility.

e.g. (real syntax)

$observable-A.subscribe(resultA => { 
    $observable-B(resultA.aValueINeed)
        .subscribe(resultB => { 
            console.log('After everything completes: ', resultB); 
        }
}

This also means that nothing else can easily consume this stream as you're completing both of the observables.

My specific use case requires the following:

  1. An initial call to get $observable-A as it doesn't require anything else (it's a basic http GET call)
  2. An http GET call to another service that requires data from $observable-A, which returns $observable-B
  3. Using both observable results (A + B) to create an object for my service (In my case, an Angular service) to return a single list.

I also need to be able to subscribe to this function inside my service, this is why going with the subscribe method above, won't work for me.

like image 793
James Avatar asked Dec 14 '22 08:12

James


1 Answers

Snorre Danielsen nailed this problem and full credit for this solution goes to him. I'd recommend having a look at this.

The great thing about rxjs operators is their interoperability. You can mix and match almost everything to get your desired outcome.

In short. The code answer to this question is below, and i'll explain it further.

$observable-A.pipe(
    mergeMap(resultA => {
        return combineLatest(
            of(resultA),
            $observable-B(resultA)
        )
    }),
    map(([resultA, resultB]) => {
        // you can do anything with both results here
        // we can also subscribe to this any number of times because we are doing all the processing with
        // pipes and not completing the observable
    }
)

mergeMap (or flatMap which is an alias) takes an observable as an input and projects it (just like a regular map function). This means we can use its result as the input for $observable-B. Now this is great for when you actually want to only return the second observable result. e.g.

$observable-A.pipe(
    mergeMap(resultA => $observable-B(resultA)),
    map((resultB) => {
        // resultA doesn't exist here and we can only manipulate resultB
    }
)

Now, back to the main solution. combineLatest is the key here. It allows the mergeMap to essentially "wait" for the internal observable ($observable-B) to complete. Without it, you're just going to return an observable to your next rxjs operator in the pipe, which is not what we want (as you're expecting to deal with regular operator functions inside a pipe).

Because of this, we can take these new merged observables into the next part of the chain and use them together. We use the of() function to re-create resultA, as combineLatest only takes observables as inputs and resultA is a completed observable in this state.

A hint: Take a look at the map(([resultA, resultB]). The reason we use this notation to refer to our variables instead of the standard map(results). Is so we can directly reference them without using results[0] for resultA or results[1] for resultB. Its just a lot easier to read this way.


I hope this helps people out as I have yet to find a complete SO answer which covers this case. Edits are very welcome as its complicated and I'm sure its not a perfect answer.

like image 165
James Avatar answered May 04 '23 23:05

James