Chaining HTTP Calls Using Observables With RxJS
Sometimes we need to make multiple HTTP calls in a specific order to get data. The data from previous call are either required as the input for the next call or they just must be in that logic order to make sense. In the following code examples we use RxJS of operator to create Observables to mock HTTP calls.
Nested Subscriptions
The first solution we can come up with is to use nested subscription. The code below has a few disadvantages. The code is a bit difficult to read obviously and thus will be hard to maintain. Also, even those Observables will be started in the order of first, second and third. The order of complete is in the reverse order. The log output will be like:
import { delay, of} from 'rxjs';
let first$ = of(1).pipe(delay(100));
let second$ = of(2);
let third$ = of(3);
first$.subscribe(
data => {
console.log("First data returned");
second$.subscribe(
data => {
console.log("Second data returned");
third$.subscribe(
data => {console.log("Third data returned")},
error => {},
() => { console.log("Third call completed")}
)},
error => {},
() => { console.log("Second call completed")}
)
},
error => {},
() => { console.log("First call completed")}
);
The log output:
First data returned
Second data returned
Third data returned
Third call completed
Second call completed
First call completed
Use concatWith
RxJS concatWith (concat works as well) operator let us use a source Observable and multiple other Observables for chaining after source Observable completed. Each of provided Observable won't be subscribed until the one before it completes.
Recommended by LinkedIn
import { concatWith, delay, of} from 'rxjs';
let first$ = of(1).pipe(delay(100));
let second$ = of(2).pipe(delay(100));
let third$ = of(3);
first$.pipe(concatWith(second$, third$))
.subscribe(
data => console.log(data)
);
We add a delay to the first two Observables but the third one won't complete before the first two. The log from above code will be 1, 2 and 3. This solution is simple but has one drawback: it doesn't allow subscription to individual Observable which is sometimes not desirable.
Use switchMap to set loosely chained calls
RxJS switchMap operator allow us to map source values to Observables as output.
import { of, switchMap} from 'rxjs'
let obsA$ = of("first call A");
let obsB$ = of("second call B");
let obsC$ = of("third call C");
obsA$.subscribe(
data => console.log(data),
error => {},
() => console.log("A completed")
);
obsA$.pipe( switchMap(() => obsB$))
.subscribe(
data => console.log(data),
error => {},
() => console.log("B completed")
);
obsB$.pipe(switchMap(() => obsC$))
.subscribe(
data => console.log(data),
error => {},
() => console.log("C completed")
);
In the code above, Observables obsA$, obsB$ and obsC$ will be called and completes in the order of A, B and C due to the fact that the next Observable only get subscribed when previous one completes. The log output:
first call A
A completed
second call B
B completed
third call C
C completed
Although the code is longer than concatWith solution, the code is very flexible and readable. This solution is way better than the nested subscriptions regarding maintenance.