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,mergeMapcatchError,debounceTimeforkJoin,combineLatestretry,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?
| Scenario | Use Observables | Use 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

Comments
Join the conversation! Your thoughts help the community grow.