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
Observables
andasync
pipes 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
NgRx
or 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)