askvity

How to Handle Multiple HTTP Requests in Angular?

Published in Angular HTTP 6 mins read

Handling multiple HTTP requests in Angular involves managing asynchronous operations, typically using RxJS observables provided by the HttpClient. A common requirement is to fetch data from several endpoints simultaneously or sequentially and process the results.

Strategies for Multiple HTTP Requests

There are several ways to handle multiple HTTP requests, depending on whether you need them to run in parallel, sequentially, or if one depends on the outcome of another.

Parallel Execution with forkJoin

When you need to make several requests at the same time and wait for all of them to complete before doing something, the forkJoin operator is the go-to solution in RxJS.

As stated in the reference, "A better approach is to use the forkJoin operator from RxJs and fetch the data in parallel. Using this approach, we can combine the responses from multiple HTTP requests. The requests are executed in parallel, and forkJoin will wait until they all emit."

This is ideal for scenarios like:

  • Loading configuration data from multiple sources on application startup.
  • Displaying data from various services on a single page (e.g., user details, order history, recent activity).

How forkJoin works:

  1. You pass an array or an object of observables to forkJoin.
  2. forkJoin subscribes to all of them simultaneously.
  3. It waits for each observable to emit its last value and complete.
  4. Once all observables have completed, forkJoin emits a single value:
    • If you passed an array, it emits an array of the last values in the same order.
    • If you passed an object, it emits an object with the same keys and their corresponding last values.
  5. If any of the source observables error, forkJoin will immediately error and not wait for the others.

Example using forkJoin:

import { forkJoin } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DataService {

  constructor(private http: HttpClient) {}

  getDataFromMultipleSources() {
    const users$ = this.http.get('/api/users');
    const products$ = this.http.get('/api/products');
    const orders$ = this.http.get('/api/orders');

    // Use forkJoin to execute requests in parallel
    return forkJoin({
      users: users$,
      products: products$,
      orders: orders$
    });
  }
}

You would then subscribe to the observable returned by getDataFromMultipleSources:

this.dataService.getDataFromMultipleSources().subscribe({
  next: results => {
    console.log('All data loaded:', results);
    const users = results.users;
    const products = results.products;
    const orders = results.orders;
    // Process the combined data
  },
  error: err => {
    console.error('Error loading data:', err);
    // Handle error
  }
});

Sequential Execution with concatMap or sequential

Sometimes, requests must happen one after another, perhaps because the second request depends on the result of the first.

  • concatMap: Maps each value from a source observable to an inner observable and subscribes to them in order, waiting for each inner observable to complete before moving to the next.
  • sequential (or similar patterns using from and concatMap): Can be used to process an array of tasks (like HTTP requests) one by one.

Example using concatMap (Request B depends on Request A):

import { of } from 'rxjs';
import { concatMap } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class UserService {

  constructor(private http: HttpClient) {}

  getUserAndDetails(userId: string) {
    // First, get user details
    return this.http.get(`/api/users/${userId}`).pipe(
      // Then, use the user details (e.g., department ID) to fetch related data
      concatMap((user: any) => this.http.get(`/api/departments/${user.departmentId}`))
    );
  }
}

Other Useful RxJS Operators

  • mergeMap (or flatMap): Similar to concatMap, but subscribes to inner observables immediately, allowing requests to run in parallel. Useful when order doesn't matter and you want to process results as they arrive.
  • switchMap: Cancels the previous inner observable whenever a new value arrives from the source. Useful for type-ahead search where you only care about the result of the last request.
  • parallel (pattern using mergeMap): While not a single operator like forkJoin, you can achieve parallel execution and process results as they come using mergeMap.

Comparing Operators

Here's a simple comparison of key operators for handling multiple requests:

Operator Execution Style When to Use Result Handling Error Handling
forkJoin Parallel Need ALL results from a fixed set of requests Emits one combined result (array/object) after ALL complete Errors immediately if any request errors
concatMap Sequential Requests must run in order (often with dependencies) Emits results one by one as requests complete Errors if any request errors, stopping the sequence
mergeMap Parallel (Async) Need results as they arrive, order doesn't matter Emits results one by one as requests complete (order may vary) Errors if any request errors
switchMap Parallel (Cancellable) Only care about the result of the latest request (e.g., search) Emits result of the latest request that completes Errors if the active request errors

Choosing the right operator depends entirely on the specific requirements of your application logic. forkJoin is excellent for collecting multiple independent results concurrently, while concatMap is necessary for dependent or strictly ordered operations.

Handling errors when using combining operators like forkJoin is crucial. You typically handle errors in the main subscribe block or use operators like catchError within the individual request pipes before passing them to forkJoin if you want some requests to fail without the whole operation failing.

Related Articles