Skip to content

switchMap

Projects each source value to an observable, subscribes to it, and emits its values. When a new value arrives from the source, the previous inner observable is unsubscribed.

A value the source already holds when switchMap is created (a state, an observable with an initial value) is projected at once, as map and filter start from it. A completed source is not projected.

The projection runs in a scope of its own (see createScope): what it creates is disposed when the next value switches away from it and when switchMap completes. An observable it only returns — one created outside the projection and shared with others — is unsubscribed and left running.

If the projection or the subscription to it throws, its scope is disposed. A value the inner observable emitted while being subscribed, before the subscription threw, stays the current value: it has already been passed on.

A projection that writes to the source switches to the value written. Nothing subscribes to what that projection returns. What it makes after the write is disposed. A projection that completes switchMap is treated the same way.

Timeline of observable eventsABC
switchMap(fn)
Timeline of observable eventsA1A2A3B1B2C1C2C3
// Timeline:
//
// input$ A B C
// inner A A1 A2 A3
// inner B B1 B2 (switched!)
// inner C C1 C2 C3
// result$ A1 A2 A3 B1 B2 C1 C2 C3
//
// Explanation:
// - switchMap creates an inner observable for each source value
// - When a new source value arrives, the previous inner is unsubscribed
// - A's inner completes normally (all 3 values emitted)
// - B's inner is interrupted by C (only 2 values emitted)
// - C's inner completes normally (all 3 values emitted)
const input$ = source<string>();
const result$ = input$.pipe(
switchMap((letter) => {
const inner$ = source<string>();
setTimeout(() => {
inner$.next(`${letter}1`);
}, 10);
setTimeout(() => {
inner$.next(`${letter}2`);
}, 110);
setTimeout(() => {
inner$.next(`${letter}3`);
}, 210);
return inner$;
}),
);
const valueHistory: string[] = [];
result$.subscribe((value) => {
valueHistory.push(value);
});
const sleep = (ms: number): Promise<void> =>
new Promise((resolve) => {
setTimeout(resolve, ms);
});
// Emit A - inner emits A1, A2, A3 at 10ms, 110ms, 210ms
input$.next('A');
await sleep(250);
assert.deepStrictEqual(valueHistory, ['A1', 'A2', 'A3']);
// Emit B - inner starts emitting B1, B2 at 10ms, 110ms
input$.next('B');
await sleep(150);
assert.deepStrictEqual(valueHistory, ['A1', 'A2', 'A3', 'B1', 'B2']);
// Emit C - switches away from B (B3 cancelled), C's inner starts
input$.next('C');
await sleep(250);
assert.deepStrictEqual(valueHistory, [
'A1',
'A2',
'A3',
'B1',
'B2',
'C1',
'C2',
'C3',
]);

View source on GitHub