Angular State Management with RxJS: Complete Guide
RxJS is Angular's foundation for reactive programming. Learn how to manage application state using Observables, Subjects, BehaviorSubjects, operators, and patterns for building scalable Angular applications without external state management libraries.
When I first started building Angular applications, I thought I needed Redux or NgRx for state management. Then I realized that Angular is built on RxJS, and I could manage state effectively using just Observables, Subjects, and BehaviorSubjects. This approach is lighter, simpler, and works perfectly for most applications.
RxJS is Angular's foundation for reactive programming. It provides powerful tools for managing asynchronous data streams, which makes it perfect for state management. Using BehaviorSubject to store state, exposing Observables for components to subscribe to, and using RxJS operators for derived state, you can build robust state management without external libraries.
In this guide, I'll show you how I manage state in Angular applications using RxJS. We'll cover BehaviorSubject for storing state, creating state services, using RxJS operators (map, filter, distinctUntilChanged) for derived state, sharing state between components, handling async operations, and patterns for organizing state in larger applications. I'll also share when it makes sense to use this approach versus when you might need a more complex solution like NgRx.
BehaviorSubject for State
Create a state management service:
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
interface AppState {
user: any;
businesses: any[];
selectedBusiness: any;
}
@Injectable({
providedIn: 'root'
})
export class StateService {
private stateSubject = new BehaviorSubject<AppState>({
user: null,
businesses: [],
selectedBusiness: null
});
public state$ = this.stateSubject.asObservable();
getState(): AppState {
return this.stateSubject.value;
}
setState(partialState: Partial<AppState>): void {
this.stateSubject.next({
...this.stateSubject.value,
...partialState
});
}
// Specific state selectors
getUser$(): Observable<any> {
return this.state$.pipe(
map(state => state.user)
);
}
getBusinesses$(): Observable<any[]> {
return this.state$.pipe(
map(state => state.businesses)
);
}
}Using State in Components
Subscribe to state changes:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { StateService } from './state.service';
export class BusinessListComponent implements OnInit, OnDestroy {
businesses: any[] = [];
private subscription: Subscription;
constructor(private stateService: StateService) {}
ngOnInit(): void {
this.subscription = this.stateService.getBusinesses$().subscribe(
businesses => {
this.businesses = businesses;
}
);
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
selectBusiness(business: any): void {
this.stateService.setState({ selectedBusiness: business });
}
}RxJS Operators for State
Use operators for derived state:
import { map, filter, distinctUntilChanged, shareReplay } from 'rxjs/operators';
// Filtered businesses
getActiveBusinesses$(): Observable<any[]> {
return this.state$.pipe(
map(state => state.businesses),
map(businesses => businesses.filter(b => b.isActive)),
distinctUntilChanged(),
shareReplay(1)
);
}
// Combined state
getBusinessWithUser$(): Observable<any> {
return combineLatest([
this.getUser$(),
this.getBusinesses$()
]).pipe(
map(([user, businesses]) => ({
user,
userBusinesses: businesses.filter(b => b.userId === user.id)
}))
);
}
// Debounced search
searchBusinesses(term: string): Observable<any[]> {
return of(term).pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => this.businessService.search(term))
);
}Subject for Events
Use Subject for event streams:
import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class NotificationService {
private notificationsSubject = new Subject<any>();
public notifications$ = this.notificationsSubject.asObservable();
showNotification(message: string, type: 'success' | 'error' | 'info'): void {
this.notificationsSubject.next({ message, type, timestamp: Date.now() });
}
clearNotifications(): void {
this.notificationsSubject.next(null);
}
}
// Usage in component
export class MyComponent {
constructor(private notificationService: NotificationService) {}
save(): void {
// Save logic
this.notificationService.showNotification('Saved successfully', 'success');
}
}Best Practices
- Use BehaviorSubject for state that needs current value
- Use Subject for event streams and notifications
- Always unsubscribe from observables to prevent memory leaks
- Use async pipe in templates when possible
- Use operators like distinctUntilChanged to prevent unnecessary updates
- Use shareReplay for expensive computations
- Create specific selectors for derived state
- Keep state services focused and single-responsibility
- Use combineLatest for combining multiple state streams
- Document state structure and update patterns
Conclusion
RxJS provides powerful tools for state management in Angular. With BehaviorSubject, Subjects, and operators, you can build scalable state management solutions without external libraries. This approach works well for medium to large Angular applications.