Let’s break down the differences between Angular Signals and Observables in depth, and provide examples so you get both the conceptual clarity and practical understanding.

Overview: Signals vs Observables

FeatureSignalsObservables
OriginAngular 16 (Reactively inspired)RxJS (ReactiveX library)
TypePull-based reactive primitivePush-based reactive stream
Use caseSimple state management, fine-grained reactivityComplex asynchronous streams like HTTP, WebSockets
Change detectionIntegrated with Angular’s change detectionRequires manual async pipe or subscribe
LifecycleSynchronousAsynchronous
MemoryLightweightCan cause memory leaks if not unsubscribed
InteropNative Angular APIsExternal (RxJS), but tightly integrated
Subscription managementAutomaticManual (or async pipe)
Learning curveEasy for beginnersSteeper due to stream composition

Conceptual Differences between Signals and Observables

Signals – Pull-based (Synchronous)

A Signal is like a reactive variable. You read from it like a function and Angular tracks all reads and automatically re-runs the dependent computations when values change.

Think of it like Excel: when a cell (signal) changes, all dependent cells update automatically.

Observables – Push-based (Asynchronous)

An Observable is an object that can stream various data or events that can be observed over time. You subscribe to it and it emits data (can be async). Great for event streams, API responses, user interactions, etc.

Think of it like a news subscription: you get notified (pushed) when something happens.

Basic Example

Using Signals

import { signal, computed, effect } from '@angular/core';
const count = signal(0);
const doubleCount = computed(() => count() * 2);
// Automatically re-runs when count changes
effect(() => { console.log('Double Count:', doubleCount());
});

Change signal

count.set(2); // console: Double Count: 4
count.update(n => n + 3); // console: Double Count: 10
  • signal() creates reactive state
  • computed() derives values
  • effect() tracks dependencies and re-runs when needed

Using Observables

import { of } from 'rxjs';
import { map } from 'rxjs/operators';
const count$ = of(2); // Observable emitting 2
const doubleCount$ = count$.pipe( map(count => count * 2)
);
doubleCount$.subscribe(value => { console.log('Double Count:', value); // Output: 4
});

Key Observations:

  • Observables use async operations by default (even if you use of)
  • You pipe operations and subscribe to get values

Real-world Angular Usage

With Signals (State Management)

@Component({ standalone: true, template: ` <button (click)="increment()">Increment</button> <p>Count: {{ count() }}</p> `
})
export class CounterComponent { count = signal(0); increment() { this.count.update(c => c + 1); }
}
  • No async pipe needed
  • Just use count() to access

With Observables (Async data)

@Component({ template: ` <ul> <li *ngFor="let user of users$ | async">{{ user.name }}</li> </ul> `
})
export class UserComponent { users$: Observable<User[]>; constructor(private http: HttpClient) { this.users$ = this.http.get<User[]>('/api/users'); }
}

Key Takeaways:

  • Perfect for asynchronous data like HTTP
  • Requires | async pipe

Summary Table

FeatureSignalsObservables
Data flowPull (read when needed)Push (listen for emissions)
Async supportNot inherently asyncBuilt for async
DependenciesTracked automaticallyManual management
Lifecycle managementNo cleanup neededNeeds unsubscribe or async pipe
Use casesUI state, forms, signals storeHTTP, WebSockets, time intervals

When to Use What?

ScenarioRecommended
UI state, local component stateSignals
Derived values from other variablesSignals + Computed
HTTP requests, intervals, event streamsObservables
Complex stream composition (merge, debounce, retry)Observables
Fine-grained reactivity & simple updatesSignals

Interop Example: Convert Observable to Signal

import { toSignal } from '@angular/core/rxjs-interop';
constructor(http: HttpClient) { this.userSignal = toSignal(http.get<User>('/api/user'));
}
  • Use toSignal() to connect async Observable to Signal

Conclusion

  • Use Signals for synchronous state & reactivity inside Angular templates and components.
  • Use Observables for asynchronous operations and stream-based data handling.
  • They can co-exist and even convert between each other when needed.