Signals vs BehaviorSubject โ When to Use What
Signals are not replacing RxJS. They serve different purposes. Learn when to use each, how to bridge between them, and how to gradually migrate your codebase.
The biggest misconception in Angular 2024: "Signals are replacing RxJS." This is wrong. Signals and RxJS serve DIFFERENT purposes and work TOGETHER.
"Signals = state storage, RxJS = event stream โ dono alag kaam."
The official Angular guidance:
- Signals โ for state that holds a value: user data, UI state, component inputs
- RxJS โ for async event streams: HTTP calls, WebSocket, user events, timers
- They complement each other โ you can convert between them with toSignal() and toObservable()
// Signals for state
user = signal(null);
isLoading = signal(false);
items = signal- ([]);
// RxJS for async events
items$ = this.http.get
- ('/api/items');
formChanges$ = this.myForm.valueChanges.pipe(debounceTime(300));
// Bridge: Observable โ Signal
items = toSignal(this.http.get
- ('/api/items'), { initialValue: [] });
// Bridge: Signal โ Observable
user$ = toObservable(this.user)
.pipe(filter(u => u !== null));
Use Signals when you have STATE โ a value that changes over time.
// โ
SIGNALS โ sync state
// Component local state
isOpen = signal(false);
selectedTab = signal<'info' | 'reviews'>('info');
formData = signal({ name: '', price: 0 });
// Service shared state
@Injectable({ providedIn: 'root' })
export class CartService {
items = signal([]);
total = computed(() => this.items()
.reduce((s, i) => s + i.price * i.quantity, 0));
}
// Derived state
vegItems = computed(() => this.items()
.filter(i => i.category === 'Veg'));
// UI state
showModal = signal(false);
hoveredItem = signal(null);
When signals are the BEST choice:
- Component local state โ isOpen, selectedTab, currentPage
- Service shared state โ currentUser, cart, settings
- Derived values โ computed() from other signals
- Form state โ with model() for two-way
- Any sync data that template reads directly
"Signals = storage box โ value rakho, badlo, dikhao. Simple, synchronous, predictable."
Use RxJS when you have EVENTS โ streams of data over time.
// โ
RxJS โ async events and streams
// HTTP calls (Observable from HttpClient)
this.http.get<Biryani[]>('/api/biryanis').pipe(
retry(2),
catchError(err => of([]))
);
// Complex async chains
this.searchInput$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => this.http.get<Biryani[]>(`/api/search?q=${term}`)),
catchError(err => of([]))
).subscribe(results => this.results.set(results));
// WebSocket (continuous stream)
this.socket.on('order-updates').pipe(
filter(msg => msg.restaurantId === this.id)
).subscribe(msg => this.latestOrder.set(msg));
// User events (DOM streams)
fromEvent(this.inputRef.nativeElement, 'keyup').pipe(
map(e => (e.target as HTMLInputElement).value)
);
When RxJS is the BEST choice:
- HTTP calls via HttpClient โ always returns Observable
- WebSocket streams โ real-time, continuous data flow
- Complex async chains โ switchMap, mergeMap, retry, combineLatest
- User events โ fromEvent, form valueChanges
- Timer/interval โ continuous async operations
"RxJS = water pipe โ paani continuously bah raha hai. Multiple values over time, operators for transformation."
Angular provides built-in functions to convert between Signals and Observables seamlessly.
Observable โ Signal: toSignal()
import { toSignal } from '@angular/core/rxjs-interop';
// Convert HTTP Observable to Signal
biryanis = toSignal(
this.http.get<Biryani[]>('/api/biryanis'),
{ initialValue: [] } // REQUIRED for sync access
);
// Convert BehaviorSubject to Signal
user = toSignal(this.authService.user$, { initialValue: null });
// Now read: this.user() โ no subscribe needed!
// Convert form valueChanges to Signal
formValue = toSignal(this.myForm.valueChanges, { initialValue: {} });
Signal โ Observable: toObservable()
import { toObservable } from '@angular/core/rxjs-interop';
// Convert Signal to Observable
user$ = toObservable(this.user);
// Use RxJS operators on signal values
user$.pipe(
debounceTime(300),
switchMap(user => this.http.get(`/api/${user.id}/orders`))
).subscribe(orders => this.orders.set(orders));
The bridge pattern โ most common in real code:
// Signals for state (template reads)
searchTerm = signal('');
results = signal<Biryani[]>([]);
// RxJS for async (HTTP with debounce)
private searchEffect = effect((onCleanup) => {
const term = this.searchTerm();
// Convert signal to observable for RxJS operators
toObservable(this.searchTerm).pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(t => this.http.get<Biryani[]>(`/api/search?q=${t}`))
).subscribe(results => this.results.set(results));
});
"toSignal = pipe to bucket, toObservable = bucket to pipe โ dono directions mein convert karo."
Here's a practical, step-by-step strategy for migrating an Angular service from RxJS to Signals:
Step 1: New code uses signals
// Any new component or service โ use signals by default
// Only use RxJS when you specifically need async operators
Step 2: Replace BehaviorSubject in services with signal()
// BEFORE
private userSubject = new BehaviorSubject<User | null>(null);
user$ = this.userSubject.asObservable();
// AFTER
private user = signal<User | null>(null);
readonly userReadonly = this.user.asReadonly();
// Old consumers still work with toObservable:
user$ = toObservable(this.user);
Step 3: Replace subscribe() with toSignal()
// BEFORE
ngOnInit() { this.http.get('/api/data').subscribe(d => this.data = d); }
// AFTER
data = toSignal(this.http.get('/api/data'), { initialValue: null });
// Template: {{ data() }} โ no subscribe, no ngOnDestroy!
Step 4: Gradual migration checklist
- โ New components โ signals by default
- โ New services โ signals for state, RxJS for HTTP/events
- โ Existing BehaviorSubject โ signal() + toObservable() for backward compat
- โ Existing subscribe() โ toSignal() or effect()
- โ DON'T migrate everything at once โ service by service is fine
"Ek ek karke โ purana kaam bhi chal raha hai, naya bhi add ho raha hai. Koi rush nahi."
Real-world example โ gradual migration:
// Month 1: New components use signals
// Month 2: AuthService migrated to signal + toObservable
// Month 3: CartService migrated
// Month 4: Remove old BehaviorSubject-based consumers
// Month 5: Full signal codebase โ only RxJS for HTTP/eventsKey Takeaways
- โ Signals = state (sync, simple values), RxJS = events (async, streams)
- โ Use signals for: component local state, service shared state, derived values
- โ Use RxJS for: HTTP calls, WebSocket, complex async chains, user events
- โ toSignal() converts Observable โ Signal (always provide initialValue!)
- โ toObservable() converts Signal โ Observable (for RxJS operators)
- โ Migrate gradually: new code โ signals, existing โ service by service
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login