How can I chain HTTP calls in Angular 2?
HttpAngularHttp Problem Overview
I'm new to Angular 2 and HTTP Observables. I have a component which calls an HTTP service and returns an Observable. Then I subscribe to that Observable and it works fine.
Now, I want, in that component, after calling the first HTTP service, if the call was successful, to call another HTTP service and return that Observable. So, if the first call is not successful the component returns that Observable, opposite it returns Observable of the second call.
What is the best way to chain HTTP calls? Is there an elegant way, for example like monads?
Http Solutions
Solution 1 - Http
You can do this using the mergeMap
operator.
Angular 4.3+ (using HttpClientModule
) and RxJS 6+
import { mergeMap } from 'rxjs/operators';
this.http.get('./customer.json').pipe(
mergeMap(customer => this.http.get(customer.contractUrl))
).subscribe(res => this.contract = res);
Angular < 4.3 (using HttpModule
) and RxJS < 5.5
Import the operators map
and mergeMap
, then you can chain two calls as follows:
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
this.http.get('./customer.json')
.map((res: Response) => res.json())
.mergeMap(customer => this.http.get(customer.contractUrl))
.map((res: Response) => res.json())
.subscribe(res => this.contract = res);
Some more details here: http://www.syntaxsuccess.com/viewarticle/angular-2.0-and-http
More information about the mergeMap operator can be found here
Solution 2 - Http
Using rxjs to do the job is a pretty good solution. Is it easy to read? I don't know.
An alternative way to do this and more readable (in my opinion) is to use await/async.
Example:
async getContrat(){
// Get the customer
const customer = await this.http.get('./customer.json').toPromise();
// Get the contract from the URL
const contract = await this.http.get(customer.contractUrl).toPromise();
return contract; // You can return what you want here
}
Then call it :)
this.myService.getContrat().then( (contract) => {
// do what you want
});
Or in an async function:
const contract = await this.myService.getContrat();
You can also use try/catch to manage the error:
let customer;
try {
customer = await this.http.get('./customer.json').toPromise();
}catch(err){
console.log('Something went wrong will trying to get customer');
throw err; // Propagate the error
//customer = {}; // It's a possible case
}
Solution 3 - Http
You could also chain Promises. Per this example
<html>
<head>
<meta charset="UTF-8">
<title>Chaining Promises</title>
</head>
<body>
<script>
const posts = [
{ title: 'I love JavaScript', author: 'Wes Bos', id: 1 },
{ title: 'CSS!', author: 'Chris Coyier', id: 2 },
{ title: 'Dev tools tricks', author: 'Addy Osmani', id: 3 },
];
const authors = [
{ name: 'Wes Bos', twitter: '@wesbos', bio: 'Canadian Developer' },
{ name: 'Chris Coyier', twitter: '@chriscoyier', bio: 'CSS Tricks and Codepen' },
{ name: 'Addy Osmani', twitter: '@addyosmani', bio: 'Googler'},
];
function getPostById(id) {
// Create a new promise
return new Promise((resolve, reject) => {
// Using a settimeout to mimic a database/HTTP request
setTimeout(() => {
// Find the post we want
const post = posts.find(post => post.id == id);
if (post) {
resolve(post) // Send the post back
} else {
reject(Error('No Post Was Found!'));
}
},200);
});
}
function hydrateAuthor(post) {
// Create a new promise
return new Promise((resolve, reject) => {
// Using a settimeout to mimic a database/http request
setTimeout(() => {
// Find the author
const authorDetails = authors.find(person => person.name === post.author);
if (authorDetails) {
// "Hydrate" the post object with the author object
post.author = authorDetails;
resolve(post);
}
else {
reject(Error('Can not find the author'));
}
},200);
});
}
getPostById(4)
.then(post => {
return hydrateAuthor(post);
})
.then(post => {
console.log(post);
})
.catch(err => {
console.error(err);
});
</script>
</body>
</html>