100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TypeScript

RxJS Operators for HTTP (map, switchMap, catchError)

Master the core RxJS operators — map, switchMap, catchError, and their flattening cousins — used to transform, chain, and recover from HTTP requests.

HTTP & RxJSIntermediate11 min readJul 9, 2026
Analogies

RxJS Operators for HTTP (map, switchMap, catchError)

HttpClient returns Observables, and the real power of that design shows up once you start composing RxJS operators around them with .pipe(). Rather than nesting callbacks or chaining .then(), you describe a pipeline: transform the raw response, chain a follow-up request that depends on the first result, and recover from errors — all declaratively, and all cancellable. Three operators come up constantly in HTTP work: map for shaping data, switchMap (and its siblings mergeMap, concatMap, exhaustMap) for chaining a new Observable off an existing one, and catchError for turning a failed request into a recoverable fallback stream.

🏏

Cricket analogy: Piping RxJS operators onto an HttpClient Observable is like a captain scripting a full over strategy in advance: set the field (map the data), decide who bowls next depending on the batsman (switchMap), and have a rain-delay contingency ready (catchError), all planned declaratively rather than reacting ball by ball.

map: reshaping emitted values

map applies a pure synchronous function to every value emitted by the source Observable, exactly like Array.prototype.map but for a stream over time instead of a fixed array. It's the natural tool for extracting a nested field from a response, converting date strings into Date objects, or adapting a backend DTO shape into the shape your UI actually wants — keeping that translation logic out of components.

🏏

Cricket analogy: map is like a scorer converting raw ball-by-ball data into run rate figures for every single delivery in the innings, applying the same conversion formula to each ball as it comes in, just like Array.map but streaming over time instead of a fixed scorecard.

typescript
import { map } from 'rxjs';

interface ApiUserResponse { data: { id: number; full_name: string } }
interface User { id: number; name: string }

function getUser(id: number) {
  return this.http.get<ApiUserResponse>(`/api/users/${id}`).pipe(
    map(res => ({ id: res.data.id, name: res.data.full_name } satisfies User))
  );
}

switchMap and its flattening siblings

When one asynchronous action needs to trigger another — searching as the user types, or loading a user's orders after loading the user — you need a 'flattening' operator that takes an outer Observable and, for each value, maps it to an inner Observable, then flattens the two into one stream. switchMap cancels the previous inner Observable whenever a new outer value arrives, which is exactly right for type-ahead search: only the latest keystroke's request matters. mergeMap runs all inner Observables concurrently without cancelling — appropriate when overlapping requests are independent and all results matter. concatMap queues inner Observables to run strictly one after another, useful when order must be preserved. exhaustMap ignores new outer values while an inner Observable is still running, which suits a 'submit' button you want to guard against double-clicks.

🏏

Cricket analogy: switchMap is like a captain who cancels the current bowler's spell the instant a new tactical situation arises and brings on a fresh bowler for the new situation only, ignoring what the previous bowler was doing, exactly right for reacting to the latest ball rather than every past one; mergeMap would be like running two bowlers' overs concurrently, concatMap like queuing overs strictly in order, and exhaustMap like refusing a bowling change mid-over.

typescript
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs';

searchResults$ = this.searchControl.valueChanges.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(term => this.searchService.search(term))
);

catchError: recovering from failed requests

catchError intercepts an error thrown anywhere upstream in the pipe and lets you return a replacement Observable instead of letting the error propagate and terminate the stream. This is essential for HTTP: a 404 or network failure should rarely be allowed to silently kill a subscription that a component depends on for ongoing updates. A common pattern is to log or report the error, then return of(fallbackValue) so downstream consumers (like the async pipe or toSignal) keep receiving values instead of erroring out entirely.

🏏

Cricket analogy: catchError is like a stand-in umpire stepping in when the main umpire is injured mid-over, replacing a halted match with a substitute decision so play (the subscription) continues instead of the innings being abandoned entirely, returning a fallback score rather than a blank scorecard.

typescript
import { catchError, of } from 'rxjs';

getProducts() {
  return this.http.get<Product[]>('/api/products').pipe(
    catchError(err => {
      console.error('Failed to load products', err);
      return of<Product[]>([]); // graceful fallback
    })
  );
}

A useful mental model: map transforms values, mergeMap/switchMap/concatMap/exhaustMap flatten and chain Observables-of-Observables, and catchError substitutes an entire replacement stream when an error occurs. Reaching for the wrong flattening operator (e.g. mergeMap for type-ahead search) is one of the most common sources of race-condition bugs in Angular apps.

Once catchError returns a fallback Observable, the original error is considered handled and the outer stream does not error out — but if you need the specific request to still fail visibly (e.g. to set an error flag), you must do that inside the catchError callback itself, since simply logging and swallowing the error will make the UI behave as if nothing went wrong.

  • map transforms each emitted value synchronously, ideal for reshaping HTTP response DTOs into UI-friendly models.
  • switchMap cancels the previous inner Observable on each new outer emission — correct for type-ahead search.
  • mergeMap runs inner Observables concurrently; concatMap queues them in order; exhaustMap ignores new triggers while busy.
  • catchError intercepts upstream errors and lets you substitute a fallback Observable so the stream keeps flowing.
  • Combine debounceTime and distinctUntilChanged with switchMap to avoid firing a request on every keystroke.
  • Picking the wrong flattening operator is a leading cause of race conditions in HTTP-heavy Angular code.

Practice what you learned

Was this page helpful?

Topics covered

#TypeScript#AngularStudyNotes#WebDevelopment#RxJSOperatorsForHTTPMapSwitchMapCatchError#RxJS#Operators#HTTP#Map#Networking#StudyNotes#SkillVeris