UNPKG

ngx-signal-state

Version:

Opinionated Microsized Simple State management library for Angular

147 lines (142 loc) 5.54 kB
import * as i0 from '@angular/core'; import { computed, signal, effect, untracked, Injectable } from '@angular/core'; import { startWith, switchMap } from 'rxjs'; import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop'; const notInitializedError = 'Signal state is not initialized yet, call the initialize() method before using any other methods'; class SignalState { constructor() { this.triggers = {}; this.state = computed(() => { const signals = this.throwOrReturnSignals(); return Object.keys(signals).reduce((obj, key) => { obj[key] = signals[key](); return obj; }, {}); }); } /** * Initializes the state with default values * @param state: The complete initial state */ initialize(state) { const signals = {}; Object.keys(state).forEach((key) => signals[key] = signal(state[key])); this.signals = signals; } select(key, mappingFunction) { return computed(() => { const state = this.throwOrReturnSignals()[key](); return mappingFunction ? mappingFunction(state) : state; }); } selectMany(keys, mappingFunction) { return computed(() => { const signals = this.throwOrReturnSignals(); const state = keys.reduce((obj, key) => { obj[key] = signals[key](); return obj; }, {}); return mappingFunction ? mappingFunction(state) : state; }); } /** * This method is used to pick pieces of state from somewhere else * It will return an object that contains properties as signals. * Used best in combination with the connect method * @param keys: The keys that are related to the pieces of state we want to pick */ pick(keys) { const signals = this.throwOrReturnSignals(); return keys.reduce((obj, key) => { obj[key] = signals[key]; return obj; }, {}); } /** * Connects a partial state object where every property is a Signal. * It will connect all these signals to the state * This will automatically feed the state whenever one of the signals changes * It will use an Angular effect to calculate it * @param partial: The partial object holding the signals where we want to listen to */ connect(partial) { this.throwOrReturnSignals(); Object.keys(partial).forEach((key) => { effect(() => { const v = partial[key]; this.patch({ [key]: v() }); }, // This will update the state, so we need to allow signal writes { allowSignalWrites: true }); }); } /** * Connects a partial state object where every property is an RxJS Observable * It will connect all these observables to the state and clean up automatically * For every key a trigger will be registered that can be called by using the * `trigger()` method. The trigger will retrigger the producer function of the Observable in question * @param object */ connectObservables(partial) { this.throwOrReturnSignals(); Object.keys(partial).forEach((key) => { this.triggers[key] ||= signal(0); const obs$ = partial[key]; toObservable(this.triggers[key]) .pipe(startWith(), switchMap(() => obs$), takeUntilDestroyed()) .subscribe((v) => { this.patch({ [key]: v }); }); }); } /** * Retriggers the producer function of the Observable that is connected to this key * This only works in combination with the `connectObservables()` method. * @param key */ trigger(key) { if (!this.triggers[key]) { throw new Error('There is no trigger registered for this key! You need to connect an observable. ' + 'Please use connectObservables to register the triggers'); } this.triggers[key].update((v) => v + 1); } /** * Patches the state with a partial object. * This will loop through all the state signals and update * them one by one * @param partial: The partial state that needs to be updated */ patch(partial) { const signals = this.throwOrReturnSignals(); Object.keys(partial).forEach((key) => { signals[key].set(partial[key]); }); } /** * Returns the state as a snapshot * This will read through all the signals in an untracked manner */ get snapshot() { return untracked(() => this.state()); } throwOrReturnSignals() { if (!this.signals) { throw new Error(notInitializedError); } return this.signals; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: SignalState, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: SignalState }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: SignalState, decorators: [{ type: Injectable }] }); /* * Public API Surface of ngx-signal-state */ /** * Generated bundle index. Do not edit. */ export { SignalState }; //# sourceMappingURL=ngx-signal-state.mjs.map