RxState
Overview
RxState is a light-weight reactive state management service for managing local state in Angular.
Example
Component({
selector: 'app-stateful',
template: `<div>{{ state$ | async | json }}</div>`,
providers: [RxState],
});
export class StatefulComponent {
readonly state$ = this.state.select();
constructor(private state: RxState<{ foo: string }>) {}
}
Signature
class RxState<T extends object> implements OnDestroy, Subscribable<T> {
readonly $: Observable<T> = this.accumulator.signal$;
connect(inputOrSlice$: Observable<Partial<T> | V>, projectFn?: ProjectStateReducer<T, V>) => void;
connect(key: K, slice$: Observable<T[K]>) => void;
connect(key: K, input$: Observable<V>, projectSliceFn: ProjectValueReducer<T, K, V>) => void;
set(stateOrProjectState: Partial<T> | ProjectStateFn<T>) => void;
set(key: K, projectSlice: ProjectValueFn<T, K>) => void;
select(op: OperatorFunction<T, A>) => Observable<A>;
select(k1: K1) => Observable<T[K1]>;
select(k: K, fn: (val: T[K]) => V): Observable<V>;
select(keys: K[], fn: (slice: PickSlice<T, K>) => V, keyCompareMap?: KeyCompareMap<Pick<T, K>>): Observable<V>;
select() => Observable<T>;
get() => T;
get(k1: K1) => Partial<T>;
hold(obsOrObsWithSideEffect: Observable<S>, sideEffectFn?: (arg: S) => void) => void;
setAccumulator(accumulatorFn: AccumulationFn) => void;
}


$ (state observable)
typeof: Observable<T>
The unmodified state exposed as Observable<T>. It is not shared, distinct or gets replayed.
Use the $ property if you want to read the state without having applied stateful to it.
asReadOnly
typeof: Pick<RxState<State>, 'get' | 'select' | 'computed' | 'signal'>
Return RxState in ReadOnly mode that is exposing get(), select(), computed() and signal() methods. This can be helpful when you don't want others to write in your state.
const readOnlyState = state.asReadOnly();
const getNum = readOnlyState.get('num');
const selectNum$ = readOnlyState.select('num');
Trying to call any method that is not exposed in readOnlyState will throw an appropriate error
const readOnlyState = state.asReadOnly();
readOnlyState['set']('num', (state) => state.num + 1);
throwing -> readOnlyState.set is not a function
connect
Signature
connect(inputOrSlice$: Observable<Partial<T> | V>, projectFn?: ProjectStateReducer<T, V>): void
Connect an Observable<Partial<T>> to the state T.
Any change emitted by the source will get merged into the state.
Subscription handling is done automatically.
Example
const sliceToAdd$ = interval(250).pipe(mapTo({
bar: 5,
foo: 'foo'
});
state.connect(sliceToAdd$);
// every 250ms the properties bar and foo get updated due to the emission of sliceToAdd$
// Additionally you can provide a `projectionFunction` to access the current state object and do custom mappings.
const sliceToAdd$ = interval(250).pipe(mapTo({
bar: 5,
foo: 'foo'
});
state.connect(sliceToAdd$, (state, slice) => state.bar += slice.bar);
// every 250ms the properties bar and foo get updated due to the emission of sliceToAdd$. Bar will increase by
// 5 due to the projectionFunction
Signature
connect(key: K, slice$: Observable<T[K]>): void
Connect an Observable<T[K]> source to a specific property K in the state T. Any emitted change will update
this
specific property in the state.
Subscription handling is done automatically.
Example
const myTimer$ = interval(250);
state.connect('timer', myTimer$);
// every 250ms the property timer will get updated
Signature
connect(key: K, slice$: Observable<V>, projectSliceFn: ProjectValueReducer<T, K, V>): void
Connect an Observable<V> source to a specific property in the state. Additionally you can provide a
projectionFunction to access the current state object on every emission of your connected Observable.
Any change emitted by the source will get merged into the state.
Subscription handling is done automatically.
Example
const myTimer$ = interval(250);
state.connect('timer', myTimer$, (state, timerChange) => (state.timer += timerChange));
// every 250ms the property timer will get updated
set
Signature
set(stateOrProjectState: Partial<T> | ProjectStateFn<T>): void
Manipulate one or many properties of the state by providing a Partial<T> state or a ProjectionFunction<T>.
Example
// Update one or many properties of the state by providing a `Partial<T>`
const partialState = {
foo: 'bar',
bar: 5,
};
state.set(partialState);
// Update one or many properties of the state by providing a `ProjectionFunction<T>`
const reduceFn = (oldState) => ({
bar: oldState.bar + 5,
});
state.set(reduceFn);
Signature
set(key: K, projectSlice: ProjectValueFn<T, K>): void
Manipulate a single property of the state by the property name and a ProjectionFunction<T>.
Example
const reduceFn = (oldState) => oldState.bar + 5;
state.set('bar', reduceFn);
select
Signature
select(): Observable<T>
Returns the state as cached and distinct Observable<T>. This way you don't have to think about late
subscribers,
multiple subscribers or multiple emissions of the same value
Example
const state$ = state.select();
state$.subscribe((state) => doStuff(state));
Signature
select(op: OperatorFunction<T, A>): Observable<A>
Returns the state as cached and distinct Observable<A>. Accepts arbitrary
rxjs operators to enrich the selection with reactive composition.
Example
const profilePicture$ = state.select(
map((state) => state.profilePicture),
switchMap((profilePicture) => mapImageAsync(profilePicture)),
);
Signature
select(k1: K1): Observable<T[K1]>
Access a single property of the state by providing keys.
Returns a single property of the state as cached and distinct Observable<T[K1]>.
Example
// Access a single property
const bar$ = state.select('bar');
// Access a nested property
const foo$ = state.select('bar', 'foo');
Signature
select(k: K, fn: (val: T[K]) => V): Observable<V>;
Transform a single property of the state by providing a key and map function.
Returns result of applying function to state property as cached and distinct Observable<V>.
Example
// Project state based on single property
const foo$ = state.select('bar', (bar) => `bar equals ${bar}`);
Signature
select(keys: K[], fn: (slice: PickSlice<T, K>) => V, keyCompareMap?: KeyCompareMap<Pick<T, K>>): Observable<V>;
Transform a slice of the state by providing keys and map function.
Returns result of applying function to state slice as cached and distinct Observable<V>.
Example
// Project state slice
const text$ = state.select(['query', 'results'], ({ query, results }) => `${results.length} results found for "${query}"`);
get
Signature
get(): T
Read from the state in imperative manner. Returns the state object in its current state.
Example
const { disabled } = state.get();
if (!disabled) {
doStuff();
}
Signature
get(k1: K1): Partial<T>
Read from the state in an imperative manner by providing keys as parameters to reach deeply nested values. Returns the part of state object.
Example
interface State {
bar: { foo: `test` };
baz: true;
}
// Access a single property
const bar = state.get('bar');
// Access a nested property
const foo = state.get('bar', 'foo');
hold
Signature
hold(obsOrObsWithSideEffect: Observable<S>, sideEffectFn?: (arg: S) => void): void
Manages side-effects of your state. Provide an Observable<any> side-effect and an optional
sideEffectFunction.
Subscription handling is done automatically.
Example
// Directly pass an observable side-effect
const localStorageEffect$ = changes$.pipe(tap((changes) => storeChanges(changes)));
state.hold(localStorageEffect$);
// Pass an additional `sideEffectFunction`
const localStorageEffectFn = (changes) => storeChanges(changes);
state.hold(changes$, localStorageEffectFn);
setAccumulator
Signature
setAccumulator(accumulatorFn: AccumulationFn) => void
Allows to customize state accumulation function. This can be helpful to implement deep updates and tackle other immutability problems in a custom way.
Example
const myAccumulator = (state: MyState, slice: Partial<MyState>) => ({
...state,
...slice,
});
this.state.setAccumulator(myAccumulator);