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

FeatureBehaviorSubjectSignal
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:
  1. Cleaner code
  2. Better performance
  3. Easier maintenance
  4. Future-proof architecture