Skip to main content

@rx-angular/state

Signals first

For local component state, Angular signals are the default — reach for signal(), computed(), and linkedSignal() first. RxState complements signals; it earns its place for global/shared state, complex derived state, and async-heavy orchestration (multi-source connect, actions, effects) bridged into signals — not as a replacement for signals. See Reactive state: global vs local, RxState + signals.

@rx-angular/state (RxState) is a lightweight library for managing reactive component-level state in Angular: components, services, and directives. It gives you a small, strongly-typed API (set, connect, select, signal, computed) with automatic subscription handling, so you can merge multiple asynchronous sources into one state container and read it from the template as signals or observables.

Signals first: where RxState fits

Modern Angular ships built-in reactivity: signal(), computed(), linkedSignal(), and toSignal(). For local component state, those primitives are the default; reach for them first. RxState is a complement to signals, useful when state grows past what a handful of signals express cleanly:

  • Global / shared state: a providedIn: 'root' service holding a state container that many components read.
  • Complex derived state: several slices combined and memoized, especially when the inputs are asynchronous.
  • Async-heavy orchestration: multi-source connect, plus actions and effects coordinating streams, bridged into signals for the template via .signal().

For the full mental model of when state is local vs. global and how RxState sits alongside signals, see Reactive state: global vs local, RxState + signals.

When not to reach for RxState

If a component only needs a couple of independent pieces of local state and some values derived from them, plain signal() + computed() is simpler and has no library dependency: use it, and skip RxState.

Install

npm install @rx-angular/state

Quick look

The functional rxState() API creates and configures a state container bound to the current injection context. It is destroyed automatically with its host.

import { Component, inject } from '@angular/core';
import { rxState } from '@rx-angular/state';
import { MovieResource } from './movie.resource';

@Component({
selector: 'app-movie-list',
template: `
@for (movie of movies(); track movie.id) {
<app-movie [movie]="movie" />
}
`,
})
export class MovieListComponent {
private readonly movieResource = inject(MovieResource);

private readonly state = rxState<{ movies: Movie[] }>(({ set, connect }) => {
set({ movies: [] });
connect('movies', this.movieResource.fetchMovies());
});

// read state in the template as a signal
protected readonly movies = this.state.signal('movies');
}

Where to go next