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";
interval
creates an observable that emits values incrementing by 1 every specified time interval.
For example:
const obs1$ = interval(500)
obs1$.subscribe(console.log);
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));
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);
The result will be 0, 2, 4, 6, 8. Exactly the same like
[0, 1, 2, 3, 4].map(x => x * 2).
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)
);
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)
);
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);
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);
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);
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)