In modern Angular (v16+), developers now have two powerful reactive tools
BehaviorSubject from RxJS
Signals, introduced natively in Angular 16
Both manage state — but they are built for different problems.
This article explains:
The conceptual difference
A real-world example (E-commerce Cart)
Performance implications
A modern hybrid solution (best practice in 2026)
Conceptual Difference
BehaviorSubject
A BehaviorSubject is:
A special type of Observable
Requires an initial value
Emits the latest value to new subscribers
Push-based stream
Designed for async flows
It’s ideal for:
HTTP responses
WebSocket data
Complex async transformations
Event streams
Signal
A Signal is:
A reactive value container
Pull-based
No subscription needed
Automatically tracked by Angular
Fine-grained reactive updates
It’s ideal for:
UI state
Derived state
Component state
Performance-sensitive rendering
Real-Time Example: E-Commerce Cart
Let’s imagine:
User adds products to cart
Cart total updates instantly
Products are loaded from API
UI must update efficiently
Version 1: Using BehaviorSubject (Classic Angular)
cart.service.ts
@Injectable({ providedIn: 'root' })
export class CartService {
private cartSubject = new BehaviorSubject<Product[]>([]);
cart$ = this.cartSubject.asObservable();
addToCart(product: Product) {
const current = this.cartSubject.value;
this.cartSubject.next([...current, product]);
}
getTotal(): number {
return this.cartSubject.value.reduce((sum, p) => sum + p.price, 0);
}
}cart.component.ts
export class CartComponent implements OnInit, OnDestroy {
cart: Product[] = [];
total = 0;
sub!: Subscription;
constructor(private cartService: CartService) {}
ngOnInit() {
this.sub = this.cartService.cart$.subscribe(cart => {
this.cart = cart;
this.total = cart.reduce((s, p) => s + p.price, 0);
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}Problems
Manuals subscribe/unsubscribe
Boilerplate
Whole component re-evaluates
Not fine-grained
Easy to create memory leaks
Version 2: Using Signals (Modern Angular)
cart.service.ts
@Injectable({ providedIn: 'root' })
export class CartService {
cart = signal<Product[]>([]);
total = computed(() =>
this.cart().reduce((sum, p) => sum + p.price, 0)
);
addToCart(product: Product) {
this.cart.update(items => [...items, product]);
}
}cart.component.ts
export class CartComponent {
constructor(public cartService: CartService) {}
}cart.component.html
<div *ngFor="let item of cartService.cart()">
{{ item.name }} - {{ item.price }}
</div>
<h3>Total: {{ cartService.total() }}</h3>Why Signals Win Here
No subscriptions
No memory leak risk
Computed total auto-updates
Only total re-renders when cart changes
Less code
Better performance
Performance Comparison
| Feature | BehaviorSubject | Signal |
|---|---|---|
| Change detection | Zone-based | Fine-grained |
| Subscription overhead | Yes | No |
| Memory leaks risk | Yes | No |
| Rendering optimization | Limited | Precise |
| Boilerplate | High | Low |
Important
BehaviorSubject triggers:
Emission → Subscription → Change detection
Signals trigger:
Dependency tracking → Only affected bindings re-render
This makes Signals significantly more efficient for UI state.
But Signals Are NOT a Replacement for RxJS
Now let’s extend our cart example.
Products come from API:
this.http.get<Product[]>('/api/products')We may need:
switchMap
debounceTime
retry
catchError
Signals cannot replace RxJS operators.
This is where hybrid architecture shines.
Hybrid Solution (Modern Best Practice)
Use:
RxJS for async streams
Signals for UI state
Example Hybrid Cart Service
@Injectable({ providedIn: 'root' })
export class CartService {
// RxJS for API stream
private products$ = this.http.get<Product[]>('/api/products');
// Convert observable to signal
products = toSignal(this.products$, { initialValue: [] });
// UI state as signal
cart = signal<Product[]>([]);
total = computed(() =>
this.cart().reduce((sum, p) => sum + p.price, 0)
);
addToCart(product: Product) {
this.cart.update(items => [...items, product]);
}
constructor(private http: HttpClient) {}
}Now:
API → handled by RxJS
UI state → handled by Signals
Templates stay clean
No subscriptions anywhere
High performance
Fully reactive
When To Use Which
Use Signals When:
Managing component state
Building dashboards
Forms
Derived UI values
Performance-sensitive UI
Working zoneless
Use BehaviorSubject When:
Handling async event streams
WebSockets
Complex operator chains
Interacting with legacy RxJS code
Advanced reactive flows
Use Hybrid When:
Building real-world applications
Fetching data from APIs
Managing UI state
Scaling modern Angular apps
Final Takeaway
BehaviorSubject = Reactive stream tool
Signals = Reactive UI state tool
They solve different problems.
Modern Angular applications should:
Use RxJS for async
Use Signals for state
Bridge them with toSignal()
That gives you:
Cleaner code
Better performance
Easier maintenance
Future-proof architecture

Join the conversation! Your thoughts help the community grow.