RxJS can get confusing sometimes and it may not be obvious at first how to do certain things. One of these things may be how to do multiple HTTP requests the right way - but gladly it's not too complicated.
There might be two different cases for executing multiple requests; either one by another (sequentially) or simultaneously (parallel).
Requirements
This approach depends on Angulars `HttpClient` - hence this.http
will always refer to the HttpClient
.
Additionally, to make it easier to follow, this guide will use a simple PHP script as API:
<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json");
$timeout = isset($_GET['timeout']) ? $_GET['timeout'] : 0;
sleep($timeout);
echo json_encode([
'timeout' => $timeout
]);
This script allows to call it with a timeout
parameter, will wait to respond for the time given and respond with the time it has waited.
Sequentially
A good example for sequentially executing HTTP requests are dependent queries; e.g. you want to get query some user details and projects by this user which are queried at a different endpoint.
To make this work we're going to utilize the concatMap
function within our pipe. Let's look at an example:
import {concatMap, tap} from 'rxjs/operators';
this.http.get('http://test.localhost/api.php?timeout=1')
.pipe(
tap(res => console.log('First result', res)),
concatMap((res: { timeout: number }) => this.http.get(`http://test.localhost/api.php?timeout=${+res.timeout + 1}`)),
tap(res => console.log('Second result', res)),
concatMap((res: { timeout: number }) => this.http.get(`http://test.localhost/api.php?timeout=${+res.timeout + 3}`)),
tap(res => console.log('Third result', res)),
)
.subscribe(res => console.log('Latest result', res));
Console output:
First result {timeout: "1"}
Second result {timeout: "2"}
Third result {timeout: "5"}
Latest result {timeout: "5"}
The first output is logged after 1 second, the second one after 2 seconds, the third - and latest - is logged after 5 seconds. As you can see in the pipe every request after the first one depends on the response of the previous request.
Parallel
Requests may be independent and you need to execute all requests before your application can go on. Think of something like querying your application settings (e.g. the default theme, language, ...), the current user and the current view data (e.g. the current post you're viewing) - none of these things depend on each other, but you need all of them in order for your application to even show up.
It would be a waste of resources to query these things one at a time - we could just query all of them and work with the combined results.
For making our requests parallel we're going to use forkJoin
. Requests within forkJoin
are requested in parallel, the Observable itself emits when all requests are done:
import {forkJoin} from "rxjs";
import {tap} from "rxjs/operators";
forkJoin([
this.http.get('api.php?timeout=1').pipe(tap(res => console.log(res)),
this.http.get('api.php?timeout=5').pipe(tap(res => console.log(res)),
this.http.get('api.php?timeout=3').pipe(tap(res => console.log(res)),
]).subscribe(allResults => console.log(allResults));
Taking a look at the console will show you the following result:
{timeout: "1"}
{timeout: "3"}
{timeout: "5"}
(3) [{…}, {…}, {…}]
0: {timeout: "1"}
1: {timeout: "5"}
2: {timeout: "3"}
The first three lines are from the console.log
within our tap
. You can clearly see that, even if the 3-second timeout request is listed after the 5-second request, the 3-second timeout is logged before the 5-second timeout request - since our requests are executed individually and in parallel.
After all our Observables have emitted a value (or, in other words, after the 5-second request finishes) forkJoin
will emit a value, ultimately leading to our subscribe
to be called.
In case of the example mentioned above we can make use of this by doing something like: