Understanding mergeMap vs concatMap vs exhaustMap in Angular
📖 Scenario: You are building a simple Angular component that listens to button clicks and makes simulated API calls. You want to learn how different RxJS operators mergeMap, concatMap, and exhaustMap handle multiple clicks differently.
🎯 Goal: Create an Angular standalone component that uses mergeMap, concatMap, and exhaustMap separately to handle button click events and simulate API calls. Observe how each operator manages multiple rapid clicks.
📋 What You'll Learn
Create a button click event observable
Set up a simulated API call function returning an observable with delay
Use mergeMap to handle clicks and show results
Use concatMap to handle clicks and show results
Use exhaustMap to handle clicks and show results
Display the emitted values in the template for each operator
💡 Why This Matters
🌍 Real World
Handling user events like button clicks or search inputs where multiple requests can happen quickly, and choosing the right RxJS operator controls how these requests are managed.
💼 Career
Understanding these operators is essential for Angular developers to manage asynchronous data streams effectively and avoid bugs like race conditions or unnecessary API calls.
Progress0 / 4 steps
1
Set up the Angular component and simulated API call
Create a standalone Angular component named MapOperatorsComponent. Inside it, create a method called fakeApiCall that takes a value string and returns an observable that emits `Response: ${value}` after a 1 second delay using of and delay from RxJS.
Angular
Hint
Use of from RxJS to create an observable that emits a value. Use delay(1000) to simulate a 1 second wait.
2
Add a button click observable and a config variable
Add a public property clicks$ of type Subject<string> initialized as a new Subject. Also add a public property clickCount initialized to 0. This will track the number of clicks and emit values.
Angular
Hint
Import Subject from RxJS and create clicks$ as a new Subject of strings. Initialize clickCount to zero.
3
Use mergeMap to handle clicks and show results
Import mergeMap and tap from RxJS. Create a public property mergeMapResults$ that subscribes to clicks$ and uses mergeMap to call fakeApiCall with the emitted value. Use tap to log the response. Increment clickCount and emit `Click ${clickCount}` to clicks$ when a method onClickMergeMap() is called.
Angular
Hint
Use mergeMap inside pipe on clicks$. Create a method onClickMergeMap that increments clickCount and emits a string to clicks$.
4
Add concatMap and exhaustMap handling with template buttons and display
Import concatMap and exhaustMap from RxJS. Create two new public properties concatMapResults$ and exhaustMapResults$ that use concatMap and exhaustMap respectively on clicks$ to call fakeApiCall. Add two methods onClickConcatMap() and onClickExhaustMap() that increment clickCount and emit `Click ${clickCount}` to clicks$. Update the component template to have three buttons labeled MergeMap, ConcatMap, and ExhaustMap that call their respective click methods. Below each button, display the latest emitted value from the corresponding observable using the async pipe.
Angular
Hint
Use concatMap and exhaustMap similarly to mergeMap. Add buttons in the template that call the respective click methods. Display results with the async pipe.
Practice
(1/5)
1. Which RxJS operator runs all inner tasks at the same time and emits results as they complete, without waiting for order?
easy
A. switchMap
B. concatMap
C. exhaustMap
D. mergeMap
Solution
Step 1: Understand mergeMap behavior
mergeMap subscribes to all inner observables immediately and merges their outputs as they arrive.
Step 2: Compare with other operators
concatMap waits for each inner observable to complete before starting the next, exhaustMap ignores new inner observables if one is active, and switchMap cancels previous inner observables.
Final Answer:
mergeMap -> Option D
Quick Check:
Runs all tasks at once = mergeMap [OK]
Hint: All tasks at once means mergeMap [OK]
Common Mistakes:
Confusing concatMap with mergeMap because both handle multiple tasks
Thinking exhaustMap runs all tasks simultaneously
Mixing switchMap behavior with mergeMap
2. Which of the following is the correct syntax to use concatMap in an Angular RxJS pipe?
easy
A. concatMap(source$, value => inner$)
B. source$.pipe(concatMap(value => inner$))
C. source$.concatMap(value => inner$)
D. source$.pipe(map(concatMap(value => inner$)))
Solution
Step 1: Recall RxJS pipe usage
Operators like concatMap are used inside pipe() method on observables.
Step 2: Check syntax correctness
source$.pipe(concatMap(value => inner$)) correctly uses source$.pipe(concatMap(...)). Options A and B misuse method chaining or function call. source$.pipe(map(concatMap(value => inner$))) wrongly nests concatMap inside map.
Final Answer:
source$.pipe(concatMap(value => inner$)) -> Option B
Quick Check:
Use pipe with concatMap inside [OK]
Hint: Always use pipe() with concatMap inside [OK]
Common Mistakes:
Calling concatMap directly on observable without pipe
Passing concatMap as argument to map
Using concatMap as a standalone function
3. Given the code below, what will be the output order of emitted values?
import { of } from 'rxjs';
import { concatMap, delay } from 'rxjs/operators';
of(1, 2, 3).pipe(
concatMap(x => of(x).pipe(delay(100 - x * 10)))
).subscribe(console.log);
medium
A. [3, 2, 1]
B. [1, 3, 2]
C. [1, 2, 3]
D. [2, 1, 3]
Solution
Step 1: Understand concatMap behavior with delays
concatMap waits for each inner observable to complete before starting the next, preserving order.
Step 2: Analyze delays and emission order
Even though delays differ (90ms, 80ms, 70ms), concatMap queues them and emits in source order: 1, then 2, then 3.
Final Answer:
[1, 2, 3] -> Option C
Quick Check:
concatMap preserves order despite delays [OK]
Hint: concatMap keeps order even if inner delays differ [OK]
Common Mistakes:
Assuming output order matches delay times
Confusing concatMap with mergeMap behavior
Ignoring that concatMap queues inner observables
4. You have this code snippet using exhaustMap. It should ignore new clicks while processing, but it processes all clicks anyway. What is the likely mistake?
A. fetchData() returns a promise, not an observable
B. Using exhaustMap instead of mergeMap
C. Not subscribing to the observable
D. Not returning an observable inside exhaustMap
Solution
Step 1: Check exhaustMap inner function return type
exhaustMap expects the function to return an observable. If fetchData() returns a promise, it won't behave correctly.
Step 2: Understand effect of returning a promise
Promises are not cancellable observables, so exhaustMap can't ignore new clicks properly, causing all clicks to process.
Final Answer:
fetchData() returns a promise, not an observable -> Option A
Quick Check:
exhaustMap needs observables, not promises [OK]
Hint: exhaustMap requires observables, not promises [OK]
Common Mistakes:
Thinking exhaustMap works with promises automatically
Forgetting to subscribe to the observable
Confusing exhaustMap with mergeMap usage
5. You want to handle user search input so that if a search is running, new inputs are ignored until it finishes. Which operator should you use and why?