Understanding flatMap, mergeMap and toArray operators in RxJS with TypeScript
Tags: rxjs, typescript, webdev
Published: 2020-12-29 20:40:10

RxJS is a popular library nowadays and it’s used in a wide range of applications. And it even becomes part of popular frameworks like Angular.

It’s not unknown that the library comes with many powerful functions that can be used together to solve complex problems with only a few lines of code. A couple of days ago, I saw the following question on a developer website:

> How can you use _flatMap_ operator to process a list of data as an _Observable_ and then process the data one by one as _Observables_ too?

Of course, there could be several ways to solve this problem. However, we will address a solution with _reactive thinking_ to come up with a solution that could be used in many similar problems.

## 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.

![user-addresses](//images.ctfassets.net/zojzzdop0fzx/FcsaVGBcN2sIZMpAKWG2f/88a8f04b81f060fbfc3490e35ca71973/user-addresses.png)

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

![users](//images.ctfassets.net/zojzzdop0fzx/2r74mU0H0CQ32UyS7UXQWJ/1a54a6d219430e4103f4cafbac5a098f/users.png)

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

![address-by-user-id](//images.ctfassets.net/zojzzdop0fzx/5tZiQnrur0d17aHjZDN0PV/d2731c203b60e72c83a1fd97929eb014/address-by-user-id.png)

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.

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

The _User_ interface defines the `address` attribute as optional. That means it can be `undefined` if the user doesn't "contain" a valid address value yet.

```ts
// 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.

```ts
// 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 identifier.

```ts
// index.ts
const getAddress = (userId: number): Observable<Address> => 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.

In a real-world scenario, an asynchronous operation could take a variable time according to the request. So let's add a random delay for every request to "simulate" such a situation.

```ts
// index.ts
const getAddress = (userId: number): Observable<Address> =>
  of(address[userId]).pipe(
    tap(() => console.log(`getAddress(${userId})]. Started`)),
    delay(randomDelay()),
    tap(() => console.log(`getAddress(${userId}). Finished`))
  );
```

If the _function_ keyword is your friend in JavaScript, then you can write it as follows:

```ts
// index.ts
function getAddress(userId: number): Observable<Address> {
  return of(address[userId]).pipe(
    tap(() => console.log(`getAddress(${userId})]. Started`)),
    delay(randomDelay()),
    tap(() => console.log(`getAddress(${userId}). Finished`))
  );
}
```

## 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_ too:

```ts
// index.ts

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

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

```ts
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 flatMap and mergeMap Operators

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

```ts
// index.ts

address$ = users$.pipe(
  // "flat" the Array: User[] -> User
  flatMap(users => users)
);
```

Let's describe what's happening at this point.

* **users$.pipe()**. This function call provides a readable way to use RxJS operators together (`flatMap` operator goes first).
* **flatMap()** operator allows to process the data array(`User[]`) which comes from the observable (`Observable<User[]>`). Also, we can think that this operator allows transforming the users emitted by a single observable into many observables (one per user).

So far, we're able to process every user separately. However, we're not doing anything useful yet with the new observables. Let's "map" every user and perform a new request to get their addresses:

```ts
// index.ts

address$ = users$.pipe(
  flatMap(users => users),
  // process the User and return the Address as an Observable
  mergeMap(user => {
    const address$: Observable<Address> = getAddress(user.id);
    // You can even apply other operators to address$ here...
    return address$;
  })
);
```

* **mergeMap()** operator takes the user output from the previous operator(_flatMap_). The user identifier is read to perform a request to obtain the respective address. As you may understand, this operator maps each value to an Observable.

Some reasons why using the [mergeMap](https://rxjs-dev.firebaseapp.com/api/operators/mergeMap) operator in this solution:

* Since we have a set of users, it's **required** to get the Address of all of them. 
* The order of the final result doesn't matter.

As a final step, you'll need to _subscribe_ as follows.

```ts
// Subscription
address$.subscribe((address: Address[]) => {
  console.log({ address });
});
```

After running this code, you may see something similar to the following output:

```text
getAddress(0)]. Started
getAddress(1)]. Started
getAddress(2)]. Started
getAddress(1). Finished
{address: {…}}
getAddress(2). Finished
{address: {…}}
getAddress(0). Finished
{address: {…}}
```

Surprise! We are getting one object at a time. That is totally expected since we flattened the original array and then the `mergeMap` operator is emitting an observable at a time.

> We got an initial array as an input: how can we get an array as an output?

That would be one of your questions at this time. Let's change the code using the [toArray](https://rxjs-dev.firebaseapp.com/api/operators/toArray) operator:

```ts
// index.ts

address$ = users$.pipe(
  flatMap(users => users),
  mergeMap(user => {
    const address$: Observable<Address> = getAddress(user.id);
    return address$;
  }),
  // Get the values inside an array at the end
  toArray()
);
```

With the help of `toArray` operator, once the source Observable is done, it will return an array containing all the emitted values(In this case an array of `Address[]`).

The final result may be a JSON object as follows:

```json
{
  "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
  }
]
}
```

## flatMap vs mergeMap 
According to RxJS documentation, [flatMap](https://rxjs-dev.firebaseapp.com/api/operators/flatMap) is an alias of [mergeMap](https://rxjs-dev.firebaseapp.com/api/operators/mergeMap). You can verify it [here](https://github.com/ReactiveX/rxjs/blob/a4bcb84bbcd59abc6c4e811b57a15124757db2f2/src/internal/operators/mergeMap.ts#L179).

However, it's usual to find some questions about _flatMap_ operator and doubts about how it works. Also, some time ago, people around RxJS community [suggested renaming mergeMap](https://github.com/ReactiveX/rxjs/issues/3006) back to _flatMap_.

I personally like the idea of using a _flatMap_ operator because it resembles the [Array.prototype.flatMap()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap) function in JavaScript. I prefer to use it in that way.

## Alternative Solution

Would you like to see an alternative solution to this problem? Do not miss the 
[Understanding switchMap and forkJoin operators in RxJS](https://labs.thisdot.co/blog/understanding-switchmap-and-forkjoin-operators-in-rxjs-with-typescript) article.

## Demo Project

Find the complete project running in [StackBlitz](https://stackblitz.com/edit/rxjs-flatmap-mergemap-typescript). Don't forget to open the browser's console to see the results.

You can follow me on [Twitter](https://twitter.com/luixaviles) and [GitHub](https://github.com/luixaviles) to see more about my work.

---
*This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit [thisdotlabs.com](https://www.thisdotlabs.com).*

*This Dot Media is focused on creating an inclusive and educational web for all.  We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit [thisdot.co](https://www.thisdot.co).*