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.
BehaviorSubject (or other subjects) to hold and stream state over time.@Injectable() services to share between components.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.
// 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);
}
}
// 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();
}
}
// 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) {}
}
In both implementations, you end up with a simple counter UI:
count is displayed in a paragraph.increment(), which updates the shared state.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.
async pipe or takeUntil) when needed.