signalStateStore()
Creates a reactive state store backed by Angular Signals that implements the StateStore interface from @json-render/core.
Import
import { signalStateStore } from '@threadplane/render';Signature
function signalStateStore(initialState: StateModel = {}): StateStore;Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
initialState | StateModel | {} | Initial state object. StateModel is Record<string, unknown>. |
Returns
A StateStore object with the following interface:
interface StateStore {
get(path: string): unknown;
set(path: string, value: unknown): void;
update(updates: Record<string, unknown>): void;
getSnapshot(): StateModel;
subscribe(listener: () => void): () => void;
}Methods
get(path)
Reads a value from the store at the given JSON Pointer path.
const store = signalStateStore({ user: { name: 'Alice' }, items: ['a', 'b'] });
store.get('/user/name'); // 'Alice'
store.get('/user'); // { name: 'Alice' }
store.get('/items/0'); // 'a'
store.get('/missing'); // undefined| Parameter | Type | Description |
|---|---|---|
path | string | A JSON Pointer path (e.g., /user/name) |
Returns: The value at the path, or undefined if the path does not exist.
set(path, value)
Sets a single value at the given path. Performs an immutable update -- the path to the target is cloned, preserving unchanged branches. If the new value is referentially equal (===) to the current value, the update is skipped and no subscribers are notified.
store.set('/user/name', 'Bob');
store.get('/user/name'); // 'Bob'
// No-op if value hasn't changed
store.set('/user/name', 'Bob'); // no notification| Parameter | Type | Description |
|---|---|---|
path | string | A JSON Pointer path |
value | unknown | The new value to set |
update(updates)
Batch-sets multiple values in a single operation. Only triggers one subscriber notification, regardless of how many values change. If no values actually change, no notification is triggered.
store.update({
'/user/name': 'Charlie',
'/user/age': 25,
});| Parameter | Type | Description |
|---|---|---|
updates | Record<string, unknown> | A map of JSON Pointer paths to new values |
getSnapshot()
Returns the entire state object.
const store = signalStateStore({ x: 1, y: 2 });
store.getSnapshot(); // { x: 1, y: 2 }Returns: StateModel -- the current state object.
subscribe(listener)
Registers a callback that is invoked after every state mutation. Returns an unsubscribe function.
const unsubscribe = store.subscribe(() => {
console.log('State changed');
});
store.set('/count', 1); // logs: State changed
unsubscribe();
store.set('/count', 2); // no log| Parameter | Type | Description |
|---|---|---|
listener | () => void | Callback invoked after state changes |
Returns: () => void -- an unsubscribe function.
JSON Pointer Format
Paths follow the RFC 6901 JSON Pointer specification:
- Paths start with
/ - Each
/-separated segment traverses one level - Array elements are accessed by numeric index
- Escape sequences:
~0for~,~1for/
| Path | Description |
|---|---|
/name | Top-level name property |
/user/email | Nested property |
/items/0 | First array element |
/items/2/name | name property of third array element |
/a~1b | Property named a/b |
Reactive Behavior
The store wraps state in an Angular signal(). This means:
- Reading state via
get()accesses the signal, creating a reactive dependency in anycomputed()or template that calls it - Writing state via
set()orupdate()triggers the signal, which propagates through Angular's change detection RenderSpecComponentandRenderElementComponentusecomputed()signals that depend on the store, ensuring the UI updates automatically
// The library internally does this:
const resolvedInputs = computed(() => {
const ctx = { stateModel: store.getSnapshot() };
return resolveElementProps(el.props, ctx);
});
// When store.set() fires, the signal updates, resolvedInputs recomputes,
// and NgComponentOutlet receives new inputs.Array Handling
The store preserves array types when updating elements by index:
const store = signalStateStore({ items: ['a', 'b', 'c'] });
store.set('/items/1', 'B');
store.get('/items'); // ['a', 'B', 'c']
Array.isArray(store.get('/items')); // trueImmutable Updates
Every set() and update() call produces a new state object. Unchanged branches of the state tree are shared (structural sharing):
const store = signalStateStore({ a: { x: 1 }, b: { y: 2 } });
const before = store.getSnapshot();
store.set('/a/x', 10);
const after = store.getSnapshot();
before !== after; // true -- new root
before.b === after.b; // true -- b branch unchanged, same referenceRelated
- State Store Guide -- usage patterns, providing stores, and working with arrays
- Specs Guide -- how
$stateexpressions connect to the store - provideRender() -- providing a global store