Reputation: 663
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))
Upvotes: 5
Views: 7393
Reputation: 18891
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(() => '[email protected]'),
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/[email protected]/dist/bundles/rxjs.umd.min.js"></script>
<script>
const {from, timer} = rxjs;
const {map, tap, concatAll, reduce} = rxjs.operators;
</script>
Upvotes: 1
Reputation: 14740
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
Upvotes: 3