← Back to Chapters

State with Signals or RxJS

? State with Signals or RxJS

? Quick Overview

Angular allows state management using RxJS Observables or the newer Signals API (Angular 16+). Both provide reactive, declarative ways to share and update state across components efficiently.

? Key Concepts

  • RxJS State – Uses BehaviorSubject (or other subjects) to hold and stream state over time.
  • Observables – Components subscribe to observables and react whenever new values are emitted.
  • Signals – A newer reactive primitive in Angular 16+ that exposes state as simple functions.
  • Shared Services – State is usually stored in @Injectable() services to share between components.
  • Reactivity – UI updates automatically when the underlying state changes (either via streams or signals).

? Syntax & Theory

With RxJS, state is modeled as a stream of values. A BehaviorSubject holds the current value and emits new values whenever it updates. Components subscribe to this observable and update their view-model accordingly.

With Signals, the value is wrapped in a signal: a function-like primitive you can read with count() and update with methods such as set(). This removes the need for manual subscriptions and unsubscriptions in many cases.

In both approaches, keeping all state-related logic inside a dedicated service improves testability, reusability, and separation of concerns.

? Using RxJS for State

? View Code Example
// RxJS-based global state service
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class StateService {
  private countSource = new BehaviorSubject<number>(0);
  count$ = this.countSource.asObservable();

  increment() {
    this.countSource.next(this.countSource.value + 1);
  }

  decrement() {
    this.countSource.next(this.countSource.value - 1);
  }
}

? Using RxJS in Components

? View Code Example
// Counter component consuming RxJS state
import { Component } from '@angular/core';
import { StateService } from '../state.service';

@Component({
  selector: 'app-counter',
  template: `
    <p>Count: {{ count }}</p>
    <button (click)="increment()">+</button>
    <button (click)="decrement()">-</button>
  `
})
export class CounterComponent {
  count: number = 0;

  constructor(private stateService: StateService) {
    this.stateService.count$.subscribe(value => this.count = value);
  }

  increment() {
    this.stateService.increment();
  }

  decrement() {
    this.stateService.decrement();
  }
}

⚡ Using Signals for State (Angular 16+)

? View Code Example
// State service and component using Angular signals
import { Injectable, signal } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class SignalStateService {
  count = signal(0);

  increment() {
    this.count.set(this.count() + 1);
  }

  decrement() {
    this.count.set(this.count() - 1);
  }
}

import { Component, effect } from '@angular/core';
import { SignalStateService } from '../state.service';

@Component({
  selector: 'app-counter',
  template: `
    <p>Count: {{ state.count() }}</p>
    <button (click)="state.increment()">+</button>
    <button (click)="state.decrement()">-</button>
  `
})
export class CounterComponent {
  constructor(public state: SignalStateService) {}
}

? Live Output / Explanation

In both implementations, you end up with a simple counter UI:

  • The current count is displayed in a paragraph.
  • Clicking + calls increment(), which updates the shared state.
  • Clicking - calls decrement(), which decreases the shared state.

With RxJS, the component subscribes to count$ and receives updates via the observable stream. With Signals, the template reacts automatically whenever state.count() changes, without explicit subscriptions.

✅ Tips & Best Practices

  • Use RxJS when you need powerful stream operators and complex state transformations.
  • Prefer Signals for simpler, more declarative state management in Angular 16+ apps.
  • Keep state logic in dedicated services to centralize updates and avoid duplicated logic in components.
  • Avoid mutating state directly in components; always use the methods exposed by your state service.
  • For RxJS, remember to manage subscriptions (e.g., with async pipe or takeUntil) when needed.

? Try It Yourself

  • Implement a counter with RxJS and another with Signals, then compare the code size and readability.
  • Add multiple components that read and update the same shared counter state via the service.
  • Create a more advanced example (e.g., a todo list) and manage its state using both RxJS and Signals.
  • Experiment with combining multiple observables or multiple signals to derive computed state.