DEV Community

Cover image for Mappers in RxJS
Arthur Groupp
Arthur Groupp

Posted on

Mappers in RxJS

RxJS became one of the main game changers in the world of modern front-end development. Becoming the foundation for multiple technologies and frameworks, it brings us to the necessity of deep knowledge and understanding how it works and how to use it.

In this article I’d like to discuss RxJS mappers. One of the most usual task when we work with data is transformation. Mapper operators become handy when we need to transform every value one by one.

Let’s start.

Initial setup

You will need to prepare any environment where you can write Typescript code and see the results. Feel free to do it in any of your favorite way, for example, I prefer to use Stackblitz for my experiments. Signup/in and create empty typescript project.

We need to create the first observable for future use. Let's create it with the help of interval function. First, import it:

import { interval } from "rxjs";
Enter fullscreen mode Exit fullscreen mode

interval creates an observable that emits values incrementing by 1 every specified time interval.

For example:

const obs1$ = interval(500)
obs1$.subscribe(console.log);
Enter fullscreen mode Exit fullscreen mode

Will print in the console 0, 1, 2, 3, 4, … every half a second.

If we want to do any modifications of emitted data before subscribing, we must use method pipe of observable object. It accepts operator functions as its arguments.

Why do we need, why not to do it in subscriber function? Using pipe makes our observables much more reusable and subscriptions cleaner.

Let’s limit our emitted values to 5. To achieve this we need the operator takeWhile:

const obs1$ = interval(500).pipe(takeWhile(x => x < 5));
Enter fullscreen mode Exit fullscreen mode

The code is pretty self-explanatory, it creates observable that emit values until the value is less than 5. Due to autoincrement of the values emitted by interval we will get exactly 5 values.

Map

map is the basic transformation operator and we are going to start with it. It looks very familiar from the Array type and works the same. As argument it accepts the function that has the current value as it’s argument and must return another value.

Pretty simple:

obs1$.pipe(
  map(x => x * 2)
)
.subscribe(console.log);
Enter fullscreen mode Exit fullscreen mode

The result will be 0, 2, 4, 6, 8. Exactly the same like

[0, 1, 2, 3, 4].map(x => x * 2).
Enter fullscreen mode Exit fullscreen mode

The difference is that we have this behavior time plane. If you look into console, you will see that you are getting these values not all together, but one by one with interval of 0.5 seconds according to its emitting.

I want to pay your attention on fact that the projection function must return scalar.

What to do if we need to return another observable? For this case, we have switchMap.

Initial setup modification

Let’s apply our new knowledge and modify our obs1$:

const obs1$ = interval(500).pipe(
  takeWhile(x => x < 5),
  map(x => x + 1)
);
Enter fullscreen mode Exit fullscreen mode

Now obs1$ will emit 1, 2, 3, 4, 5

Let’s add second observable

const obs2$ = interval(200).pipe(
  takeWhile(x => x < 5),
  map(x => x + 10)
);
Enter fullscreen mode Exit fullscreen mode

obs2$ will emit 10, 11, 12, 13, 14

SwitchMap

switchMap takes every value of observable created pipe and returns the observable returned by its projection function. Let’s make it out on an example.

obs1$.pipe(
  switchMap(() => obs2$)
)
.subscribe(console.log);
Enter fullscreen mode Exit fullscreen mode

The result will be 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 12, 13, 14

Why? This is very important part of understanding how switchMap works. So, what’s going on? Every time we have value from obs1$ our subscription subscribes to obs2$. obs1$ has interval 500 and obs2$ has interval 200. It means that we manage to get 2 values of obs2$ between values of obs1$. And when next value of obs1$ comes it unsubscribes us from obs2$ and subscribes again. That’s why we have 10, 11 five times and 12, 13, 14 only once when obs1$ becomes completed.

switchMap is very useful when we need to utilize the value of first observable in the second one. For example, we have Angular router that has parameter that we need to send to API to get correct response. This construction is perfect for this task. We can put pipe on paramMap or queryParamMap and “switch” to new observable that will be returned by HttpClient.

But what if we want to get all obs2$ values? For this case, we have mergeMap.

MergeMap

mergeMap does exactly the same functionality like switchMap except that it doesn’t unsubscribe when new value arrives. Let’s see it:

obs1$.pipe(
  mergeMap(() => obs2$)
)
.subscribe(console.log);
Enter fullscreen mode Exit fullscreen mode

Now we have 10, 11, 12, 10, 13, 11, 14, 12, 10, 13, 11, 14, 12, 10, 13, 11, 14, 12, 10, 13, 11, 14, 12, 13, 14

Total 25 values, 5 times 5, all of them.

Good example of using mergeMap is NgRx effects. When we create effect, we mergeMap action with the related service method.

The values we get are disordered. We get them in order of emit and when new value from obs1$ come it starts new cycle of emitting values of obs2$. Thanks to this, we get part of values from previous cycle and part from new started. What if the values order matters for us? For this case, we have concatMap.

ConcatMap

concatMap works the same way like mergeMap except that fact that it emit the values in order. Example:

obs1$.pipe(
  concatMap(() => obs2$)
)
.subscribe(console.log);
Enter fullscreen mode Exit fullscreen mode

The results will be 10, 11, 12, 13, 14, 10, 11, 12, 13, 14, 10, 11, 12, 13, 14, 10, 11, 12, 13, 14, 10, 11, 12, 13, 14

Exactly what we wanted.

Conclusion

Mappers are the most used operators in data manipulations. Very important to understand principles of its work to pick the correct one in your future tasks. When you plan the usage of mappers, first ask yourself “does my projection function return observable or scalar?” This can become the starting point to choose the correct operator to use.

Photo by National Cancer Institute on Unsplash

Top comments (0)