@signal-tree/angular 15.1.2 → 15.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -2
- package/dist/lib/define-store.js +1 -1
- package/package.json +2 -2
- package/src/lib/define-store.d.ts +20 -9
package/README.md
CHANGED
|
@@ -23,8 +23,82 @@ the complete SignalTree facade for Angular applications: import `signalTree`,
|
|
|
23
23
|
markers, enhancers, and types from this package rather than mixing kernel
|
|
24
24
|
imports into Angular application code.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
## Application-store ownership
|
|
27
|
+
|
|
28
|
+
Use `defineStore(() => signalTree(...))` for an Angular-owned application store.
|
|
29
|
+
`signalTree` selects Angular reactivity and constructs the tree; `defineStore`
|
|
30
|
+
creates its injectable token and binds destruction to the providing injector.
|
|
31
|
+
The factory runs in injection context and must return a fresh owned object or
|
|
32
|
+
function, not a primitive or a tree borrowed from another owner. Cleanup errors
|
|
33
|
+
are reported with Angular's default `ErrorHandler` so later store cleanup can
|
|
34
|
+
still run. This does not resolve the application's custom handler: that handler
|
|
35
|
+
may itself depend on a store, and the owning injector is already destroyed when
|
|
36
|
+
teardown runs.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { inject } from '@angular/core';
|
|
40
|
+
import { defineStore, signalTree } from '@signal-tree/angular';
|
|
41
|
+
|
|
42
|
+
export const SettingsStore = defineStore(() => signalTree({ theme: 'light' }), { providedIn: 'root' });
|
|
43
|
+
|
|
44
|
+
// In an Angular injection context:
|
|
45
|
+
const settings = inject(SettingsStore);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Omit `providedIn` and add the token to a component's `providers` for a separate
|
|
49
|
+
store per component. A consumer borrowing an injected store does not destroy
|
|
50
|
+
it. Do not wrap a borrowed tree in another `defineStore`: that registers a
|
|
51
|
+
second owner. Root/request injectors must themselves be destroyed by the
|
|
52
|
+
application or SSR host at the end of their lifetime.
|
|
53
|
+
|
|
54
|
+
### Readonly state and operations share one owner
|
|
55
|
+
|
|
56
|
+
`expose: 'readonly'` narrows the token for **all** consumers, including an Ops
|
|
57
|
+
service. It does not create a separate writable injection path. When components
|
|
58
|
+
need readonly state and operations need writes, keep the owner token internal
|
|
59
|
+
and expose its readonly `$` through a non-owning Angular provider:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { inject, Injectable, InjectionToken, type Provider } from '@angular/core';
|
|
63
|
+
import { asReadonly, defineStore, signalTree } from '@signal-tree/angular';
|
|
64
|
+
|
|
65
|
+
const CounterTree = defineStore(() => signalTree({ count: 0 }));
|
|
66
|
+
|
|
67
|
+
@Injectable()
|
|
68
|
+
export class CounterOps {
|
|
69
|
+
private readonly tree = inject(CounterTree);
|
|
70
|
+
readonly state = asReadonly(this.tree).$;
|
|
71
|
+
|
|
72
|
+
increment(): void {
|
|
73
|
+
this.tree.$.count.update((count) => count + 1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const COUNTER_STATE = new InjectionToken<CounterOps['state']>('CounterState');
|
|
78
|
+
|
|
79
|
+
export function provideCounterStore(): Provider[] {
|
|
80
|
+
return [CounterTree, CounterOps, { provide: COUNTER_STATE, useFactory: () => inject(CounterOps).state }];
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Register `provideCounterStore()` at application bootstrap for one application
|
|
85
|
+
owner, or in a component's `providers` for independent local owners. Register
|
|
86
|
+
Ops and the reader together with the tree at the intended scope. Components use
|
|
87
|
+
`inject(COUNTER_STATE).count()` and `inject(CounterOps).increment()`; only
|
|
88
|
+
`CounterTree` owns teardown. The reader exposes neither writers nor `destroy()`.
|
|
89
|
+
Readonly remains a compile-time boundary, not runtime access control.
|
|
90
|
+
|
|
91
|
+
For stores where every injected consumer should be readonly, the existing
|
|
92
|
+
`expose: 'readonly'` option is sufficient. Prefer an inferred config literal or
|
|
93
|
+
`{ expose: 'readonly' } satisfies DefineStoreConfig`; widening to
|
|
94
|
+
`DefineStoreConfig` erases the information required to choose the readonly
|
|
95
|
+
return type.
|
|
96
|
+
|
|
97
|
+
## Tree construction
|
|
98
|
+
|
|
99
|
+
Inside the store factory, declare state, enhancers, and derived recipes together.
|
|
100
|
+
The following standalone example uses explicit cleanup; this lower-level form
|
|
101
|
+
also serves tests and intentionally manual lifetimes:
|
|
28
102
|
|
|
29
103
|
```ts
|
|
30
104
|
import { asReadonly, batching, entityMap, signalTree } from '@signal-tree/angular';
|
|
@@ -53,6 +127,7 @@ const tree = signalTree(
|
|
|
53
127
|
|
|
54
128
|
const reader = asReadonly(tree);
|
|
55
129
|
reader.$.selectedName();
|
|
130
|
+
tree.destroy(); // End the explicitly owned standalone example.
|
|
56
131
|
```
|
|
57
132
|
|
|
58
133
|
There is one construction grammar: state, enhancers, and one derived factory are
|
package/dist/lib/define-store.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{\u0275\u0275defineInjectable as __defineInjectable,inject,DestroyRef,ErrorHandler}from"@angular/core";function defineStore(factory,config={}){class SignalTreeStore{static \u0275prov=__defineInjectable({token:SignalTreeStore,providedIn:config.providedIn??null,factory:()=>new SignalTreeStore});constructor(){const tree=factory();if(tree===null||typeof tree!=="object"&&typeof tree!=="function"){throw new TypeError("[SignalTree] defineStore factory must return an object or function.")}inject(DestroyRef).onDestroy(()=>{try{const destroy=tree.destroy;if(typeof destroy==="function")destroy.call(tree)}catch(error){new ErrorHandler().handleError(error)}});return tree}}return SignalTreeStore}export{defineStore};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signal-tree/angular",
|
|
3
|
-
"version": "15.1.
|
|
3
|
+
"version": "15.1.4",
|
|
4
4
|
"description": "Angular realization for SignalTree.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"llms.txt"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@signal-tree/kernel": "15.1.
|
|
28
|
+
"@signal-tree/kernel": "15.1.4"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@angular/core": "^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
@@ -5,9 +5,9 @@ import type { ISignalTreeOf, ReadonlyStoreOf } from '@signal-tree/kernel/adapter
|
|
|
5
5
|
*/
|
|
6
6
|
export interface DefineStoreConfig {
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* provide it locally via a component/route `providers` array.
|
|
8
|
+
* `'root'` provides an application singleton. `'platform'` shares the store
|
|
9
|
+
* across applications on that platform; do not use it for request-local state.
|
|
10
|
+
* Omit/`null` to provide it locally via a component/route `providers` array.
|
|
11
11
|
*/
|
|
12
12
|
providedIn?: 'root' | 'platform' | null;
|
|
13
13
|
/**
|
|
@@ -21,9 +21,10 @@ export interface DefineStoreConfig {
|
|
|
21
21
|
* it does not protect against a deliberate `as any` bypass — it protects
|
|
22
22
|
* the common case where an AI agent or developer reaches for a
|
|
23
23
|
* `.set()`/mutator that simply isn't offered on the injected type. Use it
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* when every consumer of this token should receive a readonly tree. An Ops
|
|
25
|
+
* service injecting this same token is also readonly. For separate readers
|
|
26
|
+
* and writers, own one writable tree and expose a non-owning readonly `$`
|
|
27
|
+
* token; see the Angular package README's ownership example.
|
|
27
28
|
*
|
|
28
29
|
* Only accepted when the factory returns a real tree
|
|
29
30
|
* (`signalTree(...)`-shaped); combining it with
|
|
@@ -36,14 +37,24 @@ export interface DefineStoreConfig {
|
|
|
36
37
|
* idiomatic Angular DI pattern for a tree, comparable to NgRx SignalStore's
|
|
37
38
|
* `signalStore()`.
|
|
38
39
|
*
|
|
39
|
-
* `inject(MyStore)` resolves to the **real tree** —
|
|
40
|
+
* `inject(MyStore)` resolves to the **real tree** — with `$` and any
|
|
40
41
|
* configured enhancer methods — not a wrapper. The tree's
|
|
41
42
|
* `destroy()` is tied to the host injector's lifecycle via `DestroyRef`, so a
|
|
42
43
|
* component-provided store tears down with the component and a root store with
|
|
43
44
|
* the app.
|
|
44
45
|
*
|
|
45
46
|
* The factory runs inside Angular's injection context, so it may call `inject()`
|
|
46
|
-
* (e.g. to read other services).
|
|
47
|
+
* (e.g. to read other services). Return a fresh owned object or function for
|
|
48
|
+
* each providing injector, never a tree borrowed from another owner. The
|
|
49
|
+
* factory selects the realization: import `signalTree` from this Angular
|
|
50
|
+
* facade, not the neutral kernel. Use ordinary DI aliases for borrowed views;
|
|
51
|
+
* another `defineStore` would register another destruction owner.
|
|
52
|
+
*
|
|
53
|
+
* Primitive factory results are rejected. If the returned object has a
|
|
54
|
+
* `destroy()` method, failures are reported with Angular's default `ErrorHandler`,
|
|
55
|
+
* not resolved from DI: an application handler may itself depend on this store,
|
|
56
|
+
* and the owning injector is already destroyed when cleanup runs. Reporting
|
|
57
|
+
* allows sibling cleanup to continue. SignalTree destruction is idempotent.
|
|
47
58
|
*
|
|
48
59
|
* @example
|
|
49
60
|
* ```ts
|
|
@@ -90,6 +101,6 @@ export declare function defineStore<T, A>(factory: () => ISignalTreeOf<T, 'angul
|
|
|
90
101
|
* literal (or `as const`). Deliberate: this cliff is what makes
|
|
91
102
|
* `expose: 'readonly'` misuse a compile error instead of a silent no-op.
|
|
92
103
|
*/
|
|
93
|
-
export declare function defineStore<R>(factory: () => R, config?: DefineStoreConfig & {
|
|
104
|
+
export declare function defineStore<R extends object>(factory: () => R, config?: DefineStoreConfig & {
|
|
94
105
|
expose?: undefined;
|
|
95
106
|
}): Type<R>;
|