RxState
RxState is a light-weight reactive state management service for managing local state in Angular. This page documents the class-based API, the legacy surface. For new code, prefer the functional rxState() creator, which wraps this class and binds its lifecycle to the injection context automatically.
Every signature below is source-derived (package 21.1.1).
Legacy guidance — the functional rxState() creator wraps this class and manages its lifecycle for you. This page documents a legacy approach; class-based RxState / DI-style consumers may still need it. For new work, prefer the modern approach. See Reactive state: global vs local, RxState + signals for the full picture.
The class-based RxState<T> (via extends RxState or constructor DI + providers: [RxState]) is the legacy way to use RxState. Class-based RxState<T> is not formally deprecated, but the functional rxState() is the modern default. Reach for the class API only if you have existing DI-style consumers. For the mental model of when RxState fits at all, see Reactive state: global vs local, RxState + signals.
Import
import { RxState } from '@rx-angular/state';
Signature
RxState implements Subscribable<State> (not OnDestroy):
class RxState<State extends object> implements Subscribable<State> {
readonly $: Observable<State>;
// connect — 8 overloads (Observable + Signal sources)
connect(inputOrSlice$: Observable<Partial<State>>): void;
connect(signal: Signal<Partial<State>>): void;
connect<Value>(inputOrSlice$: Observable<Value>, projectFn: ProjectStateReducer<State, Value>): void;
connect<Value>(signal: Signal<Value>, projectFn: ProjectStateReducer<State, Value>): void;
connect<Key extends keyof State>(key: Key, slice$: Observable<State[Key]>): void;
connect<Key extends keyof State>(key: Key, signal: Signal<State[Key]>): void;
connect<Key extends keyof State, Value>(key: Key, input$: Observable<Value>, projectSliceFn: ProjectValueReducer<State, Key, Value>): void;
connect<Key extends keyof State, Value>(key: Key, signal: Signal<Value>, projectSliceFn: ProjectValueReducer<State, Key, Value>): void;
set(stateOrProjectState: Partial<State> | ProjectStateFn<State>): void;
set<Key extends keyof State, Object>(key: Key, projectSlice: ProjectValueFn<State, Key>): void;
select(): Observable<State>;
select<A>(op: OperatorFunction<State, A>): Observable<A>;
select<KeyA extends keyof State>(keyA: KeyA): Observable<State[KeyA]>;
select<Key extends keyof State, Value>(key: Key, fn: (val: State[Key]) => Value): Observable<Value>;
select<Key extends keyof State, Value>(keys: Key[], fn?: (slice: PickSlice<State, Key>) => Value, keyCompareMap?: KeyCompareMap<Pick<State, Key>>): Observable<Value>;
get(): State;
get<KeyA extends keyof State>(keyA: KeyA): State[KeyA];
signal<Key extends keyof State>(key: Key): Signal<State[Key]>;
computed<ComputedType>(fn: (slice: SignalStateProxy<State>) => ComputedType): Signal<ComputedType>;
computedFrom<TypeA = State>(op1: OperatorFunction<State, TypeA>): Signal<TypeA>;
asReadOnly(): Pick<RxState<State>, 'get' | 'select' | 'computed' | 'signal'>;
hold<SideEffect>(obsOrObsWithSideEffect: Observable<SideEffect>, sideEffectFn?: (arg: SideEffect) => void): void;
/** @deprecated use provideRxStateConfig(withAccumulatorFn(...)) */
setAccumulator(accumulatorFn: AccumulationFn): void;
}
Both
select,connect,computedFrom, andgetcarry additional deep-key / multi-operator overloads marked@internalin source; the public overloads are shown above.
Members
$ (state observable)
typeof: Observable<State>
The unmodified state exposed as Observable<State>. It is not shared, distinct, or replayed. Use $ to read the state without having stateful applied to it.
connect
Connect an Observable or Signal source to the state; every emission is merged in. Subscription handling is automatic. 8 overloads cover whole-slice source, single-key source, and single-key-with-projection, each in an Observable and a Signal variant.
// whole-slice observable
state.connect(sliceToAdd$);
// single key
state.connect('timer', interval(250));
// single-key signal
state.connect('currentTime', currentTimeSignal);
// single key with projection
state.connect('timer', interval(250), (s, tick) => s.timer + tick);
set
Write state by Partial<State>, a projection function, or a single key + value projection.
state.set({ foo: 'bar', bar: 5 });
state.set((oldState) => ({ bar: oldState.bar + 5 }));
state.set('bar', (oldState) => oldState.bar + 5);
select
Read state reactively as a cached, distinct Observable. Overloads: whole state, an arbitrary RxJS operator, a single key, a key + map function, and a keys-slice + map function (with optional KeyCompareMap).
const state$ = state.select();
const bar$ = state.select('bar');
const label$ = state.select('bar', (bar) => `bar equals ${bar}`);
See CompareFn and KeyCompareMap for custom key comparison.
get
Read state imperatively. get() is a one-shot, non-reactive snapshot: it creates no reactive dependency and never re-runs when state changes. Use it only in imperative code (event handlers, set()/connect() updater callbacks, tests). For template bindings or derived values, read reactively with signal, select, or computed instead.
get(): State;
get<KeyA extends keyof State>(keyA: KeyA): State[KeyA];
get('bar') returns State['bar'] (the exact key type, not Partial<State>).
const { disabled } = state.get();
const bar = state.get('bar');
signal
typeof: signal<Key extends keyof State>(key: Key): Signal<State[Key]>
Returns a Signal of the given key. Its initial value is the current key value; it updates whenever the key changes. This is the signals-first read surface.
readonly foo = this.state.signal('foo');
computed
Create a computed Signal from multiple keys via a signal-state proxy.
readonly total = this.state.computed((s) => s.foo + s.bar);
computedFrom
Create a computed Signal derived from state through RxJS operators. Throws if there is no synchronous initial value; use startWith() to seed one.
readonly bigFoo = this.state.computedFrom(
map((s) => s.foo),
filter((foo) => foo > 5),
);
asReadOnly
typeof: Pick<RxState<State>, 'get' | 'select' | 'computed' | 'signal'>
Return RxState in read-only mode, exposing only get(), select(), computed(), and signal(). Useful when you do not want consumers to write into your state. Calling a non-exposed method (e.g. set) throws.
const readOnlyState = state.asReadOnly();
const num = readOnlyState.get('num');
hold
Manage a side-effect. Provide an Observable side-effect and an optional sideEffectFn. Subscription handling is automatic. hold is not deprecated on the class.
state.hold(changes$, (changes) => storeChanges(changes));
hold()exists only on the class. On the functionalrxState()handle, userxEffects().register(...)instead.
setAccumulator
@deprecated: use provideRxStateConfig(withAccumulatorFn(...)) instead; will be removed in a future version.
setAccumulator(accumulatorFn: AccumulationFn): void
Customize the accumulation function (e.g. to implement deep updates).
Reading state in the signal era
On modern, zoneless apps prefer the signal accessors (signal('key'), computed(...), computedFrom(...)) for template bindings, and reserve $ / select() for RxJS pipelines. toSignal() also works on any select() observable.
Minimal example
import { Component } from '@angular/core';
import { rxState } from '@rx-angular/state';
@Component({
selector: 'app-stateful',
template: `{{ foo() }}`,
})
export class StatefulComponent {
// Modern default — the class API is documented here for legacy consumers.
private readonly state = rxState<{ foo: string }>(({ set }) => set({ foo: 'bar' }));
readonly foo = this.state.signal('foo');
}
See also
- Reference:
rxState(functional creator), the modern default. - Reference:
CompareFn·KeyCompareMap. - Concept: Reactive state: global vs local, RxState + signals.