The Angular ExhaustMap maps each value from the source observable into an inner observable, subscribes to it. It then starts emitting the values from it replacing the original value. It then waits for the inner observable to finish. If it receives any new values before the completion of the inner observable it ignores it. It receives a new value after completion of the inner observable, then it creates a new inner observable. The whole process repeats itself until the source observable is completes
Table of Contents
Syntax
The syntax of the exhaustMap
operator is as shown below.
1 2 3 | exhaustMap(project: (value: T, index: number) => O): OperatorFunction<T, ObservedValueOf<O>> |
project:
is a function that we use to manipulate the values emitted by the source observable. The project function accepts two arguments. one is value
i.e. the value emitted by the source observable. The second argument is index
number. The index
number starts from 0
for the first value emitted and incremented by one for every subsequent value emitted. It is similar to the index of an array. The project function must return an observable.
ExhaustMap Example
To use ExhaustMap in Angular first we need to import it our Component or Service.
1 2 3 | import { exhaustMap} from 'rxjs/operators'; |
In the following code, we have two observables srcObservable
which emits 1,2,3,4
& innerObservable
which emits 'A','B','C','D'
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | let srcObservable= of(1,2,3,4) let innerObservable= of('A','B','C','D') srcObservable.pipe( exhaustMap( val => { console.log('Source value '+val) console.log('starting new observable') return innerObservable }) ) .subscribe(ret=> { console.log('Recd ' + ret); }) //Output Source value 1 starting new observable Recd A Recd B Recd C Recd D Source value 2 starting new observable Recd A Recd B Recd C Recd D Source value 3 starting new observable Recd A Recd B Recd C Recd D Source value 4 starting new observable Recd A Recd B Recd C Recd D |
The ExhaustMap receives its values from the srcObservable
. For each value, it creates a new observable i.e. innerObservable.
It also automatically subscribes to the innerObservable
. The innerObservable
emits the values (A, B, C, D), and pushes it to the subscribers.
The ExhaustMap differs from the MergeMap, SwitchMap & ConcatMap the way it handles the inner observable. ExhaustMap always waits for the inner observable to finish. It ignores any value it receives from the source during this period. Any value it receives after the inner observable is finished is accepted and it creates a new inner observable.
This difference is becomes clear in the next example.
ExhaustMap waits for the inner observable to finish
ExhaustMap creates and waits for inner observable before resuming.
In the following example, we create an observable from the click
event of a button
using the fromEvent
method. On every click of the button, the ExhaustMap operator returns an inner observable delayedObs
The delayedObs
emits 5 values separated by 1000 ms.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core'; import { of, from, fromEvent, interval, Observable } from 'rxjs'; import { switchMap, map, catchError } from 'rxjs/operators'; @Component({ selector: 'app-root', template: `<button #button>Click Me</button>`, }) export class AppComponent implements AfterViewInit{ @ViewChild('button',{static:true}) button; clicks$:Observable<any>; ngAfterViewInit() { this.clicks$ = fromEvent(this.button.nativeElement, 'click'); this.exhaustMapExample3(); } delayedObs(count:number) { return new Observable((observer) => { setTimeout(() => { observer.next(count+" A") }, 1000); setTimeout(() => { observer.next(count+" B") }, 2000); setTimeout(() => { observer.next(count+" C") }, 3000); setTimeout(() => { observer.next(count+" D") }, 4000); setTimeout(() => { observer.next(count+" E"); observer.complete() }, 5000); }) } exhaustMapExample3() { let obs= this.clicks$ .pipe( exhaustMap(() => { this.count=this.count+1; return this.delayedObs(this.count) }) ) .subscribe(val => console.log(val)); } } |
When you click on the button, the clicks
observable emits its first value. Inside the exhaustMap
we increase the count by 1 and pass it to the delayedObs
. The exhaustMap
subscribes to the delayedObs
. It starts emitting values A to E prepended by the count.
When you click again, the exhaustMap
checks to see if the previous observable has finished. If not it ignore the value.
You can verify it from the following result. When we click multiple times, it ignores the values when the inner observable is running.
Using ExhaustMap in Angular
The exhaustMap is useful in scenarios, where you want to prevent submission of duplicate values
For Example, the user clicking on the Submit button twice will trigger two HTTP calls to the back end. The ExhaustMap will prevent the creation of new HTTP call until the previous one finishes
References
Read More