Enhancing Search with switchMap
There is a problem with our previous implementation of incremental search.
What if the server, for some reason, takes a very long time to respond to a particular query? If we use mergeMap
, we run the risk of getting results back from the server in the wrong order. Let's illustrate this with an example.
A Quick Example
Consider a situation where we first type in the letters ABC
, and suppose the string ABC
is actually a special string where it will take the server a few extra seconds to reply.
Meanwhile, after we paused for a bit (more than the debounce time), we decide to type in another letter (the letter X) and our app sends a request to the server for the string ABCX
. Since ABCX
is not considered a special string, the server replies very quickly and our app sets the suggestions for ABCX
.
A few seconds later, however, the server finally replies with the response for the ABC
string, and our app receives that response and sets the search suggestions for ABC
, overwriting the suggestions for the ABCX
string, even though the request for that actually came afterwards.
Here is a simple diagram to illustrate the issue:
You can see that A2 arrives after B2 even though the A1 request began first. This will end up showing the wrong results to the user. "If the last input in the search was ABCX
why am I seeing the results for ABC
?" the user might think. To get around this problem we need to replace mergeMap
with switchMap
.
What is switchMap
?
switchMap
?switchMap
is very similar to mergeMap
, but with a very important distinction. Any events to be merged into the trunk stream are ignored if a new event comes in. Here is a marble diagram showing the behavior of switchMap
:
In short, every time an event comes down the stream, mergeMap
will subscribe to (and invoke) a new observable without unsubscribing from any other observable created by a previous event. switchMap
on the other hand will automatically unsubscribe from any previous observable when a new event comes down the stream.
In the diagram above, the round "marbles" represent events in the originating stream. In the resulting stream, "diamonds" mark the creation (and subscription) of an inner observable (that is eventually merged onto the trunk stream) and "squares" represent values emitted from that same inner observable.
Just like mergeMap
, the red marble gets replaced with a red diamond and a subsequent red square. The interaction between the green and blue marble events are more interesting. Note that the green marble gets mapped to a green diamond immediately. And if enough time had passed, a green square would be pushed into the trunk stream but we do not see that here.
Before the green square event is able to happen, a blue marble comes through and gets mapped to a blue diamond. What happened is that the green square is now ignored and do not get merged back into the trunk stream. The behavior of switchMap
can be likened to a mergeMap
that "switches" to the more immediate incoming event and ignores all previously created event streams.
In our case, because the blue marble event happened very quickly after the green marble, we "switched" over to focus on dealing with the blue marble instead. This behavior is what will prevent the problem we described above.
If we apply switchMap
to the above example, the response for ABC
would be ignored and the suggestions for ABCX
would remain.
Enhanced Search with switchMap
switchMap
Here is the revised component using switchMap
instead of mergeMap
.
app/app.component.ts
This implementation of incremental search with switchMap
is more robust than the one we saw on the previous page with mergeMap
. The suggestions that the user sees will always eventually reflect the last thing the user typed. Thanks to this, we can guarantee a great user experience regardless of how the server responds.
Further Resources
Last updated