Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there an operator that works as concatMap but with more than one inner observables

I'm using an observable to query my DB. This observable will return an array with all the matching objects found. My problem is that I want to map the observable with more details that I will retrieve from another API.

I tried concatMap but it only let me nest 1 observable inside the initial observable

const usersAPI$: Observable<Users[]> = //  something
const assistsAPI$: Observable<Assists[]> = //  something

const modifiedAssists$ = assistsAPI$.find().pipe(
  concatMap(assists =>
    assists.map(assist => usersAPI$.get(assist.userID)
       .pipe(
          map(user => 
            assist = {...assist}, ...{userName: user.userName}
          )
        )
    )
  )
);

You can see a similar working example here, on stackblitz, https://stackblitz.com/edit/rxjs-concatmap-issue-stackoverflow where the "result" is the correct way of using concatMap and the "result2" is the non working way that I expected to work

like image 800
J Rui Pinto Avatar asked Apr 07 '19 22:04

J Rui Pinto


Video Answer


1 Answers

You have to process your inner array of Observables in some way. You could use forkJoin, merge or concat depending on your needs.

forkJoin

forkJoin will process your inner API calls in parallel and return an array when all API calls complete. Note that the inner Observables have to complete. I think this is what should suit you in your app.

import { forkJoin } from 'rxjs';

const modifiedAssists$ = assistsAPI$.find().pipe(
  concatMap(assists => forkJoin(assists.map(assist => someObservable ))
);

merge

merge will subscribe to all inner API calls at once and emit the results of all calls one by one as they arrive.

import { merge } from 'rxjs';

const modifiedAssists$ = assistsAPI$.find().pipe(
  concatMap(assists => merge(...assists.map(assist => someObservable )),
  // toArray() add the toArray operator if you need the result as an array
);

This will give you the desired result in your stackblitz, but I think your stackblitz is somewhat missleading and not what you're looking for in the example code from your question, as the inner Observable in your stackblitz emits multiple times and the final output isn't an array. If the order of the inner requests doesn't matter, they all emit one value and then complete and you need the results of all request as an array just use forkJoin.

concat

concat will subscribe to all inner API calls one after another. The next Observable in the sequence will only be subscribed to after the previous completed. The execution will therefore be slower than with forkJoin or merge as the next HTTP request will only be executed after the previous returned a value. Use this if the calls to your userAPI specifically have to be made in the same order as the assists in your array.

import { concat } from 'rxjs';

const modifiedAssists$ = assistsAPI$.find().pipe(
  concatMap(assists => concat(...assists.map(assist => someObservable )),
  // toArray() add the toArray operator if you need the result as an array
);
like image 167
frido Avatar answered Oct 20 '22 14:10

frido