Using RxJS for State Management in Angular

Managing state is central to most Angular applications. RxJS is useful when that state arrives through asynchronous streams, especially when we need to combine events, cancel work or derive one stream from another. The operators help express those relationships; we still need clear ownership and subscription lifetimes.
Today, I intend to discuss RxJS and why it is a decent choice for state management in Angular. We'll look at practical examples of using Observables and Subjects, handling state clearly, and keeping your applications scalable and maintainable.
Why Use RxJS for State Management?
Angular integrates with RxJS, a library built around Observables. An Observable describes a source of values and how a subscriber receives them. That makes it useful for events and asynchronous data, as well as some shared‑state patterns.
Using RxJS, we can:
- Compose asynchronous events and data sources.
- Derive one stream from several inputs.
- Keep writable state behind deliberate update methods.
- Give subscriptions a clear owner and lifetime.
Understanding RxJS Basics Clearly
Before diving into examples, let's quickly summarise some key RxJS concepts that we'll use regularly:
Observables
An Observable can emit zero, one or many values, synchronously or asynchronously, then complete or fail. A subscription listens to those notifications. This small example emits two values and completes:
import { Observable } from 'rxjs';
const myObservable = new Observable<number>((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.complete();
});You can then subscribe to your observable to receive updates:
myObservable.subscribe(value => console.log(value));Subjects
Subjects extend Observables, letting you emit new values manually:
import { Subject } from 'rxjs';
const mySubject = new Subject<number>();
mySubject.subscribe(value => console.log('Subscriber received:', value));
mySubject.next(1); // Subscriber receives: 1
mySubject.next(2); // Subscriber receives: 2This makes subjects useful for managing state. A service can keep the writable subject private, expose an observable view for subscriptions, and provide methods for intentional updates.
Practical State Management in Angular with RxJS
Let's have a look at some practical ways to manage application state effectively using RxJS in Angular.
Simple State Example with Subjects
Imagine a service holding a local signed‑in indicator for the interface. This is UI state, not proof of an authenticated session. The following TypeScript example also shows a standalone component consuming it with AsyncPipe:
import { Component, inject, Injectable } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AuthService {
private readonly loggedInSubject = new BehaviorSubject<boolean>(false);
readonly loggedIn$ = this.loggedInSubject.asObservable();
login() {
this.loggedInSubject.next(true);
}
logout() {
this.loggedInSubject.next(false);
}
}
@Component({
selector: 'app-login-status',
standalone: true,
imports: [AsyncPipe],
template: `<p>{{ (auth.loggedIn$ | async) ? 'Signed in' : 'Signed out' }}</p>`,
})
export class LoginStatusComponent {
readonly auth = inject(AuthService);
}BehaviorSubject supplies its current value immediately on subscription. Here, AsyncPipe subscribes through the template, updates the view and unsubscribes when the component is destroyed. Calling login() only changes the local boolean: real authentication and authorisation must be checked by the server for protected data and operations.
Using Observables for Derived State
Observables let us derive complex state from simpler state streams easily, for example, by combining multiple streams into one:
import { combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
const isLoggedIn$ = authService.loggedIn$;
const userProfile$ = userService.userProfile$;
const userDetails$ = combineLatest([isLoggedIn$, userProfile$]).pipe(
map(([loggedIn, user]) => (loggedIn ? user : null))
);combineLatest waits until every input has emitted at least once. After that, an emission from either input produces a new combined value. The initial false from loggedIn$ is not enough on its own: userProfile$ must also emit before userDetails$ produces anything. Decide whether the UI needs an explicit loading state or an initial profile value.
Advantages of RxJS for State Management in Angular
RxJS offers several clear benefits when managing state in Angular apps:
- Derived state: express how a result depends on its input streams.
- Declarative composition: keep event relationships together instead of coordinating unrelated callbacks.
- Operators: choose behaviour such as debouncing, switching or combining sources deliberately.
- Angular integration: use
AsyncPipefor template subscriptions and provide teardown for subscriptions owned elsewhere.
How RxJS Compares with Alternatives
For synchronous UI state and derived values, Angular signals can be a simpler starting point. RxJS remains useful for asynchronous streams and their timing or cancellation rules. A dedicated store such as NgRx can add conventions when several teams need to coordinate state changes.
- Use signals when the main job is reading and updating synchronous UI values.
- Use RxJS where stream composition helps express events, requests or cancellation.
- Consider a store when explicit update conventions, tooling and shared ownership justify it.
These choices can coexist. Keep the boundary clear so a value does not acquire several competing owners merely because the application uses more than one reactive tool.
Wrapping Up
RxJS earns its place when the stream relationships make the code easier to understand. Start with the source of truth, define who can update it and decide who disposes of each subscription. Those choices matter more than the number of operators in the example.
Key Takeaways
- RxJS naturally integrates with Angular, providing reactive state management.
- Use Observables for data streams and Subjects for manual state updates.
- Clearly derived state streams help simplify complex application states.
- Choose between signals, RxJS and a store according to the state's behaviour and ownership, rather than project size alone.