If you’ve been an Angular developer for a while, you probably know the RxJS initiation ritual:
- First, you learn
Observable. - Then you learn
map,mergeMap,switchMap,concatMap, and (because life is cruel) the difference between them. - Finally, you question your career choices at 2 AM while debugging a subscription leak.
Sound familiar? You’re not alone.
But here’s the good news: Angular Signals are here (and by 2025, they’re everywhere). They let you replace a huge chunk of your RxJS boilerplate with something simpler, reactive, and dare I say… fun.
Let’s dive into what signals are, why they matter, and how you can start replacing heavy RxJS patterns the right way.
What Are Angular Signals (and Why Should You Care)?
At their core, signals are reactive values. Think of them like variables that automatically tell Angular when they’ve changed — so your components update without all the subscription gymnastics.
Here’s the simplest signal you’ll ever write:
import { signal } from '@angular/core';
const counter = signal(0);
console.log(counter()); // 0
counter.set(1);
console.log(counter()); // 1
Yes, you read that right. counter() returns the value, and set updates it. Simple. Reactive. No need to subscribe().
Why not just use RxJS?
RxJS is powerful, but for local, stateful values, it’s often overkill. It’s like using a space rocket to deliver a pizza. Signals, on the other hand, are lightweight and designed specifically for state management inside Angular apps.
Signals vs RxJS: The 2025 Perspective
Here’s a quick side-by-side for clarity:
| Feature | RxJS Observables | Angular Signals |
|---|---|---|
| Learning Curve | Steep (lots of operators, concepts) | Flat (get/set, computed) |
| Use Case | Async streams, websockets, events | Component state, UI reactivity |
| Boilerplate | High (subscriptions, operators) | Low (direct, reactive variables) |
| Debugging | Can be tricky | Easy (inspect like a normal variable) |
👉 The rule of thumb in 2025:
- Signals for component state, UI bindings, and derived values.
- RxJS for event streams, websockets, and advanced async flows.
Example 1: Goodbye BehaviorSubject
Before (RxJS):
import { BehaviorSubject } from 'rxjs';
const count$ = new BehaviorSubject(0);
count$.next(1);
count$.subscribe(value => console.log(value));
After (Signals):
import { signal } from '@angular/core';
const count = signal(0);
count.set(1);
console.log(count());
Notice how much simpler that is? No next(), no subscriptions, no mental overhead.
Example 2: Computed Signals (Derived State)
Before (RxJS):
import { BehaviorSubject, map } from 'rxjs';
const price$ = new BehaviorSubject(100);
const tax$ = price$.pipe(map(price => price * 0.2));
tax$.subscribe(tax => console.log(tax));
After (Signals):
import { signal, computed } from '@angular/core';
const price = signal(100);
const tax = computed(() => price() * 0.2);
console.log(tax()); // 20
Witty translation: RxJS is like solving algebra with a 200-page textbook. Signals? They’re like scribbling on a napkin and still getting it right.
Example 3: Reactive UI Without Subscriptions
Before (RxJS + AsyncPipe):
<div *ngIf="user$ | async as user">
Hello {{ user.name }}
</div>
After (Signals):
user = signal({ name: 'Alice' });
<div>
Hello {{ user().name }}
</div>
Simple. Direct. And it works without the AsyncPipe clutter.
When Not to Use Signals
Let’s not get carried away — RxJS isn’t dead. You still need it for:
- Websocket streams
- Debouncing/throttling user input
- Complex event orchestration
So think of signals as replacing RxJS in the “state management” lane, not the whole highway.
Real-World Patterns You Can Copy
- Component State
cartItems = signal<string[]>([]); addItem(item: string) { this.cartItems.update(items => [...items, item]); } - Form State Without NgRx
form = signal({ email: '', password: '' }); updateEmail(newEmail: string) { this.form.update(f => ({ ...f, email: newEmail })); } - Loading Indicators
isLoading = signal(false); async fetchData() { this.isLoading.set(true); await fetch('/api/data'); this.isLoading.set(false); }
These patterns cut 30–50% of boilerplate compared to RxJS-heavy setups.
Common Gotchas
- Forgetting to call signals: Remember,
counteris a function. Always usecounter()to read it. - Overusing signals: Don’t replace everything. Keep RxJS where streams shine.
- Migration pain: Large apps can switch gradually. Start with new components instead of rewriting everything at once.
Interactive Check-in 👀
- Have you ever written
takeUntil(this.destroy$)more times than you’d like to admit? - Would you trade 50 lines of RxJS operators for 10 lines of signals?
- If Angular gave you a way to write less boilerplate, why not use it?
Tell me in the comments: Which RxJS pattern are you most excited to replace with signals?
Final Thoughts
By 2025, Angular Signals aren’t just a shiny new feature — they’re the new normal. They let us:
- Write cleaner, simpler code.
- Avoid the infamous “subscription hell.”
- Still keep RxJS where it truly shines.
So the next time someone asks you, “Should we use RxJS or signals?”, you can confidently say:
“Both — but let’s stop using RxJS like duct tape for every problem.”
Leave a Reply