Understanding map, switchMap, and exhaustMap in NgRx Effects: A Guide to Stream Transformation
The differences between exhaustMap, map, and switchMap in the context of NgRx effects:
Understanding map, switchMap, and exhaustMap in NgRx Effects: A Guide to Stream Transformation

The differences between exhaustMap, map, and switchMap in the context of NgRx effects:
map
Purpose: Transforms each emission from an Observable into a new value.
Behavior:
- Applies the provided function to each value emitted by the source Observable.
- The resulting values form a new Observable.
Use Cases:
- Modifying data within an Observable stream (e.g., multiplying numbers, converting strings).
- Filtering out specific values (using conditional logic within the function).
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { increaseByTwo } from '../actions/counter.actions';
import { map } from 'rxjs/operators';
@Injectable()
export class CounterEffects {
countByTwo$ = createEffect(() =>
this.actions$.pipe(
ofType(increaseByTwo),
map((action) => action.payload * 2) // Double the payload
)
);
constructor(private actions$: Actions) {}
}
switchMap
Purpose: Cancels any ongoing inner Observable subscription and subscribes to a new one based on the latest emission.
Behavior:
- When the source Observable emits a value,
switchMapunsubscribes from any currently active inner Observable subscription (if any). - It then subscribes to a new inner Observable returned by the provided function, using the emitted value as input.
- Only the emissions from the latest inner Observable are emitted by the resulting Observable.
Use Cases:
- Replacing an ongoing action with a new one based on the latest emission (e.g., fetching data for a newly selected user).
- Handling side effects that should only occur once per emission (e.g., making a single API call for each user action).
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { loadUser, loadUserSuccess } from '../actions/user.actions';
import { switchMap } from 'rxjs/operators';
import { of } from 'rxjs'; // For error handling (optional)
@Injectable()
export class UserEffects {
loadUser$ = createEffect(() =>
this.actions$.pipe(
ofType(loadUser),
switchMap((action) =>
this.userService.getUser(action.payload.userId).pipe(
map((user) => loadUserSuccess({ user })),
catchError((error) => of(loadUserFailure({ error }))) // Optional error handling
)
)
)
);
constructor(private actions$: Actions, private userService: UserService) {}
}
exhaustMap
Purpose: Similar to switchMap, but ignores any subsequent emissions from the source Observable while the inner Observable is active.
Behavior:
- When the source Observable emits a value,
exhaustMapunsubscribes from any currently active inner Observable subscription (if any). - It then subscribes to a new inner Observable returned by the provided function, using the emitted value as input.
- Only the emissions from the latest inner Observable are emitted by the resulting Observable.
- If another emission occurs from the source Observable before the inner Observable completes, that emission is discarded.
Use Cases:
- Preventing multiple side effects (e.g., API calls) from being triggered due to rapid user input (e.g., search queries).
- Ensuring only one action is processed at a time, especially when actions might trigger long-running operations.
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { searchProducts, searchProductsSuccess } from '../actions/products.actions';
import { exhaustMap } from 'rxjs/operators';
import { of } from 'rxjs'; // For error handling (optional)
@Injectable()
export class ProductEffects {
searchProducts$ = createEffect(() =>
this.actions$.pipe(
ofType(searchProducts),
exhaustMap((action) =>
this.productService.searchProducts(action.payload.searchTerm).pipe(
map((products) => searchProductsSuccess({ products })),
catchError((error) => of(searchProductsFailure({ error }))) // Optional error handling
)
)
)
);constructor(private actions$: Actions, private userService: UserService) {}
}
Choosing between map, switchMap, and exhaustMap in NgRx effects depends on how you want your effects to handle the data stream and side effects triggered by actions:
Use map when:
- You simply need to transform each value emitted by the source Observable into a new value.
- The order of emissions doesn’t matter.
- There are no side effects involved.
Example: Doubling a counter value:
map((action) => action.payload * 2)
Use switchMap when:
- You want to cancel any ongoing side effects and initiate a new one based on the latest emission.
- The order of emissions matters, and you only care about the results from the latest action.
- You might have multiple side effects triggered in rapid succession, but only the most recent one should be processed.
Example: Fetching user data based on the latest selected user ID:
TypeScript
switchMap((action) => this.userService.getUser(action.payload.userId))
Use exhaustMap when:
- You want to ensure only one side effect is processed at a time, even if multiple emissions occur rapidly.
- Any subsequent emissions from the source Observable while the inner Observable is active should be discarded.
- This is useful for preventing unintended side effects due to rapid user input (e.g., search queries).
Example: Debouncing a search query to avoid overwhelming the server with too many requests:
exhaustMap((action) => this.productService.searchProducts(action.payload.searchTerm))
Here’s a table summarizing the key considerations:

메타데이터
- post_id
- c1d9ea23713b
- slug
- understanding-map-switchmap-and-exhaustmap-in-ngrx-effects-a-guide-to-stream-transformation-c1d9ea23713b
- url
- https://medium.com/@louistrinh/understanding-map-switchmap-and-exhaustmap-in-ngrx-effects-a-guide-to-stream-transformation-c1d9ea23713b
- canonical_url
- https://medium.com/@louistrinh/understanding-map-switchmap-and-exhaustmap-in-ngrx-effects-a-guide-to-stream-transformation-c1d9ea23713b
- author_url
- https://medium.com/@louistrinh
- status
- ok
- fetched_at
- 2026-09-04 21:40:00