By Diego Liascovich
Full-Stack Developer | Microservices | Angular | Node.js
In this post, I share how I migrated a real-world Angular application from heavy use of
Observablesandasyncpipes to the new reactive model based onsignal()introduced in Angular 16+.
I walk through real decisions, code examples (before/after), and practical lessons learned.
π§ Why Migrate from Observables to Signals?
RxJS is powerful, but can get overly complex with:
-
switchMap,combineLatest,Subject,unsubscribe, etc. - Harder debugging and lifecycle issues.
- Performance pitfalls if subscriptions aren't handled properly.
With signals:
- No need to manually subscribe or unsubscribe.
- More predictable rendering.
- Cleaner code and localized reactive state.
ποΈ Real App Context
A real Angular 15 app used for managing orders and invoices (orders, billings), using RxJS, was migrated to Angular 17.
It used BehaviorSubject, Observable, combineLatest, and async pipe extensively for data and UI state.
π Before: RxJS-Based Code
// orders.service.ts
private ordersSubject = new BehaviorSubject<Order[]>([]);
orders$ = this.ordersSubject.asObservable();
loadOrders() {
this.http.get<Order[]>('/api/orders')
.subscribe(data => this.ordersSubject.next(data));
}
<!-- orders.component.html -->
<div *ngIf="orders$ | async as orders">
<app-order *ngFor="let order of orders" [data]="order"></app-order>
</div>
β After: Signal-Based Code
// orders.service.ts
import { signal } from '@angular/core';
orders = signal<Order[]>([]);
loadOrders() {
this.http.get<Order[]>('/api/orders')
.subscribe(data => this.orders.set(data));
}
// orders.component.ts
readonly orders = this.ordersService.orders;
<!-- orders.component.html -->
<div *ngFor="let order of orders()">
<app-order [data]="order"></app-order>
</div>
π‘ Migrating Derived Logic (e.g., combineLatest)
Before:
// Combining filter and data
readonly filteredOrders$ = combineLatest([
this.orders$,
this.filter$
]).pipe(
map(([orders, filter]) => orders.filter(order => order.status === filter))
);
After (with computed and signal):
readonly filter = signal('pending');
readonly filteredOrders = computed(() =>
this.orders().filter(order => order.status === this.filter())
);
π§ Important Considerations
π§Ή No More Manual Subscriptions
With signal(), Angular handles lifecycle cleanup. No need for takeUntil, unsubscribe, or memory leak worries.
π Mixing RxJS and Signals
To convert an Observable to a Signal:
readonly user$ = this.authService.user$;
readonly user = toSignal(this.user$, { initialValue: null });
To go the other way:
const user$ = fromSignal(this.user);
β οΈ When Not to Migrate (Yet)
- Your app heavily depends on
NgRxor RxJS-centric libs. - You have mature RxJS-based logic with full test coverage.
- You're using Angular < 16.
π§ Conclusion
Migrating to Signals allowed me to:
- Simplify component code.
- Eliminate manual subscriptions.
- Improve app performance and readability.
You donβt need to migrate all at once. You can combine RxJS and Signals in the same app and migrate incrementally.
π Enjoyed this?
If you're thinking of migrating your Angular app to Signals, I hope this post gives you some real insight and confidence.
Drop a comment if you have questions or want to share your experience!
Top comments (0)