74

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
zlaja
  • 1,351
  • 3
  • 13
  • 23

2 Answers2

114

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

frido
  • 13,065
  • 5
  • 42
  • 56
TGH
  • 38,769
  • 12
  • 102
  • 135
26

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
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
xrobert35
  • 2,488
  • 1
  • 16
  • 15
  • 4
    Spent a long time following examples of mergeMap and forkJoin to try to make an HTTP call for every element in the response of another HTTP call in Angular 8, but after discussing it with my peers they vastly preferred the readability of a simple async like this. +1 for not going down a rabbit hole of overengineering trying to use reactive functions just for the sake of using reactive functions. – pumpkinthehead Nov 19 '19 at 15:32
  • 1
    This is way simpler than rxjs way of doing this. I prefer this. – Pramesh Bajracharya Jul 28 '20 at 07:27