3

I want to chain multiple observables in a single stream and preserve the previous value further down the pipe chain.

Each observable must run sequentially (one after the other, example: if the first observable completes it must go to the next one) and the order is important. I don't want to subscribes to them in parallel (just like forkJoin)

The output must give me the user info, cellphones, addresses and the email of the user.

I can do it with switchmap, this approach does work; but is there a better approach?

Just a dummy example:

    this.userService.getUser(id)
    .pipe(
        switchMap(userResponse => {
            return this.cellphoneService.getCellphones(userResponse.id)
                .pipe(
                    map(cellphonesResponse => [userResponse, cellphonesResponse])
                )
        }),
        switchMap(([userResponse, cellphonesResponse]) => {
            return this.emailService.getEmails(userResponse.id)
                .pipe(
                    map(emailResponse => [userResponse, cellphonesResponse, emailResponse])
                )
        }),
        switchMap(([userResponse, cellphonesResponse, emailResponse]) => {
            return this.addressService.getAddresses(userResponse.id)
                .pipe(
                    map(addressResponse => [userResponse, cellphonesResponse, emailResponse, addressResponse])
                )
        }),
    ).subscribe(response => console.log(response))
carlos
  • 591
  • 1
  • 5
  • 15
  • check this one https://stackoverflow.com/questions/53560652/how-to-make-a-sequence-of-http-requests-in-angular-6-using-rxjs/53560996#53560996 – Fan Cheung Jul 21 '22 at 02:40

2 Answers2

1

Instead of mapping your response to an array after each call, you can instead nest your switchMaps, then all responses will be in scope, so you can just use a single map:

this.userService.getUser(id).pipe(
   switchMap(user => this.cellphoneService.getCellphones(user.id).pipe(
      switchMap(cellphones => this.emailService.getEmails(user.id).pipe(
         switchMap(email => this.addressService.getAddresses(user.id).pipe(
            map(address => [user, cellphones, email, address])
         ))
      ))
   ))
).subscribe(response => console.log(response))

This is described in a little more detail in this answer

BizzyBob
  • 12,309
  • 4
  • 27
  • 51
  • @BizzyBop Is ``concat`` a good approach too? I was reading that ``concat`` runs multiples observables in sequence. And Is it bad practice to use many nested ``switchMap``? – carlos Jul 22 '22 at 00:50
  • It depends on the situation. Concat provides a different behavior. It will emit the result for each of its source observable. The solution above emits only one time after all “sources” emit. Also, with concat, you will not have access to emissions from other observables. I do not believe having bested switchMaps is inherently bad. – BizzyBob Jul 22 '22 at 09:27
1

You can create an observable of observables. The concatAll operator will subscribe to each one of them in order but only one at a time. Meaning that concatAll first subscribes to name$ and emits its value. Then name$ completes and concatAll moves to email$, etc.

In the end concatAll will emit three values but reduce will accumulate these values into one array. Thus the data$ stream emits only once.

const name$ = timer(250).pipe(
  map(() => 'john doe'),
  tap(str => console.log(`received: ${str}`))
);

const email$ = timer(150).pipe(
  map(() => 'johndoe@example.com'),
  tap(str => console.log(`received: ${str}`))
);

const phone$ = timer(50).pipe(
  map(() => '777 555 666'),
  tap(str => console.log(`received: ${str}`))
);

const data$ = from([name$, email$, phone$]).pipe(
  concatAll(),
  reduce((acc, data) => acc.concat(data), [])
);

data$.subscribe(d => console.log('emitted:', d));
<script src="https://unpkg.com/rxjs@7.5.7/dist/bundles/rxjs.umd.min.js"></script>
<script>
const {from, timer} = rxjs;
const {map, tap, concatAll, reduce} = rxjs.operators;
</script>
customcommander
  • 17,580
  • 5
  • 58
  • 84