switchMap

signature: switchMap(project: function: Observable, resultSelector: function(outerValue, innerValue, outerIndex, innerIndex): any): Observable

Map to observable, complete previous inner observable, emit values.


:bulb: If you would like more than one inner subscription to be maintained, try
mergeMap!

:bulb: This operator is generally considered a safer default to
mergeMap!

:bulb: This operator can cancel in-flight network requests!


Why use switchMap?

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 perfect 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.

switchMap - 图4

Examples

Example 1: Restart interval every 5 seconds

( StackBlitz |
jsBin |
jsFiddle )

  1. import { timer } from 'rxjs/observable/timer';
  2. import { interval } from 'rxjs/observable/interval';
  3. import { switchMap } from 'rxjs/operators';
  4. //emit immediately, then every 5s
  5. const source = timer(0, 5000);
  6. //switch to new inner observable when source emits, emit items that are emitted
  7. const example = source.pipe(switchMap(() => interval(500)));
  8. //output: 0,1,2,3,4,5,6,7,8,9...0,1,2,3,4,5,6,7,8
  9. const subscribe = example.subscribe(val => console.log(val));
Example 2: Reset on every click

( StackBlitz |
jsBin |
jsFiddle )

  1. import { fromEvent } from 'rxjs/observable/fromEvent';
  2. import { interval } from 'rxjs/observable/interval';
  3. import { switchMap, mapTo } from 'rxjs/operators';
  4. //emit every click
  5. const source = fromEvent(document, 'click');
  6. //if another click comes within 3s, message will not be emitted
  7. const example = source.pipe(
  8. switchMap(val => interval(3000).pipe(mapTo('Hello, I made it!')))
  9. );
  10. //(click)...3s...'Hello I made it!'...(click)...2s(click)...
  11. const subscribe = example.subscribe(val => console.log(val));
Example 3: Using a resultSelector function

( StackBlitz |
jsBin |
jsFiddle )

  1. import { timer } from 'rxjs/observable/timer';
  2. import { interval } from 'rxjs/observable/interval';
  3. import { switchMap } from 'rxjs/operators';
  4. //emit immediately, then every 5s
  5. const source = timer(0, 5000);
  6. //switch to new inner observable when source emits, invoke project function and emit values
  7. const example = source.pipe(
  8. switchMap(
  9. _ => interval(2000),
  10. (outerValue, innerValue, outerIndex, innerIndex) => ({
  11. outerValue,
  12. innerValue,
  13. outerIndex,
  14. innerIndex
  15. })
  16. )
  17. );
  18. /*
  19. Output:
  20. {outerValue: 0, innerValue: 0, outerIndex: 0, innerIndex: 0}
  21. {outerValue: 0, innerValue: 1, outerIndex: 0, innerIndex: 1}
  22. {outerValue: 1, innerValue: 0, outerIndex: 1, innerIndex: 0}
  23. {outerValue: 1, innerValue: 1, outerIndex: 1, innerIndex: 1}
  24. */
  25. const subscribe = example.subscribe(val => console.log(val));
Example 4: Countdown timer with switchMap

( StackBlitz )

  1. import { interval } from 'rxjs/observable/interval';
  2. import { fromEvent } from 'rxjs/observable/fromEvent';
  3. import { merge } from 'rxjs/observable/merge';
  4. import { empty } from 'rxjs/observable/empty';
  5. import { switchMap, scan, takeWhile, startWith, mapTo } from 'rxjs/operators';
  6. const countdownSeconds = 10;
  7. const setHTML = id => val => (document.getElementById(id).innerHTML = val);
  8. const pauseButton = document.getElementById('pause');
  9. const resumeButton = document.getElementById('resume');
  10. const interval$ = interval(1000).pipe(mapTo(-1));
  11. const pause$ = fromEvent(pauseButton, 'click').pipe(mapTo(false));
  12. const resume$ = fromEvent(resumeButton, 'click').pipe(mapTo(true));
  13. const timer$ = merge(pause$, resume$)
  14. .pipe(
  15. startWith(true),
  16. switchMap(val => (val ? interval$ : empty())),
  17. scan((acc, curr) => (curr ? curr + acc : acc), countdownSeconds),
  18. takeWhile(v => v >= 0)
  19. )
  20. .subscribe(setHTML('remaining'));
HTML
  1. <h4>
  2. Time remaining: <span id="remaining"></span>
  3. </h4>
  4. <button id="pause">
  5. Pause Timer
  6. </button>
  7. <button id="resume">
  8. Resume Timer
  9. </button>

Additional Resources


:file_folder: Source Code:
https://github.com/ReactiveX/rxjs/blob/master/src/internal/operators/switchMap.ts