Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way for multiple HTTP Request in Angular

I am trying to send 2 HTTP requests one by one; if the first one is succeeds, send the second one, if not display the corresponding error message regarding to the first request.

I am planning to use something like that, but not sure if it is the best option for this scenario:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: 'app/app.component.html'
})
export class AppComponent {
  loadedCharacter: {};
  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http.get('/api/people/1').subscribe(character => {
      this.http.get(character.homeworld).subscribe(homeworld => {
        character.homeworld = homeworld;
        this.loadedCharacter = character;
      });
    });
  }
}

I have different requests e.g. PUT and CREATE also using this approach. I know there are other ways e.g. forkjoin, mergemap, but if this one solves my problem seems to be more readable. Any idea?

like image 286
Prag Dopravy Avatar asked Dec 10 '22 00:12

Prag Dopravy


2 Answers

First of all, your code works and that's great - you can leave it as is and everything will be fine.

On the other hand, there is a way for multiple improvements that will help you and your colleagues in future:

  1. try to move http-related logic to the service instead of calling http in the components - this will help you to split the code into view-related logic and the business/fetching/transformation-related one.
  2. try to avoid nested subscribes - not only you ignore the mighty power of Observables but also tie the code to a certain flow without an ability to reuse these lines somewhere in the application. Returning the Observable might help you with "sharing" the results of the request or transforming it in some way.
  3. flatMap/mergeMap, concatMap and switchMap work in a different way, providing you an ability to control the behaviour the way you want. Though, for http.get() they work almost similar, it's a good idea to start learning those combining operators as soon as possible.
  4. think about how you'll handle the errors in this case - what will happen if your first call will result an error? Observables have a powerful mechanism of dealing with them, while .subscribe allows you to handle an error only in one way.

An example using the switchMap:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: 'app/app.component.html'
})
export class AppComponent {
  loadedCharacter: {};
  constructor(private http: HttpClient) {}

  ngOnInit() {
    const character$ = this.http.get('/api/people/1').pipe(
      tap(character => this.characterWithoutHomeworld = character), // setting some "in-between" variable
      switchMap(character => {
        return this.http.get(character.homeworld).pipe(
            map(homeworld => {
                    return {
                        ...character,
                        homeworld: homeworld
                    }
                }
            )
        )
      }),
      catchError(errorForFirstOrSecondCall => {
        console.error('An error occurred: ', errorForFirstOrSecondCall);
        // if you want to handle this error and return some empty data use:
        // return of({});
        
        // otherwise: 
        throw new Error('Error: ' + errorForFirstOrSecondCall.message);
      })
);

    // you can either store this variable as `this.character$` or immediately subscribe to it like:
    character$.subscribe(loadedCharacter => {
        this.loadedCharacter = loadedCharacter;
    }, errorForFirstOrSecondCall => {
       console.error('An error occurred: ', errorForFirstOrSecondCall);
    })
  }
}


like image 164
Yevhenii Dovhaniuk Avatar answered Dec 30 '22 20:12

Yevhenii Dovhaniuk


2 nested subscriptions are never a way to go. I recommend this approach:

this.http.get('/api/people/1').pipe(
  switchMap(character => this.http.get(character.homeworld).pipe(
    map(homeworld => ({ ...character, homeworld })),
  )),
).subscribe(character => this.loadedCharacter = character);

Edit: For your university

this.http.get('/api/people/1').pipe(
  switchMap(character => this.http.get(character.university).pipe(
    map(university => ({ ...character, university})),
  )),
).subscribe(character => this.loadedCharacter = character);

Or even chain university and homeworld requests

this.http.get('/api/people/1').pipe(
  switchMap(character => this.http.get(character.homeworld).pipe(
    map(homeworld => ({ ...character, homeworld })),
    // catchError(err => of({ ...character, homeworld: dummyHomeworld })),
  )),
  switchMap(character => this.http.get(character.university).pipe(
    map(university => ({ ...character, university})),
  )),
).subscribe(character => this.loadedCharacter = character);
like image 42
MoxxiManagarm Avatar answered Dec 30 '22 21:12

MoxxiManagarm