App Development RxJS

RxJS – Part 2: Higher-Order Mapping

In this blog, we’ll discuss ‘What is the importance of using RxJS Higher-Order Mapping’. RxJS is a library for reactive programming for making asynchronous network calls easier using observables and several operators. In Reactive Programming, RxJS Higher-Order Mapping Operators play a very crucial role in solving many operations.

The most commonly used RxJS higher-order mapping operators are switchMap, mergeMap, concatMap and exhaustMap. You might be in doubt where to use these operators. It will be clear once you have gone through the different operators explained in this blog, where the functionality for each of them is explained. However, use it with utmost care. It will result in unintended complex output if we used it wrongly.

Higher-Order Mapping Operators

The most commonly used higher-order mapping operators are concatMap, switchMap, mergeMap and exhaustMap. In our examples we have used marble diagrams which you can find in detail from https://rxmarbles.com/. For RxJS higher order mapping working example in Angular, visit https://stackblitz.com/edit/RxJS-higher-order-mapping-demo. Let’s see the different situations where these operators came in to use:

ConcatMap

In the above-mentioned example of nested subscriptions, we might be clicking the apply button multiply times. However these operations are done in parallel, the backend may not handle these sequentially. So to ensure sequential handling of operations, observable concatenation is required. Let’s understand more about this with the help of a marble diagram.

Here there are two observables. First observable with values a,b and second observable with values x,y. The vertical bar after b in the first observable shows its end. Here both observables are fed into concat() function. Concat function first subscribes to the first observable and starts subscribing to the second observable only after completing the first observable. This is what is the key point of observable concatenation. To ensure the sequential operation of observables, we have to concatenate the multiple observables. ConcatMap always ensures the sequential flow of Http requests.

If explained with the help of the marble diagram shown above, the concatMap will take the first value(here ‘a’) then convert it into Http observable or inner observable and subscribe the inner observable to the result observable. Only after completing this Http request, the concatMap will consider the second value(here ‘b’). The concatMap will consider the new observable only after completing the previous observables. So to ensure the sequential flow of Http requests we use concatMap operator. The code snippet below shows how to use concatMap() operator.

fromEvent(applyBtn, 'click')

    .pipe(concatMap(click => apply()))

    .subscribe(response => {

        //response of apply()

    });

MergeMap

Sometimes there arise cases to run requests parallel without waiting for an observable to complete. Unlike concat, merge will not wait to complete an observable to subscribe to another observable. Let’s understand more about this with the help of a marble diagram.

Here from the marble diagram, we can see that the values of the source observables appear in the result immediately after they are subscribed. Here the result will complete after all the merged observables are completed. Now let’s see more about the mergeMap operator with another marble diagram and also how this mergeMap works with the higher-order observables.

Here each value is mapped to inner observable and this is subscribed to by the mergeMap. When coming to new values in the inner observable, they immediately reflect in the result observable. 

Here we do not have to wait for an inner observable to complete to subscribe to the next inner observable. This can be done in parallel. That is, multiple inner observables can overlap as shown in red in the above diagram. So multiple Http requests can be run in parallel as shown in the above diagram. The code snippet below shows how to use mergeMap() operator.

fromEvent(applyBtn, 'click')

    .pipe(mergeMap(click => apply()))

    .subscribe(response => {

        //response of apply()

    });


SwitchMap

This is another type of flattening technique that we commonly used. Here when a new observable starts emitting values like a new user click, we will unsubscribe from the previous observable. That is when taking a user click as an example when a new stream or event occurs, the ongoing stream will get unsubscribed. Let’s understand more about this with help from another marble diagram.

Here the values 1,3,5 will get converted to inner observables by using a mapping function. These mapped inner observables are fed into a switchMap operator. Here the values of the inner observables immediately appear in the result until a new value appears before completing an ongoing observable. Here in the diagram, we can see that in the red box before completing 30-30-30, when a new value 5 appears, it gets unsubscribed from the inner observable. And since the switchMap unsubscribed the 30-30-30, it will not appear in the result.

So switchMap will make sure that the unused resources will be released by unsubscribing the unused observables. A very common use case for this switchMap is searching. Suppose a user might be searching for the word “Hello”. The possible search string for the word is shown below.

However, instead of sending all these values to the backend, let’s wait for a user to send a stabilized input by using the debouncedTime operator. However, we can handle this more easily by using a switchMap operator by canceling the previous search requests and this way we can release unused server resources. The code snippet below shows how to use switchMap() operator.

fromEvent(applyBtn, 'click')

    .pipe(switchMap(click => apply()))

    .subscribe(response => {

        //response of apply()

    });

ExhaustMap

Unlike switchMap, sometimes we want to cancel the new requests until the previous value gets completed. For example, suppose a user clicks an apply button and an Http request is in progress. But when the user clicks the apply button multiple times, we don’t want to handle all these requests in sequence, unlike concatMap. Instead, we have to cancel the unwanted new click requests and the exhaustMap is used to handle such situations. Now let’s see more about this with the help of a marble diagram.

Here the source observable emits the value 1 and it gets converted to inner observable 10-10-10 and since no new value is emitted in between it gets immediately reflected in the output. Then a new value 3 gets emitted and gets converted to 30-30-30 inner observable. When processing that request a new value 5 gets emitted and this new request is canceled by the exhaustMap and completes the 30-30-30 request and reflects in the output. So the exhaustMap will reduce the number of Http requests going out from the browser. The code snippet below shows how to use exhaustMap() operator.

fromEvent(applyBtn, 'click')

    .pipe(exhaustMap(click => apply()))

    .subscribe(response => {

        //response of apply()

    });

Conclusion

In this blog, we learned higher-order observables and the higher-order operators we used to flatten them instead of nested subscribes. Even though all these are higher-order operators, these are different in many ways in their behavior and it is very important to understand the correct scenario to apply these operators to avoid nasty bugs.  

If we want to complete the requests in a sequence, concatMap is the right choice. If we want to process the requests in parallel, mergeMap is the best choice. If we want to cancel a request when a new request comes, switchMap can be used and if we want to cancel a new request when one is ongoing, exhaustMap is the best one.

This is only a small part of RxJS. To know and learn more about RxJS, you can visit https://RxJS-dev.firebaseapp.com/guide/overview.