I don’t know if it’s just me, but when I started to learn Angular and RxJS, it was hard for me to figure out which operator to use to do a certain thing. The switchMap, mergeMap, exhaustMap and concatMap all seem to be similar and the documentation uses very generic examples – transforming ‘1’ into ‘1A’ and it’s hard to translate it to the real-life use cases.
This is my try to explain those 4 operators, which seem to do the same action, but they have some differences and they should be used in different contexts.
Usually, we have an observable, which emits some events. We can use
map() operator to do some action for each such event and return its result, thus creating a stream of results.
events$.pipe( map(event => processTheEventSomehow(event)) )
There is however a case when the action we want to do, returns an observable itself. You can think about making an API call or so. If we use
map() operator, we will end up with something like this:
where we wanted just an ordinary, non-nested observable.
This is where transformation operators come in, such as
exhaustMap. On the glance, all examples below will map emitted events to API calls. However, depending on the result we want to achieve, we will use a different operator.
The best example for this operator is a typeahead search. There is an input field, and as soon as the user starts typing, suggestions are displayed. For each letter, we want to present more and more accurate suggestions.
When the user typed in another letter, we no longer need to know what is the current API request’s result – the new one with one more letter will yield way better suggestions. This is exactly how switchMap operator works. As soon as the new event appears, if the inner observable hasn’t completed yet, it is canceled and the new one is created.
You can see it in the demo below – if you write a letter, a request will be sent to receive a suggestion from the API. If you write the letters fast enough, the subsequent requests to the API will be canceled, and only the last one will complete, returning the suggestions for the most recent user input.
Sidenote: To be clear – don’t use this code on production as-is. Making an API call immediately after each keypress may not be a good idea. I did it such way because I wanted those examples to be as simple and as understandable as possible.
This operator is very similar to the
switchMap, but as in the previous example, it was the last event’s result that was the most important, for
exhaustMap it’s the first.
Think of a refresh button. If you push it once and the data is being fetched, there is no need to make another request until the first one finishes. In
exhaustMap, the subsequent events are skipped until the inner observable completes.
exhaustMap felt natural for reading operations,
mergeMap is quite the opposite. We can skip reading some of the results and focus on the most recent ones, but when we save data, we want it all stored. This is what this operator does – there will be an inner observable created for all of the events from the outer observable.
The simplest example I could think of is saving clicks user made to some backend service. To do that, we transform DOM events stream, making for each one an API call to store it in our service.
What may be a problem with
mergeMap? It’s when the order of data is important. When you run multiple requests in a short time, so they run simultaneously, no one can guarantee, that they will arrive in the order you intended. For such case, it may be wiser to use a
concatMap operator, which won’t process next event until the inner observable for the previous one hasn’t completed yet.
As we can see, the usual answer “it depends” can be used in more places than we would think. I hope, that I clarified some of the confusion and now you can make more conscious decisions about operator usage.
If it helped you, or if something wasn’t clear, leave a comment below!