Let’s dive deep into switchMap with an example, so it helps as to understand what is it and when to use it.
Say we need to implement functionality for traffic signal which consists of 4 roads(A, B, C, D) meeting at a junction. The signals of these roads can be controlled either manually or automatically. The signal change is asynchronous(Observable- data stream).
const roadSignals = Observable.from(['Road SignalA', 'Road SignalB', 'Road SignalC', 'Road SignalD' ]); roadSignals.subscribe((signal)=>console.log(signal));
Output:
Road SignalA Road SignalB Road SignalC Road SignalD
Here comes the next part. We have to allow vehicles to pass through the junction when the signal is ON. Let’s try doing this with the help of the map. (Map -transform the items emitted by an Observable by applying a function to each item. Click here to learn about map )
roadSignals.pipe( .map((signal) => changeSignal(signal)), .map((allow) => allow.allowVehiclesObservable()) ).subscribe((vehiclesObservable) => { vehiclesObservable.subscribe((vehicle) => console.log(vehicle)) });
Here the vehicles in roads refer to an observable. So the output will be an Observable inside an Observable. We have to subscribe them in order to get vehicles individually.
In our case, allowVehiclesObservable is the outer Observable and vehiclesObservable is the inner Observable. We need to subscribe the vehiclesObservable(inner Observable) in order to get the vehicles individually. This method is called as FLATTENING.(subscribe inside a subscribe).
Come on let’s do flattening to get vehicles individually.
roadSignals.pipe( .map((signal) => changeSignal(signal)), .map((allow) => allow.allowVehiclesObservable()) ).subscribe((vehiclesObservable) => { vehiclesObservable.subscribe((vehicle) => console.log(vehicle)) });
Let a1, a2,.. b1, b2,.. be the vehicles in Road A, B, ..respectively.
Output :
a1 a2 a3 b1 b2 a4
Yes! Now we get vehicles individually. Flattening works:)
Wait a sec. What’s happening. Vehicles from Road A as well as Road B is also moving. There are chances for accidents. After a few seconds, Signal C and Signal D will go ON. What if the vehicles from all the roads are moving.
Oh no. We should stop this. How to do this?
Whenever flattening is used we should use a Flattening Strategy.
- We need to store a subscription to the current vehiclesObservable subscription.
let subscription; (declare at top); subscription = vehiclesObservable.subscribe(vehicle => console.log(vehicle));
- If new vehiclesObservable comes in we need to unsubscribe from the previous subscription before we subscribe to the new observable.
if(subscription) subscription.unsubscribe(); subscription = vehiclesObservable.subscribe(vehicle => console.log(vehicle));
Let’s apply the above steps:
let subscription; roadSignals.pipe( map((signal) => changeSignal(signal)), map((allow) => allow.allowVehiclesObservable()) ).subscribe((vehiclesObservable) => { if(subscription){ subscription.unsubscribe(); } subscription = vehiclesObservable.subscribe((vehicle) => console.log(vehicle)) });
Output :
a1 a2 b1 b2 b3 c1 c2
SignalA comes first. Initially, the subscription is undefined. RoadA-vehiclesObservable is stored in the subscription(local variable) and is subscribed. Vehicles from roadA start moving. When SignalB comes in, the subscription is defined (RoadA-vehiclesObservable). The if condition becomes true. Unsubscribe the existing subscription(RoadA-vehiclesObservable) and store the new subscription(RoadB-vehiclesObservable) to get vehicles from road B.No more vehicles from Road A..! When SignalC comes in, the existing subscription (RoadB-vehiclesObservable) is unsubscribed and new RoadC-vehicleObservable is subscribed.
As soon as the outer observable changes we kill the most recent observable and switch to new observable to replace it. Thus unsubscribing from the previous observable before subscribing to the new one is called as SWITCH Strategy.
Yes, we have achieved our desired result. But now, the problem is the code became complex with observables, two subscribes, subscription, unsubscribe and all of these.
What to do?
Here comes switchMap for our rescue. We can replace the map and inner subscription with switchMap.
roadSignals.pipe( map((signal) => changeSignal(signal)), switchMap((allow) => allow.allowVehiclesObservable()) ).subscribe((vehicle) => { console.log(vehicle) });
Output :
Signal A vehicle a1 vehicle a2 Signal B vehicle b1 vehicle b2 Signal C vehicle c1 vehicle c2
The main difference between switchMap and other flattening operators is the cancellation effect. On each emission, the previous inner observable (the result of the function you supplied) is canceled and the new observable is subscribed. You can remember this by the phrase switch to a new observable.
When to use and avoid switchMap?
switchMap can be used, when a new search is made, pending results are no longer needed; And should be avoided in scenarios where every request needs to complete, think writes to a database. switchMap could cancel a request if the source emits quickly enough.
References: