Multiple API calls with same request

10,463

Solution 1

There are essentially 2 options:

  1. Is the option you have where you use forkJoin() to combine all the observables into an array

    • PROS: you know you will have all your data loaded inside you subscribe()
    • CONS: you have to wait for every HTTP request to finish before forkJoin emits the value
    • Note: you can implement a nice helper function like @Prince recommended
  2. You can use mergeMap() for your IDs and react whenever one of the observables completes

    • PROS: you don't have to wait for every HTTP request to complete. You can react as they complete. Also, you can handle errors easier (so if one request fails you can still continue with the other requests).
    • CONS: you have to handle your emitted values in the .subscribe() a little differently

At the end of the day, you will need to decided which approach is best for you. There isn't anything wrong with the implementation you have. I noticed you are keeping your loadedCharacter as an object, so honestly option 2 may be good for your use case

Example code for Option 2. See this stackblitz for a small demo:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, from, Subject } from 'rxjs';
import { mergeMap, takeUntil } from 'rxjs/operators';

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

  ngOnInit() {
    /* ids of all the characters you want to load*/
    const characterIds = [1, 2];

    /* this will emit each id as a value */
    from(characterIds).pipe(

      /* merge each id to an Observable of the http get request */
      mergeMap(id => this.http.get(`https://swapi.co/api/people/${id}`)),

      /* cancel any pending requests when the component unloads.
        this will avoid any RxJS memory leaks */
      takeUntil(this.endSubs$)
    ).subscribe(
      character => {
        /* note that you will only receive 1 character at a time */
        console.log('received character', character);
        this.loadedCharacter[character.id] = character; // or whatever you want to do
      },
      err => console.log('Error loading a character', err),
      () => console.log('All character requests have finished')
    );
  }

  /* clean up our subscriptions when the component destroys */
  ngOnDestroy() {
    this.endSubs$.next();
    this.endSubs$.complete();
  }
}

EDIT: I added some RxJS cleanup code to avoid any memory leaks from the mergeMap. Any requests that are pending when this component unloads will be cancelled. Here is an example SO answer explaining Observable cleanup, and here is a relevant RxJS article on where to place your takeUntil().

Solution 2

You can extract the logic to create observable inside a common function and use it like below:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, forkJoin } from 'rxjs';

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

  ngOnInit() {
    let character1 = this.createHttpObservable('1');
    let character2 = this.createHttpObservable('2');

    forkJoin([character, character2]).subscribe(results => {
      // results[0] is our character
      // results[1] is our character2
    });
  }

 createHttpObservable(id:string) {
   const url = 'https://swapi.co/api/people/';
   return this.http.get(`url${id}`);
 }
}
Share:
10,463

Related videos on Youtube

Thalapathy
Author by

Thalapathy

Updated on May 26, 2022

Comments

  • Thalapathy
    Thalapathy almost 2 years

    I want to make multiple HTTP calls with the same endpoint passing different id. Is there a better way to handle this from UI. We cannot change the backend right now, is there a better way?

    import { Component } from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    import { Observable, forkJoin } from 'rxjs';
    
    @Component({
      selector: 'app-root',
      templateUrl: 'app/app.component.html'
    })
    export class AppComponent {
      loadedCharacter: {};
      constructor(private http: HttpClient) {}
    
      ngOnInit() {
        let character1 = this.http.get('https://swapi.co/api/people/1');
        let character2 = this.http.get('http://swapi.co/api/people/2');
    
        forkJoin([character, character2]).subscribe(results => {
          // results[0] is our character
          // results[1] is our character2
        });
      }
    }
    
    • Eldar
      Eldar almost 4 years
      No. And posting a comment requires 15 characters.
  • Lievno
    Lievno almost 4 years
    When you use mergeMap do not forget to unsubscribe and/or manage the numbers of concurrent inner observables to avoid memory leaks and you can use the second argument (resultSelector) to manipulate the data.
  • DJ House
    DJ House almost 4 years
    That is a good point. The OP didn't have any observable clean up so I didn't want to muddle up the code explanation, but I probably should just to remain thorough.
  • Lievno
    Lievno almost 4 years
    I hope I did not offend you, your response is great, it is more an advice/reminder for @Thalapathy to find the best solution without side effects.
  • DJ House
    DJ House almost 4 years
    No offense taken! I honestly just forgot. I get lazy sometimes when working with Observables that complete (like http.get() does). But it does make the answer better and will help others viewing it. Also there is still a chance the observable emits and completes after the component destroys which could create some weird bugs. So thank you for the reminder!
  • Chris
    Chris almost 4 years
    use concatMap, so as not to increase server capacity
  • DJ House
    DJ House almost 4 years
    Yes, concatMap() is another option. concatMap would execute the requests in order. Meaning it would wait for the first request to complete, and then send the next. If overworking the server is a concern, you can definitely switch the mergeMap for a concatMap.