Multiple API calls with same request
Solution 1
There are essentially 2 options:
-
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
-
PROS: you know you will have all your data loaded inside you
-
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}`);
}
}
Related videos on Youtube
Thalapathy
Updated on May 26, 2022Comments
-
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 almost 4 yearsNo. And posting a comment requires 15 characters.
-
-
Lievno almost 4 yearsWhen 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 almost 4 yearsThat 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 almost 4 yearsI 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 almost 4 yearsNo 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 almost 4 yearsuse concatMap, so as not to increase server capacity
-
DJ House almost 4 yearsYes,
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 themergeMap
for aconcatMap
.