Exception Handling  

Observables vs Signals & Error Handling Patterns in RxJS

Angular 17+ introduced Signals, causing developers to ask how they compare to Observables—and whether they replace RxJS.
This guide explains the differences clearly and ends with practical error-handling patterns you should use in real projects.

1. What Are Observables? (Short Recap)

Observables are asynchronous data streams from RxJS.

They are ideal for:

  • HTTP calls

  • WebSockets

  • Continuous event streams

  • Streams requiring operators (filter, map, mergeMap…)

Key characteristics:

  • Lazy (execute when subscribed)

  • Multicast/Unicast depending on usage

  • Can be cancelled

  • Powerful because of operators

2. What Are Signals? (Short Recap)

Signals in Angular are reactive variables that trigger change detection efficiently.

They are ideal for:

  • Local component state

  • Derived reactive values

  • Simple app-level state stores

Key characteristics:

  • Synchronous

  • Pull-based (read when needed)

  • No “stream” behavior

  • No need for async pipes (direct reads like counter())

Example

const count = signal(0);
count.set(5);
console.log(count());

3. Observables vs Signals — When to Use What?

A) Use Observables When You Need Streams

Perfect for:

  • HTTP requests

  • WebSocket data

  • Real-time event listeners

  • Multiple chained async operations

  • Retry/backoff logic

They support operators like:

  • map, switchMap, mergeMap

  • catchError, debounceTime

  • forkJoin, combineLatest

  • retry, delay, takeUntil

B) Use Signals When You Need State

Use signals for:

  • UI state

  • Filtering / sorting state

  • Component-level reactive values

  • Derived state (computed)

  • Minimal boilerplate

Signals shine for:

  • Simplicity

  • Performance

  • Predictable reactivity

C) Should You Replace Observables With Signals?

NO.

Observables handle asynchronous streams.
Signals handle reactive state.

They complement each other.

You can even convert between them:

toSignal(myObservable$);
toObservable(mySignal);

4. Mixing Observables and Signals (Best Practices)

Best Practice 1: Use Signals for Component State

Example

const users = signal<User[]>([]);

Best Practice 2: Convert HTTP Observables → Signals

Clean approach:

users = toSignal(this.http.get<User[]>('/api/users'));

Best Practice 3: Avoid Signals for Continuous Streams

Bad use case:

  • Scroll events

  • Mouse move

  • WebSockets

Use Observables.

5. Error Handling Patterns in RxJS (Practical Guide)

Error handling is where RxJS shines.

Below are the patterns you should use in real-world Angular apps.

Pattern 1: catchError for Transforming Errors

Recommended for HTTP.

this.http.get('/api/items').pipe(
  catchError(err => {
    console.error('API failed', err);
    return of([]); // fallback
  })
)

Pattern 2: retry / retryWhen for Network Retry Logic

Auto retry:

httpCall$.pipe(
  retry(3)
);

Retry with delay:

httpCall$.pipe(
  retryWhen(errors =>
    errors.pipe(delay(2000))
  )
);

Pattern 3: finalize for Cleanup

Useful for loaders/spinners.

loading.set(true);

this.http.get('/api').pipe(
  finalize(() => loading.set(false))
);

Pattern 4: Centralized Error Handling with tap

Log all errors in one place.

stream$.pipe(
  tap({
    error: (e) => logService.log(e)
  })
);

Pattern 5: takeUntil for Auto-Unsubscribe

Best for ngOnDestroy.

private destroy$ = new Subject<void>();

obs$.pipe(
  takeUntil(this.destroy$)
).subscribe();

Pattern 6: Fallback Strategy Using onErrorResumeNext

onErrorResumeNext(
  api1$,
  api2$
);

Ensures failure doesn't break the entire chain.

6. Which Should You Use for a New Angular App?

ScenarioUse ObservablesUse Signals
HTTP requests✔️via toSignal()
UI state✔️
Complex async flows✔️
Local component state✔️
Real-time events✔️

7. Summary (Easy to Remember)

Observables

  • Async streams

  • Powerful operators

  • Best for HTTP, events, real-time data

Signals

  • Simple reactive state

  • Synchronous

  • Local UI state and derived values