
March 5, 2026 06:56 by
Peter
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