UNPKG

@rx-angular/state

Version:

@rx-angular/state is a light-weight, flexible, strongly typed and tested tool dedicated to reduce the complexity of managing component state and side effects in angular

504 lines 21.2 kB
import { Signal } from '@angular/core'; import { AccumulationFn, KeyCompareMap, PickSlice } from '@rx-angular/state/selections'; import { Observable, OperatorFunction, Subscribable, Unsubscribable } from 'rxjs'; import { SignalStateProxy } from './signal-state-proxy'; import * as i0 from "@angular/core"; export type ProjectStateFn<Type> = (oldState: Type) => Partial<Type>; export type ProjectValueFn<Type, Key extends keyof Type> = (oldState: Type) => Type[Key]; export type ProjectStateReducer<Type, Value> = (oldState: Type, value: Value) => Partial<Type>; export type ProjectValueReducer<Type, Key extends keyof Type, Value> = (oldState: Type, value: Value) => Type[Key]; export type ReadOnly = 'get' | 'select' | 'computed' | 'signal'; /** * @description * 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 }>) {} * } * * @docsCategory RxState * @docsPage RxState */ export declare class RxState<State extends object> implements Subscribable<State> { private subscription; protected scheduler: import("rxjs").SchedulerLike | "sync" | null; private accumulator; private effectObservable; private readonly injector; private signalStoreProxy; /** * @description * The unmodified state exposed as `Observable<State>`. It is not shared, distinct or gets replayed. * Use the `$` property if you want to read the state without having applied {@link stateful} to it. */ readonly $: Observable<State>; /** * @internal */ constructor(); /** * @description * * Return RxState in ReadOnly mode exposing only methods for reading state * get(), select(), computed() and signal() methods. * This can be helpful when you don't want others to write in your state. * * @example * ```typescript * const readOnlyState = state.asReadOnly(); * const getNum = state.get('num'); * const selectNum$ = state.select('num'); * ``` * * @return Pick<RxState<State>, ReadOnly> */ asReadOnly(): Pick<RxState<State>, ReadOnly>; /** * @description * * Allows to customize state accumulation function. * This can be helpful to implement deep updates and tackle other immutability problems in a custom way. * @example * * ```typescript * const myAccumulator = (state: MyState, slice: Partial<MyState>) => deepCopy(state, slice); * * this.state.setAccumulator(myAccumulator); * ``` * * @param {AccumulationFn} accumulatorFn * @return void * * @deprecated * Use `provideRxStateConfig` and provide the accumulator with the `withAccumulator` provider function. * Will be removed in future versions. */ setAccumulator(accumulatorFn: AccumulationFn): void; /** * @description * Read from the state in imperative manner. Returns the state object in its current state. * * @example * const { disabled } = state.get(); * if (!disabled) { * doStuff(); * } * * @return State */ get(): State; /** * @description * Read from the state in imperative manner by providing keys as parameters. * Returns the part of state object. * * @example * // Access a single property * const bar = state.get('bar'); * * // Access a nested property * const foo = state.get('bar', 'foo'); * * @param {KeyA} keyA * @return State[KeyA] */ get<KeyA extends keyof State>(keyA: KeyA): State[KeyA]; /** @internal **/ get<KeyA extends keyof State, KeyB extends keyof State[KeyA]>(keyA: KeyA, keyB: KeyB): State[KeyA][KeyB]; /** @internal **/ get<KeyA extends keyof State, KeyB extends keyof State[KeyA], KeyC extends keyof State[KeyA][KeyB]>(keyA: KeyA, keyB: KeyB, keyC: KeyC): State[KeyA][KeyB][KeyC]; /** @internal **/ get<KeyA extends keyof State, KeyB extends keyof State[KeyA], KeyC extends keyof State[KeyA][KeyB], KeyD extends keyof State[KeyA][KeyB][KeyC]>(keyA: KeyA, keyB: KeyB, keyC: KeyC, keyD: KeyD): State[KeyA][KeyB][KeyC][KeyD]; /** @internal **/ get<KeyA extends keyof State, KeyB extends keyof State[KeyA], KeyC extends keyof State[KeyA][KeyB], KeyD extends keyof State[KeyA][KeyB][KeyC], KeyE extends keyof State[KeyA][KeyB][KeyC][KeyD]>(keyA: KeyA, keyB: KeyB, keyC: KeyC, keyD: KeyD, keyE: KeyE): State[KeyA][KeyB][KeyC][KeyD][KeyE]; /** @internal **/ get<KeyA extends keyof State, KeyB extends keyof State[KeyA], KeyC extends keyof State[KeyA][KeyB], KeyD extends keyof State[KeyA][KeyB][KeyC], KeyE extends keyof State[KeyA][KeyB][KeyC][KeyD], KeyF extends keyof State[KeyA][KeyB][KeyC][KeyD][KeyE]>(keyA: KeyA, keyB: KeyB, keyC: KeyC, keyD: KeyD, keyE: KeyE, keyF: KeyF): State[KeyA][KeyB][KeyC][KeyD][KeyE][KeyF]; /** * @description * Manipulate one or many properties of the state by providing * a `Partial<State>`state or a `ProjectionFunction<State>`. * * @example * // Update one or many properties of the state by providing a `Partial<State>` * * const partialState = { * foo: 'bar', * bar: 5 * }; * state.set(partialState); * * // Update one or many properties of the state by providing a `ProjectionFunction<State>` * * const reduceFn = oldState => ({ * bar: oldState.bar + 5 * }); * state.set(reduceFn); * * @param {Partial<State>|ProjectStateFn<State>} stateOrProjectState * @return void */ set(stateOrProjectState: Partial<State> | ProjectStateFn<State>): void; /** * @description * Manipulate a single property of the state by the property name and a `ProjectionFunction<State>`. * * @example * const reduceFn = oldState => oldState.bar + 5; * state.set('bar', reduceFn); * * @param {Key} key * @param {ProjectValueFn<State, Key>} projectSlice * @return void */ set<Key extends keyof State, Object>(key: Key, projectSlice: ProjectValueFn<State, Key>): void; /** * @description * Connect an `Observable<Partial<State>>` to the state `State`. * 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 * * @param {Observable<Partial<State>>} inputOrSlice$ * @return void */ connect(inputOrSlice$: Observable<Partial<State>>): void; /** * @description * Connect a `Signal<Partial<State>>` to the state `State`. * Any change emitted by the source will get merged into the state. * * @example * const partialState = signal({ foo: 'foo', bar: 5 }); * state.connect(partialState); * * @param {Signal<Partial<State>>} signal * @return void */ connect(signal: Signal<Partial<State>>): void; /** * @description * Connect an `Observable<Value>` to the state `State`. * Any change emitted by the source will get forwarded to project function and merged into the state. * Subscription handling is done automatically. * * You have to provide a `projectionFunction` to access the current state object and do custom mappings. * * @example * const sliceToAdd$ = interval(250); * state.connect(sliceToAdd$, (type, value) => ({bar: value})); * // every 250ms the property bar get updated due to the emission of sliceToAdd$ * * @param {Observable<Value>} inputOrSlice$ * @param {ProjectStateReducer<State, Value>} projectFn * @return void */ connect<Value>(inputOrSlice$: Observable<Value>, projectFn: ProjectStateReducer<State, Value>): void; /** * @description * Connect a `Signal<Value>` to the state `State`. * Any change emitted by the source will get forwarded to the project function and merged into the state. * * You have to provide a `projectionFunction` to access the current state object and do custom mappings. * * @example * const signalSlice = signal(5); * state.connect(signalSlice, (type, value) => ({bar: value})); * * @param {Signal<Value>} signal * @param {ProjectStateReducer<State, Value>} projectFn * @return void */ connect<Value>(signal: Signal<Value>, projectFn: ProjectStateReducer<State, Value>): void; /** * * @description * Connect an `Observable<State[Key]>` source to a specific property `Key` in the state `State`. * 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 * @param {Key} key * @param {Observable<State[Key]>} slice$ * * @return void */ connect<Key extends keyof State>(key: Key, slice$: Observable<State[Key]>): void; /** * * @description * Connect a `Signal<State[Key]>` source to a specific property `Key` in the state `State`. * Any emitted change will update this specific property in the state. * * @example * const currentTime = signal(Date.now()) * state.connect('currentTime', currentTime); * * @param {Key} key * @param {Signal<State[Key]>} signal * * @return void */ connect<Key extends keyof State>(key: Key, signal: Signal<State[Key]>): void; /** * @description * Connect an `Observable<Value>` 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 * * @param {Key} key * @param {Observable<Value>} input$ * @param {ProjectValueReducer<State, Key, Value>} projectSliceFn * * @return void */ connect<Key extends keyof State, Value>(key: Key, input$: Observable<Value>, projectSliceFn: ProjectValueReducer<State, Key, Value>): void; /** * * @description * Connect a `Signal<Value>` 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 currentTime = signal(Date.now()) * state.connect('currentTime', currentTime, (state, currentTime) => state.currentTime = currentTime); * * @param {Key} key * @param {Signal<Value>} signal * @param {ProjectValueReducer<State, Key, Value>} projectSliceFn * * @return void */ connect<Key extends keyof State, Value>(key: Key, signal: Signal<Value>, projectSliceFn: ProjectValueReducer<State, Key, Value>): void; /** * @description * Returns the state as cached and distinct `Observable<Type>`. * 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)); * * @returns Observable<TType> */ select(): Observable<State>; /** * @description * Returns the state as cached and distinct `Observable<TypeA>`. Accepts arbitrary * [rxjs operators](https://rxjs-dev.firebaseapp.com/guide/operators) * to enrich the selection with reactive composition. * * @example * const profilePicture$ = state.select( * map((state) => state.profilePicture), * switchMap(profilePicture => mapImageAsync(profilePicture)) * ); * @param op { OperatorFunction<Type, TypeA> } * @returns Observable<TypeA> */ select<TypeA = State>(op: OperatorFunction<State, TypeA>): Observable<TypeA>; /** * @internal */ select<TypeA = State, TypeB = TypeA>(op1: OperatorFunction<State, TypeA>, op2: OperatorFunction<TypeA, TypeB>): Observable<TypeB>; /** * @internal */ select<TypeA = State, TypeB = TypeA, TypeC = TypeB>(op1: OperatorFunction<State, TypeA>, op2: OperatorFunction<TypeA, TypeB>, op3: OperatorFunction<TypeB, TypeC>): Observable<TypeC>; /** * @internal */ select<TypeA = State, TypeB = TypeA, TypeC = TypeB, TypeD = TypeC>(op1: OperatorFunction<State, TypeA>, op2: OperatorFunction<TypeA, TypeB>, op3: OperatorFunction<TypeB, TypeC>, op4: OperatorFunction<TypeC, TypeD>): Observable<TypeD>; /** * @internal */ select<TypeA = State, TypeB = TypeA, TypeC = TypeB, TypeD = TypeC, TypeE = TypeD>(op1: OperatorFunction<State, TypeA>, op2: OperatorFunction<TypeA, TypeB>, op3: OperatorFunction<TypeB, TypeC>, op4: OperatorFunction<TypeC, TypeD>, op5: OperatorFunction<TypeD, TypeE>): Observable<TypeE>; /** * @description * 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<Value>`. * * @example * // Project state slice * const text$ = state.select( * ['query', 'results'], * ({ query, results }) => `${results.length} results found for "${query}"` * ); * * @param {Key[]} keys * @param {(slice: PickSlice<Type, Key>) => Value} fn * @param {KeyCompareMap<Pick<Type, Key>>} keyCompareMap * * @return Observable<Value> */ select<Key extends keyof State, Value>(keys: Key[], fn?: (slice: PickSlice<State, Key>) => Value, keyCompareMap?: KeyCompareMap<Pick<State, Key>>): Observable<Value>; /** * @description * 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<Value>`. * * @example * // Project state based on single property * const foo$ = state.select('bar', bar => `bar equals ${bar}`); * * @param {Key} key * @param {(val: Type[Key]) => Value} fn * * @return Observable<Value> */ select<Key extends keyof State, Value>(key: Key, fn: (val: State[Key]) => Value): Observable<Value>; /** * @description * Access a single property of the state by providing keys. * Returns a single property of the state as cached and distinct `Observable<State[KeyA]>`. * * @example * // Access a single property * * const bar$ = state.select('bar'); * * // Access a nested property * * const foo$ = state.select('bar', 'foo'); * * @return Observable<Type[KeyA]> */ select<KeyA extends keyof State>(keyA: KeyA): Observable<State[KeyA]>; /** * @internal */ select<KeyA extends keyof State, KeyB extends keyof State[KeyA]>(keyA: KeyA, keyB: KeyB): Observable<State[KeyA][KeyB]>; /** * @internal */ select<KeyA extends keyof State, KeyB extends keyof State[KeyA], KeyC extends keyof State[KeyA][KeyB]>(keyA: KeyA, keyB: KeyB, keyC: KeyC): Observable<State[KeyA][KeyB][KeyC]>; /** * @internal */ select<KeyA extends keyof State, KeyB extends keyof State[KeyA], KeyC extends keyof State[KeyA][KeyB], KeyD extends keyof State[KeyA][KeyB][KeyC]>(keyA: KeyA, keyB: KeyB, keyC: KeyC, keyD: KeyD): Observable<State[KeyA][KeyB][KeyC][KeyD]>; /** * @internal */ select<KeyA extends keyof State, KeyB extends keyof State[KeyA], KeyC extends keyof State[KeyA][KeyB], KeyD extends keyof State[KeyA][KeyB][KeyC], KeyE extends keyof State[KeyA][KeyB][KeyC][KeyD]>(keyA: KeyA, keyB: KeyB, keyC: KeyC, keyD: KeyD, keyE: KeyE): Observable<State[KeyA][KeyB][KeyC][KeyD][KeyE]>; /** * @internal */ select<KeyA extends keyof State, KeyB extends keyof State[KeyA], KeyC extends keyof State[KeyA][KeyB], KeyD extends keyof State[KeyA][KeyB][KeyC], KeyE extends keyof State[KeyA][KeyB][KeyC][KeyD], KeyF extends keyof State[KeyA][KeyB][KeyC][KeyD][KeyE]>(keyA: KeyA, keyB: KeyB, keyC: KeyC, keyD: KeyD, keyE: KeyE, keyF: KeyF): Observable<State[KeyA][KeyB][KeyC][KeyD][KeyE][KeyF]>; /** * @description * Returns a signal of the given key. It's first value is determined by the * current keys value in RxState. Whenever the key gets updated, the signal * will also be updated accordingly. * * @example * const fooSignal = state.signal('foo'); * * @param {Key} key * * @return Signal<State[Key]> */ signal<Key extends keyof State>(key: Key): Signal<State[Key]>; /** * @description * Lets you create a computed signal based off multiple keys stored in RxState. * * @example * const computedSignal = state.computed((s) => s.foo + s.bar); * * @param {(slice: SignalStateProxy<Type>) => ComputedType} fn * @return Signal<ComputedType> */ computed<ComputedType>(fn: (slice: SignalStateProxy<State>) => ComputedType): Signal<ComputedType>; /** * @description * Lets you create a computed signal derived from state and rxjs operators. * * @throws If the initial value is not provided and the signal is not sync. * Use startWith() to provide an initial value. * * @example * const computedSignal = state.computedFrom( * map(state => state.foo), * filter(foo => foo > 5) * ); * * @param op1 { OperatorFunction<Type, TypeA> } * @returns Signal<TypeA> */ computedFrom<TypeA = State>(op1: OperatorFunction<State, TypeA>): Signal<TypeA>; /** @internal */ computedFrom<TypeA = State, TypeB = TypeA>(op1: OperatorFunction<State, TypeA>, op2: OperatorFunction<TypeA, TypeB>): Signal<TypeB>; /** @internal */ computedFrom<TypeA = State, TypeB = TypeA, TypeC = TypeB>(op1: OperatorFunction<State, TypeA>, op2: OperatorFunction<TypeA, TypeB>, op3: OperatorFunction<TypeB, TypeC>): Signal<TypeC>; /** @internal */ computedFrom<TypeA = State, TypeB = TypeA, TypeC = TypeB, TypeD = TypeC>(op1: OperatorFunction<State, TypeA>, op2: OperatorFunction<TypeA, TypeB>, op3: OperatorFunction<TypeB, TypeC>, op4: OperatorFunction<TypeC, TypeD>): Signal<TypeD>; /** @internal */ computedFrom<TypeA = State, TypeB = TypeA, TypeC = TypeB, TypeD = TypeC, TypeE = TypeD>(op1: OperatorFunction<State, TypeA>, op2: OperatorFunction<TypeA, TypeB>, op3: OperatorFunction<TypeB, TypeC>, op4: OperatorFunction<TypeC, TypeD>, op5: OperatorFunction<TypeD, TypeE>): Signal<TypeE>; /** * @description * 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); * * @param {Observable<SideEffect>} obsOrObsWithSideEffect * @param {function} [sideEffectFn] * * @return void */ hold<SideEffect>(obsOrObsWithSideEffect: Observable<SideEffect>, sideEffectFn?: (arg: SideEffect) => void): void; /** * @internal */ subscribe(): Unsubscribable; static ɵfac: i0.ɵɵFactoryDeclaration<RxState<any>, never>; static ɵprov: i0.ɵɵInjectableDeclaration<RxState<any>>; } //# sourceMappingURL=rx-state.service.d.ts.map