Skip to the solution
MediumFrontend9 min read

How does catchError work in RxJS, and how do you return a fallback value?

939 views

The problem

Question presented to candidate: "Our user-profile page loads data with HttpClient. When the API fails, the page shows nothing and, after one failed lookup, the search box stops working entirely. How does catchError work, how would you show a fallback value, and why might the search box have died?"

What a strong answer should cover:

  • An error terminates an Observable: no more values, no completion. catchError intercepts that error and replaces the errored stream with a new Observable you return.
  • Fallback value: catchError(() => of(fallback)), which emits the fallback and then completes. Other options: EMPTY (complete silently), throwError(() => mappedError) (rethrow a friendlier error), or caught (resubscribe, usually better done with retry).
  • The selector must return an Observable input; returning undefined throws a TypeError, and a plain string is iterated character by character.
  • Placement: catchError on the outer chain swaps the whole stream, so a long-lived trigger (clicks, search terms, actions) dies; put it inside switchMap / mergeMap on the inner request.
  • Combine with retry({ count, delay }) before it, and decide whether errors belong in a global interceptor or per call.

Clarifying questions expected:

  • "Should the user see a fallback, an error message, or should we retry first?"
  • "Is the failing call a one-off load or part of a long-lived stream such as a search box or an effect?"

Code / implementation expected: Yes β€” a fallback with of() and the inner-vs-outer placement shown on a stream that keeps receiving values.

  • rxjs
  • error-handling

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 10 sections

Read the code

AngularcatchError outside vs inside switchMap: one stream dies, the other survives
import { Component, OnDestroy, signal } from "@angular/core";
import { Observable, Subject, Subscription, catchError, delay, of, switchMap, throwError } from "rxjs";

interface User {
  id: number;
  name: string;
}

// Fake API: user 2 does not exist and the "server" answers with an error.
function getUser(id: number): Observable<User> {
  return id === 2
    ? throwError(() => new Error("404 user " + id))
    : of({ id, name: "user-" + id }).pipe(delay(10));
}

const FALLBACK: User = { id: 0, name: "guest" };

@Component({
  selector: "app-root",
  template: `
    @for (id of [1, 2, 3]; track id) {
      <button (click)="clicks$.next(id)">Load user {{ id }}</button>
    }
    <p>catchError OUTSIDE switchMap: {{ outer().join(" | ") }}</p>
    <p>catchError INSIDE switchMap: {{ inner().join(" | ") }}</p>
  `,
})
export class AppComponent implements OnDestroy {
  clicks$ = new Subject<number>();
  outer = signal<string[]>([]);
  inner = signal<string[]>([]);
  private subs = new Subscription();

  constructor() {
    const logTo = (target: typeof this.outer) => ({
      next: (u: User) => target.update((l) => [...l, u.name]),
      complete: () => target.update((l) => [...l, "COMPLETED (stream is dead)"]),
    });

    // Wrong place: the error reaches the outer chain, catchError swaps the WHOLE
    // click stream for of(FALLBACK), which emits once and completes.
    this.subs.add(
      this.clicks$
        .pipe(
          switchMap((id) => getUser(id)),
          catchError(() => of(FALLBACK)),
        )
        .subscribe(logTo(this.outer)),
    );

    // Right place: only the failed request is replaced; the click stream lives on.
    this.subs.add(
      this.clicks$
        .pipe(switchMap((id) => getUser(id).pipe(catchError(() => of(FALLBACK)))))
        .subscribe(logTo(this.inner)),
    );
  }

  ngOnDestroy() {
    this.subs.unsubscribe();
  }
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 25 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextMedium

What are Angular lifecycle hooks?

Continue