ngx-signal-state
Version:
Opinionated Microsized Simple State management library for Angular
147 lines (142 loc) • 5.54 kB
JavaScript
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