switchMap
💡 This operator can cancel in-flight network requests!
The main difference between
switchMap
and other flattening operators is the cancelling effect. On each emission the previous inner observable (the result of the function you supplied) is cancelled and the new observable is subscribed. You can remember this by the phrase switch to a new observable.This works perfectly for scenarios like typeaheads where you are no longer concerned with the response of the previous request when a new input arrives. This also is a safe option in situations where a long lived inner observable could cause memory leaks, for instance if you used mergeMap with an interval and forgot to properly dispose of inner subscriptions. Remember,
switchMap
maintains only one inner subscription at a time, this can be seen clearly in the first example.Be careful though, you probably want to avoid
switchMap
in scenarios where every request needs to complete, think writes to a database. switchMap
could cancel a request if the source emits quickly enough. In these scenarios mergeMap is the correct option.Example 1: Restart interval on every click
// RxJS v6+
import { interval, fromEvent } from 'rxjs';
import { switchMap } from 'rxjs/operators';
fromEvent(document, 'click')
.pipe(
// restart counter on every click
switchMap(() => interval(1000))
)
.subscribe(console.log);
Example 2: Countdown timer with pause and resume
// RxJS v6+
import { interval, fromEvent, merge, empty } from 'rxjs';
import { switchMap, scan, takeWhile, startWith, mapTo } from 'rxjs/operators';
const COUNTDOWN_SECONDS = 10;
// elem refs
const remainingLabel = document.getElementById('remaining');
const pauseButton = document.getElementById('pause');
const resumeButton = document.getElementById('resume');
// streams
const interval$ = interval(1000).pipe(mapTo(-1));
const pause$ = fromEvent(pauseButton, 'click').pipe(mapTo(false));
const resume$ = fromEvent(resumeButton, 'click').pipe(mapTo(true));
const timer$ = merge(pause$, resume$)
.pipe(
startWith(true),
switchMap(val => (val ? interval$ : empty())),
scan((acc, curr) => (curr ? curr + acc : acc), COUNTDOWN_SECONDS),
takeWhile(v => v >= 0)
)
.subscribe((val: any) => (remainingLabel.innerHTML = val));
Example 3: Using a resultSelector function
// RxJS v6+
import { timer, interval } from 'rxjs';
import { switchMap } from 'rxjs/operators';
// switch to new inner observable when source emits, emit result of project function
timer(0, 5000)
.pipe(
switchMap(
_ => interval(2000),
(outerValue, innerValue, outerIndex, innerIndex) => ({
outerValue,
innerValue,
outerIndex,
innerIndex
})
)
)
/*
Output:
{outerValue: 0, innerValue: 0, outerIndex: 0, innerIndex: 0}
{outerValue: 0, innerValue: 1, outerIndex: 0, innerIndex: 1}
{outerValue: 1, innerValue: 0, outerIndex: 1, innerIndex: 0}
{outerValue: 1, innerValue: 1, outerIndex: 1, innerIndex: 1}
*/
.subscribe(console.log);
- Nicholas Jamieson
- 🎥 💵 - John Linquist
- 🎥 💵 - André Staltz
- 🎥 💵 - André Staltz
- 🎥 - Kwinten Pisman
Last modified 2yr ago