Understanding switchMap and forkJoin operators in RxJS with TypeScript

Nov 20, 2020 · 5 minutes read · [RxJS] [TypeScript]

RxJS is a library with more than 22 Million downloads per week. It’s widely used in the JavaScript world and it has great support of TypeScript. There’s no need to say much about it to understand how important is today.

Some days ago I saw the following question in a developer forum:

Is there a way to handle a list of Observables and get the final result of them once they’re completed?

Right after reading that question, I started to think in a reactive approach in time to come up with a “pattern” that can be used in various scenarios.

The Problem

Let’s suppose you’re building an application that needs to handle a series of asynchronous calls to display the users and their respective addresses.

The Problem - User vs Address

However, in a real-world scenario, the system can provide a RESTful service to get the list of the Users(ie. getUsers()).

Data Example: Users

Then, you’ll need to perform another HTTP call to get the address for each of the previous users(ie. getAddress(userId)).

Data Example: Address by User Identifier

Let’s solve this problem in a Reactive Approach using RxJS operators and TypeScript.

Define the Data Model

Let’s rely on TypeScript interfaces and powerful static typing to have the model ready.

// user.ts
export interface User {
  id: number;
  name: string;
  address?: Address;
}

The User interface defines the address attribute as optional and that means it can be undefined if the user doesn’t “contain” a valid address value yet.

// address.ts
export interface Address {
  country: string;
  state: string;
  city: string;
  street: string;
  zipCode: number;
}

The Address interface displays a set of attributes that represents a complete address.

The Data Example

To make testing easier, let’s define a dataset for the entities we defined already in the model.

// index.t

import { Address, User } from "./address";
import { User } from "./user";

const users: User[] = [
  {
    id: 0,
    name: "Donald Mayfield"
  },
  {
    id: 1,
    name: "Jill J. Fritz"
  },
  {
    id: 2,
    name: "Terry Buttram"
  }
];

const address: Address[] = [
  {
    street: "2180 BELLFLOWER",
    country: "USA",
    state: "AL",
    city: "Madison",
    zipCode: 35064
  },
  {
    street: "845 ODOM ROAD, SUITE 200",
    country: "USA",
    state: "CA",
    city: "Los Angeles",
    zipCode: 90720
  },
  {
    street: "9025 QUEENS BLVD",
    country: "USA",
    state: "NY",
    city: "Queens",
    zipCode: 11355
  }
];

Looks like we’ll need a function to get an Address given a User id.

// index.ts
const getAddress  = (userId: number) => of(address[userId]);

The previous function getAddress will return an Address as an Observable: Observable<Address>. This is possible with the use of the of operator from RxJS, which is a “Creation Operator” that converts the arguments(an Address object) to an observable sequence.

Processing the Data as Observables

Since we have the data model defined, it is time to create some variables that allow to “contain” the set of users and addresses as Observables:

// index.ts

let users$: Observable<User[]>;
let address$: Observable<Address[]>;

Next, let’s assign the appropriate value to the users$variable:

users$ = of(users);

Again, the previous line is using the of operator to create an observable from the users array.

In the real world, the “users” data will come from a RESTful endpoint, a JSON file, or any other function that can perform an asynchronous call. You may expect an Observable as a result of that function or you can create it using an RxJS Operator.

Using switchMap and forkJoin Operators

Now It is time to process the set of Users and perform the getAddress(id) call for every User:

address$ = users$.pipe(
  switchMap((users: User[]) => {
    // Iterate the 'users' array and perform a call to 'getAddress' 
    users.forEach(user => {
      const address = getAddress(user.id);
      // ...
    });
  })
);

What is happening here?

  • users$.pipe(). This function call provides a readable way to use RxJS operators together(switchMap in this example).
  • switchMap() operator comes first and allows to process the data(User[]) which comes from the observable(Observable<User[]>). Also, it allows returning an Observable as a result of applying getAddress() call for every user.

However, we’re not doing anything useful yet from getAddress() results. Let’s create an array to “store” them and return a new Observable:

address$ = users$.pipe(
  switchMap((users: User[]) => {
    // Create and initialize the array
    const addressArray$: Observable<Address>[] = [];
    // Iterate over 'users' array
    users.forEach(user => {
      const address$: Observable<Address> = getAddress(user.id);
      addressArray$.push(address$);
    });
    // [Observable<Address>, Observable<Address>, ....., Observable<Address>]
    return forkJoin(addressArray$);
  })
);

The following operations are performed inside switchMap call:

  • An empty array is created in order to “contain” the results of every getAddress call. This array will expect to contain a set of Observable<Address>.
  • users.forEach iterates over the users array to add every Observable<Address> to the previous addressArray$ variable.
  • forkJoin(addressArray$). We got an array of Observables at this point and the forkJoin operator will do the following:
    • Accept the array of Observables as an input(Observable<Address>[])
    • It will wait for every Observable(from the array) to complete and then combine the last values they emitted.
    • Finally it will return an array of Address[] as a new Observable: Observable<Address[]>

As a final step, you’ll need to subscribe as follows.

// Subscription
address$.subscribe((address: Address[]) => {
  console.log({ address }); // Prints the array of addresses: Address[]
});

The result would be the following JSON object:

{
  "address": [
  {
    "street": "2180 BELLFLOWER",
    "country": "USA",
    "state": "AL",
    "city": "Madison",
    "zipCode": 35064
  },
  {
    "street": "845 ODOM ROAD, SUITE 200",
    "country": "USA",
    "state": "CA",
    "city": "Los Angeles",
    "zipCode": 90720
  },
  {
    "street": "9025 QUEENS BLVD",
    "country": "USA",
    "state": "NY",
    "city": "Queens",
    "zipCode": 11355
  }
]
}

Demo Project

Find the complete project running in StackBlitz. Don’t forget to open the browser’s console to see the results.


You can follow me on Twitter and GitHub to see more about my work.

tweet Share