@ngxs/store 21.0.0-dev.master-f8041d1 → 21.0.0-dev.master-35d18c0
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/fesm2022/ngxs-store-operators.mjs +201 -34
- package/fesm2022/ngxs-store-operators.mjs.map +1 -1
- package/fesm2022/ngxs-store.mjs +42 -26
- package/fesm2022/ngxs-store.mjs.map +1 -1
- package/index.d.ts +7 -1
- package/operators/index.d.ts +180 -13
- package/package.json +1 -1
|
@@ -6,12 +6,28 @@ const isNumber = (value) => typeof value === 'number';
|
|
|
6
6
|
const invalidIndex = (index) => Number.isNaN(index) || index === -1;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Adds items to the end of an array without mutating the original. Handles
|
|
10
|
+
* the case where the array property does not exist yet, so callers do not
|
|
11
|
+
* need to initialise it before appending. A `null`, `undefined`, or empty
|
|
12
|
+
* `items` argument is treated as a no-op to allow safe pass-through of
|
|
13
|
+
* optional data.
|
|
14
|
+
*
|
|
15
|
+
* @param items - Items to add to the end of the array.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* // Add a new zebra to the end of the list without touching the rest of state.
|
|
20
|
+
* ctx.setState(
|
|
21
|
+
* patch<AnimalsStateModel>({
|
|
22
|
+
* zebras: append<string>([action.payload])
|
|
23
|
+
* })
|
|
24
|
+
* );
|
|
25
|
+
* ```
|
|
10
26
|
*/
|
|
11
27
|
function append(items) {
|
|
12
28
|
return function appendOperator(existing) {
|
|
13
|
-
//
|
|
14
|
-
//
|
|
29
|
+
// Nothing meaningful to append, so preserve the existing reference
|
|
30
|
+
// to avoid invalidating memoized selectors unnecessarily.
|
|
15
31
|
const itemsNotProvidedButExistingIs = (!items || !items.length) && existing;
|
|
16
32
|
if (itemsNotProvidedButExistingIs) {
|
|
17
33
|
return existing;
|
|
@@ -19,12 +35,29 @@ function append(items) {
|
|
|
19
35
|
if (isArray(existing)) {
|
|
20
36
|
return existing.concat(items);
|
|
21
37
|
}
|
|
22
|
-
//
|
|
23
|
-
//
|
|
38
|
+
// The array property was never initialised, so `items` becomes the
|
|
39
|
+
// initial state rather than being appended to a non-existent array.
|
|
24
40
|
return items;
|
|
25
41
|
};
|
|
26
42
|
}
|
|
27
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Chains multiple state operators so they execute left-to-right, each
|
|
46
|
+
* receiving the output of the previous one. Useful when several independent
|
|
47
|
+
* transformations must be applied to the same state slice in a single atomic
|
|
48
|
+
* update, avoiding multiple `setState` calls.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* // Apply two independent array mutations in one atomic setState call.
|
|
53
|
+
* ctx.setState(
|
|
54
|
+
* compose<AnimalsStateModel>(
|
|
55
|
+
* patch({ zebras: append<string>([action.zebraName]) }),
|
|
56
|
+
* patch({ pandas: removeItem<string>(name => name === action.pandaToRemove) })
|
|
57
|
+
* )
|
|
58
|
+
* );
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
28
61
|
function compose(...operators) {
|
|
29
62
|
return function composeOperator(existing) {
|
|
30
63
|
return operators.reduce((accumulator, operator) => operator(accumulator), existing);
|
|
@@ -32,32 +65,51 @@ function compose(...operators) {
|
|
|
32
65
|
}
|
|
33
66
|
|
|
34
67
|
function retrieveValue(operatorOrValue, existing) {
|
|
35
|
-
//
|
|
36
|
-
//
|
|
68
|
+
// Delegate to the operator so a derived transformation can be applied
|
|
69
|
+
// rather than substituting a static value.
|
|
37
70
|
if (isStateOperator(operatorOrValue)) {
|
|
38
71
|
const value = operatorOrValue(existing);
|
|
39
72
|
return value;
|
|
40
73
|
}
|
|
41
|
-
//
|
|
42
|
-
// e.g. `elseOperatorOrValue` is `undefined`
|
|
43
|
-
// then we just return an original value
|
|
74
|
+
// No else branch was provided, so leave the state unchanged.
|
|
44
75
|
if (operatorOrValue === undefined) {
|
|
45
76
|
return existing;
|
|
46
77
|
}
|
|
47
78
|
return operatorOrValue;
|
|
48
79
|
}
|
|
49
80
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* @param
|
|
81
|
+
* Applies one of two operators (or values) based on a condition, keeping
|
|
82
|
+
* conditional logic out of action handlers and inside the state mutation
|
|
83
|
+
* pipeline where it belongs.
|
|
84
|
+
*
|
|
85
|
+
* @param condition - A boolean or a predicate receiving the current state value.
|
|
86
|
+
* Use a predicate when the decision depends on the existing state rather than
|
|
87
|
+
* external data available at dispatch time.
|
|
88
|
+
* @param trueOperatorOrValue - Applied when `condition` is truthy.
|
|
89
|
+
* @param elseOperatorOrValue - Applied when `condition` is falsy. Omit to
|
|
90
|
+
* leave the state unchanged in the false branch.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* // Only add a panda when the list has fewer than 5 — the cap is enforced
|
|
95
|
+
* // inside the operator so the action handler stays free of branching logic.
|
|
96
|
+
* ctx.setState(
|
|
97
|
+
* patch<AnimalsStateModel>({
|
|
98
|
+
* pandas: iif(
|
|
99
|
+
* pandas => pandas.length < 5,
|
|
100
|
+
* append<string>([action.payload])
|
|
101
|
+
* )
|
|
102
|
+
* })
|
|
103
|
+
* );
|
|
104
|
+
* ```
|
|
55
105
|
*/
|
|
56
106
|
function iif(condition, trueOperatorOrValue, elseOperatorOrValue) {
|
|
57
107
|
return function iifOperator(existing) {
|
|
58
|
-
//
|
|
108
|
+
// Normalise to a boolean so both plain booleans and predicates
|
|
109
|
+
// share the same resolution path below.
|
|
59
110
|
let result = !!condition;
|
|
60
|
-
//
|
|
111
|
+
// Predicates receive the current state value so the decision can be
|
|
112
|
+
// based on live state rather than values captured at dispatch time.
|
|
61
113
|
if (isPredicate(condition)) {
|
|
62
114
|
result = condition(existing);
|
|
63
115
|
}
|
|
@@ -69,25 +121,51 @@ function iif(condition, trueOperatorOrValue, elseOperatorOrValue) {
|
|
|
69
121
|
}
|
|
70
122
|
|
|
71
123
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
124
|
+
* Inserts an item into an array without mutating the original, satisfying
|
|
125
|
+
* NGXS's immutability requirement. Handles the case where the array property
|
|
126
|
+
* does not exist yet, so callers do not need to initialise it first.
|
|
127
|
+
*
|
|
128
|
+
* @param value - The item to insert. A `null` or `undefined` value is a no-op
|
|
129
|
+
* so that callers can pass through optional data safely.
|
|
130
|
+
* @param beforePosition - Index before which to insert. Omit (or pass a
|
|
131
|
+
* non-positive number) to prepend to the beginning of the array.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* // Prepend a new zebra (no position = insert at index 0).
|
|
136
|
+
* ctx.setState(
|
|
137
|
+
* patch<AnimalsStateModel>({
|
|
138
|
+
* zebras: insertItem<string>(action.payload)
|
|
139
|
+
* })
|
|
140
|
+
* );
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* // Insert before index 2, shifting subsequent items right.
|
|
146
|
+
* ctx.setState(
|
|
147
|
+
* patch<AnimalsStateModel>({
|
|
148
|
+
* zebras: insertItem<string>(action.payload, 2)
|
|
149
|
+
* })
|
|
150
|
+
* );
|
|
151
|
+
* ```
|
|
74
152
|
*/
|
|
75
153
|
function insertItem(value, beforePosition) {
|
|
76
154
|
return function insertItemOperator(existing) {
|
|
77
|
-
//
|
|
78
|
-
//
|
|
155
|
+
// `== null` covers both `null` and `undefined` while letting falsy
|
|
156
|
+
// values like `0` or `false` through, where `!value` would not.
|
|
79
157
|
if (value == null && existing) {
|
|
80
158
|
return existing;
|
|
81
159
|
}
|
|
82
|
-
//
|
|
160
|
+
// The array property may not have been initialised yet; treat it as
|
|
161
|
+
// empty so callers don't have to guard against that case themselves.
|
|
83
162
|
if (!isArray(existing)) {
|
|
84
163
|
return [value];
|
|
85
164
|
}
|
|
86
165
|
const clone = existing.slice();
|
|
87
166
|
let index = 0;
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
// everything except number will return false here
|
|
167
|
+
// `> 0` rather than `>= 0` intentionally: non-numeric values coerce
|
|
168
|
+
// to NaN and fail this check, so no explicit `isNumber` call is needed.
|
|
91
169
|
if (beforePosition > 0) {
|
|
92
170
|
index = beforePosition;
|
|
93
171
|
}
|
|
@@ -96,6 +174,40 @@ function insertItem(value, beforePosition) {
|
|
|
96
174
|
};
|
|
97
175
|
}
|
|
98
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Applies a partial update to a state object, only cloning it when at least
|
|
179
|
+
* one property actually changes. This preserves referential equality for
|
|
180
|
+
* unchanged states, preventing unnecessary re-renders in `OnPush` components
|
|
181
|
+
* and keeping memoized selectors from recalculating.
|
|
182
|
+
*
|
|
183
|
+
* Each property in `patchObject` can itself be a state operator, enabling
|
|
184
|
+
* nested immutable updates without manually spreading every level of the tree.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* // Add an optional property to a state slice without touching existing ones.
|
|
189
|
+
* ctx.setState(
|
|
190
|
+
* patch<AnimalsStateModel>({ monkeys: [] })
|
|
191
|
+
* );
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* // Deep update — specify explicit types at each level so TypeScript can
|
|
197
|
+
* // catch property name mistakes in nested patches.
|
|
198
|
+
* ctx.setState(
|
|
199
|
+
* patch<AddressStateModel>({
|
|
200
|
+
* country: patch<AddressStateModel['country']>({
|
|
201
|
+
* city: patch<AddressStateModel['country']['city']>({
|
|
202
|
+
* address: patch<AddressStateModel['country']['city']['address']>({
|
|
203
|
+
* line1: action.line1
|
|
204
|
+
* })
|
|
205
|
+
* })
|
|
206
|
+
* })
|
|
207
|
+
* })
|
|
208
|
+
* );
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
99
211
|
function patch(patchObject) {
|
|
100
212
|
return function patchStateOperator(existing) {
|
|
101
213
|
let clone = null;
|
|
@@ -116,6 +228,24 @@ function patch(patchObject) {
|
|
|
116
228
|
};
|
|
117
229
|
}
|
|
118
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Like `patch`, but safe to call when the state slice is `null` or
|
|
233
|
+
* `undefined`. Treats a missing slice as an empty object so the patch is
|
|
234
|
+
* applied against a clean baseline rather than throwing. Useful for lazily
|
|
235
|
+
* initialised state properties or optional sub-states that may not have been
|
|
236
|
+
* set yet.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* // Update a nested preferences slice that starts as null — no prior
|
|
241
|
+
* // null-check needed; safePatch treats null as an empty object.
|
|
242
|
+
* ctx.setState(
|
|
243
|
+
* patch<UserStateModel>({
|
|
244
|
+
* preferences: safePatch<UserPreferences>({ theme: action.theme })
|
|
245
|
+
* })
|
|
246
|
+
* );
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
119
249
|
function safePatch(patchSpec) {
|
|
120
250
|
const patcher = patch(patchSpec);
|
|
121
251
|
return function patchSafely(existing) {
|
|
@@ -124,10 +254,30 @@ function safePatch(patchSpec) {
|
|
|
124
254
|
}
|
|
125
255
|
|
|
126
256
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
257
|
+
* Replaces or transforms a single array element without cloning elements that
|
|
258
|
+
* did not change, preserving referential equality for the rest of the array.
|
|
259
|
+
* Returns the original array reference when nothing changed, keeping
|
|
260
|
+
* memoized selectors and `OnPush` components from re-rendering unnecessarily.
|
|
261
|
+
*
|
|
262
|
+
* @param selector - The index to update, or a predicate used to locate the
|
|
263
|
+
* item. Prefer a predicate when the item's position may have shifted since the
|
|
264
|
+
* index was last known.
|
|
265
|
+
* @param operatorOrValue - The replacement value, or a state operator applied
|
|
266
|
+
* to the existing element when a derived update is needed.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* // Rename a panda — locate it by current name so the index doesn't need
|
|
271
|
+
* // to be known ahead of time.
|
|
272
|
+
* ctx.setState(
|
|
273
|
+
* patch<AnimalsStateModel>({
|
|
274
|
+
* pandas: updateItem<string>(
|
|
275
|
+
* name => name === action.payload.name,
|
|
276
|
+
* action.payload.newName
|
|
277
|
+
* )
|
|
278
|
+
* })
|
|
279
|
+
* );
|
|
280
|
+
* ```
|
|
131
281
|
*/
|
|
132
282
|
function updateItem(selector, operatorOrValue) {
|
|
133
283
|
return function updateItemOperator(existing) {
|
|
@@ -142,8 +292,8 @@ function updateItem(selector, operatorOrValue) {
|
|
|
142
292
|
return existing;
|
|
143
293
|
}
|
|
144
294
|
let value = null;
|
|
145
|
-
//
|
|
146
|
-
//
|
|
295
|
+
// Resolve the new value before touching the array so we can bail out
|
|
296
|
+
// early and skip the clone when nothing actually changed.
|
|
147
297
|
const theOperatorOrValue = operatorOrValue;
|
|
148
298
|
if (isStateOperator(theOperatorOrValue)) {
|
|
149
299
|
value = theOperatorOrValue(existing[index]);
|
|
@@ -151,8 +301,8 @@ function updateItem(selector, operatorOrValue) {
|
|
|
151
301
|
else {
|
|
152
302
|
value = theOperatorOrValue;
|
|
153
303
|
}
|
|
154
|
-
//
|
|
155
|
-
//
|
|
304
|
+
// Return the original reference to prevent memoized selectors and
|
|
305
|
+
// OnPush components from reacting to a no-op update.
|
|
156
306
|
if (value === existing[index]) {
|
|
157
307
|
return existing;
|
|
158
308
|
}
|
|
@@ -163,7 +313,24 @@ function updateItem(selector, operatorOrValue) {
|
|
|
163
313
|
}
|
|
164
314
|
|
|
165
315
|
/**
|
|
166
|
-
*
|
|
316
|
+
* Removes a single element from an array without mutating the original.
|
|
317
|
+
* Returns the original array reference when no matching item is found, so
|
|
318
|
+
* memoized selectors are not invalidated by a no-op removal.
|
|
319
|
+
*
|
|
320
|
+
* @param selector - The index to remove, or a predicate used to locate the
|
|
321
|
+
* item. Prefer a predicate when the item's position is not guaranteed to be
|
|
322
|
+
* stable across concurrent state updates.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* // Remove a panda by name — a predicate is safer than a hard-coded index
|
|
327
|
+
* // because the array order may change between dispatch and execution.
|
|
328
|
+
* ctx.setState(
|
|
329
|
+
* patch<AnimalsStateModel>({
|
|
330
|
+
* pandas: removeItem<string>(name => name === action.payload)
|
|
331
|
+
* })
|
|
332
|
+
* );
|
|
333
|
+
* ```
|
|
167
334
|
*/
|
|
168
335
|
function removeItem(selector) {
|
|
169
336
|
return function removeItemOperator(existing) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngxs-store-operators.mjs","sources":["../../../packages/store/operators/src/utils.ts","../../../packages/store/operators/src/append.ts","../../../packages/store/operators/src/compose.ts","../../../packages/store/operators/src/iif.ts","../../../packages/store/operators/src/insert-item.ts","../../../packages/store/operators/src/patch.ts","../../../packages/store/operators/src/safe-patch.ts","../../../packages/store/operators/src/update-item.ts","../../../packages/store/operators/src/remove-item.ts","../../../packages/store/operators/src/index.ts","../../../packages/store/operators/src/ngxs-store-operators.ts"],"sourcesContent":["import { StateOperator } from './types';\n\nexport const isArray = Array.isArray;\n\nexport type Predicate<T = any> = (value: T | Readonly<T>) => boolean;\n\nconst isFunction = (value: unknown) => typeof value == 'function';\n\nexport const isStateOperator = isFunction as <T>(\n value: T | StateOperator<T>\n) => value is StateOperator<T>;\n\nexport const isPredicate = isFunction as <T>(\n value: Predicate<T> | boolean | number\n) => value is Predicate<T>;\n\nexport const isNumber = (value: unknown): value is number => typeof value === 'number';\n\nexport const invalidIndex = (index: number) => Number.isNaN(index) || index === -1;\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isArray } from './utils';\n\n/**\n * @param items - Specific items to append to the end of an array\n */\nexport function append<T>(items: NoInfer<T[]>): StateOperator<T[]> {\n return function appendOperator(existing: ExistingState<T[]>): T[] {\n // If `items` is `undefined` or `null` or `[]` but `existing` is provided\n // just return `existing`\n const itemsNotProvidedButExistingIs = (!items || !items.length) && existing;\n if (itemsNotProvidedButExistingIs) {\n return existing as unknown as T[];\n }\n\n if (isArray(existing)) {\n return existing.concat(items as unknown as ExistingState<T[]>);\n }\n\n // For example if some property is added dynamically\n // and didn't exist before thus it's not `ArrayLike`\n return items as unknown as T[];\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\nexport function compose<T>(...operators: NoInfer<StateOperator<T>[]>): StateOperator<T> {\n return function composeOperator(existing: ExistingState<T>): T {\n return operators.reduce(\n (accumulator, operator) => operator(accumulator as ExistingState<T>),\n existing as T\n );\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\nimport { isStateOperator, isPredicate, Predicate } from './utils';\n\nfunction retrieveValue<T>(\n operatorOrValue: StateOperator<T> | T,\n existing: ExistingState<T>\n): T {\n // If state operator is a function\n // then call it with an original value\n if (isStateOperator(operatorOrValue)) {\n const value = operatorOrValue(existing);\n return value as T;\n }\n\n // If operator or value was not provided\n // e.g. `elseOperatorOrValue` is `undefined`\n // then we just return an original value\n if (operatorOrValue === undefined) {\n return existing as T;\n }\n\n return operatorOrValue as T;\n}\n\n/**\n * @param condition - Condition can be a plain boolean value or a function,\n * that returns boolean, also this function can take a value as an argument\n * to which this state operator applies\n * @param trueOperatorOrValue - Any value or a state operator\n * @param elseOperatorOrValue - Any value or a state operator\n */\nexport function iif<T>(\n condition: NoInfer<Predicate<T>> | boolean,\n trueOperatorOrValue: NoInfer<StateOperator<T> | T>,\n elseOperatorOrValue?: NoInfer<StateOperator<T> | T>\n): StateOperator<T> {\n return function iifOperator(existing: ExistingState<T>): T {\n // Convert the value to a boolean\n let result = !!condition;\n // but if it is a function then run it to get the result\n if (isPredicate(condition)) {\n result = condition(existing as T);\n }\n\n if (result) {\n return retrieveValue<T>(trueOperatorOrValue as StateOperator<T> | T, existing);\n }\n\n return retrieveValue<T>(elseOperatorOrValue! as StateOperator<T> | T, existing);\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isArray } from './utils';\n\n/**\n * @param value - Value to insert\n * @param [beforePosition] - Specified index to insert value before, optional\n */\nexport function insertItem<T>(value: NoInfer<T>, beforePosition?: number): StateOperator<T[]> {\n return function insertItemOperator(existing: ExistingState<T[]>): T[] {\n // Have to check explicitly for `null` and `undefined`\n // because `value` can be `0`, thus `!value` will return `true`\n if (value == null && existing) {\n return existing as T[];\n }\n\n // Property may be dynamic and might not existed before\n if (!isArray(existing)) {\n return [value as unknown as T];\n }\n\n const clone = existing.slice();\n\n let index = 0;\n\n // No need to call `isNumber`\n // as we are checking `> 0` not `>= 0`\n // everything except number will return false here\n if (beforePosition! > 0) {\n index = beforePosition!;\n }\n\n clone.splice(index, 0, value as unknown as T);\n return clone;\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isStateOperator } from './utils';\n\ntype NotUndefined<T> = T extends undefined ? never : T;\n\nexport type ɵPatchSpec<T> = { [P in keyof T]?: T[P] | StateOperator<NotUndefined<T[P]>> };\n\nexport function patch<T extends Record<string, any>>(\n patchObject: NoInfer<ɵPatchSpec<T>>\n): StateOperator<T> {\n return function patchStateOperator(existing: ExistingState<T>): T {\n let clone = null;\n for (const k in patchObject) {\n const newValue = patchObject[k];\n const existingPropValue = existing?.[k];\n const newPropValue = isStateOperator(newValue)\n ? newValue(<any>existingPropValue)\n : newValue;\n if (newPropValue !== existingPropValue) {\n if (!clone) {\n clone = { ...(<any>existing) };\n }\n clone[k] = newPropValue;\n }\n }\n return clone || existing;\n };\n}\n","import { patch, type ɵPatchSpec } from './patch';\nimport type { ExistingState, NoInfer, StateOperator } from './types';\n\nexport function safePatch<T extends object>(\n patchSpec: NoInfer<ɵPatchSpec<T>>\n): StateOperator<T> {\n const patcher = patch(patchSpec as ɵPatchSpec<T>) as unknown as StateOperator<\n Readonly<NonNullable<T>>\n >;\n return function patchSafely(existing: ExistingState<T>): T {\n return patcher(existing ?? ({} as ExistingState<Readonly<NonNullable<T>>>));\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\nimport { isStateOperator, isPredicate, isNumber, invalidIndex, Predicate } from './utils';\n\n/**\n * @param selector - Index of item in the array or a predicate function\n * that can be provided in `Array.prototype.findIndex`\n * @param operatorOrValue - New value under the `selector` index or a\n * function that can be applied to an existing value\n */\nexport function updateItem<T>(\n selector: number | NoInfer<Predicate<T>>,\n operatorOrValue: NoInfer<T> | NoInfer<StateOperator<T>>\n): StateOperator<T[]> {\n return function updateItemOperator(existing: ExistingState<T[]>): T[] {\n let index = -1;\n\n if (isPredicate(selector)) {\n index = existing.findIndex(selector as Predicate<T>);\n } else if (isNumber(selector)) {\n index = selector;\n }\n\n if (invalidIndex(index)) {\n return existing as T[];\n }\n\n let value: T = null!;\n // Need to check if the new item value will change the existing item value\n // then, only if it will change it then clone the array and set the item\n const theOperatorOrValue = operatorOrValue as T | StateOperator<T>;\n if (isStateOperator(theOperatorOrValue)) {\n value = theOperatorOrValue(existing[index] as ExistingState<T>);\n } else {\n value = theOperatorOrValue;\n }\n\n // If the value hasn't been mutated\n // then we just return `existing` array\n if (value === existing[index]) {\n return existing as T[];\n }\n\n const clone = existing.slice();\n clone[index] = value as T;\n return clone;\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isPredicate, isNumber, invalidIndex, Predicate } from './utils';\n\n/**\n * @param selector - index or predicate to remove an item from an array by\n */\nexport function removeItem<T>(selector: number | NoInfer<Predicate<T>>): StateOperator<T[]> {\n return function removeItemOperator(existing: ExistingState<T[]>): T[] {\n let index = -1;\n\n if (isPredicate(selector)) {\n index = existing.findIndex(selector);\n } else if (isNumber(selector)) {\n index = selector;\n }\n\n if (invalidIndex(index)) {\n return existing as T[];\n }\n\n const clone = existing.slice();\n clone.splice(index, 1);\n return clone;\n };\n}\n","/**\n * @module\n * @description\n * Entry point for all public APIs of this package.\n */\nexport { append } from './append';\nexport { compose } from './compose';\nexport { iif } from './iif';\nexport { insertItem } from './insert-item';\nexport { patch, type ɵPatchSpec } from './patch';\nexport { safePatch } from './safe-patch';\nexport { isStateOperator, isPredicate, type Predicate } from './utils';\nexport { updateItem } from './update-item';\nexport { removeItem } from './remove-item';\nexport type { ExistingState, NoInfer, StateOperator } from './types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngxs-store-operators.mjs","sources":["../../../packages/store/operators/src/utils.ts","../../../packages/store/operators/src/append.ts","../../../packages/store/operators/src/compose.ts","../../../packages/store/operators/src/iif.ts","../../../packages/store/operators/src/insert-item.ts","../../../packages/store/operators/src/patch.ts","../../../packages/store/operators/src/safe-patch.ts","../../../packages/store/operators/src/update-item.ts","../../../packages/store/operators/src/remove-item.ts","../../../packages/store/operators/src/index.ts","../../../packages/store/operators/src/ngxs-store-operators.ts"],"sourcesContent":["import { StateOperator } from './types';\n\nexport const isArray = Array.isArray;\n\nexport type Predicate<T = any> = (value: T | Readonly<T>) => boolean;\n\nconst isFunction = (value: unknown) => typeof value == 'function';\n\nexport const isStateOperator = isFunction as <T>(\n value: T | StateOperator<T>\n) => value is StateOperator<T>;\n\nexport const isPredicate = isFunction as <T>(\n value: Predicate<T> | boolean | number\n) => value is Predicate<T>;\n\nexport const isNumber = (value: unknown): value is number => typeof value === 'number';\n\nexport const invalidIndex = (index: number) => Number.isNaN(index) || index === -1;\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isArray } from './utils';\n\n/**\n * Adds items to the end of an array without mutating the original. Handles\n * the case where the array property does not exist yet, so callers do not\n * need to initialise it before appending. A `null`, `undefined`, or empty\n * `items` argument is treated as a no-op to allow safe pass-through of\n * optional data.\n *\n * @param items - Items to add to the end of the array.\n *\n * @example\n * ```ts\n * // Add a new zebra to the end of the list without touching the rest of state.\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * zebras: append<string>([action.payload])\n * })\n * );\n * ```\n */\nexport function append<T>(items: NoInfer<T[]>): StateOperator<T[]> {\n return function appendOperator(existing: ExistingState<T[]>): T[] {\n // Nothing meaningful to append, so preserve the existing reference\n // to avoid invalidating memoized selectors unnecessarily.\n const itemsNotProvidedButExistingIs = (!items || !items.length) && existing;\n if (itemsNotProvidedButExistingIs) {\n return existing as unknown as T[];\n }\n\n if (isArray(existing)) {\n return existing.concat(items as unknown as ExistingState<T[]>);\n }\n\n // The array property was never initialised, so `items` becomes the\n // initial state rather than being appended to a non-existent array.\n return items as unknown as T[];\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\n/**\n * Chains multiple state operators so they execute left-to-right, each\n * receiving the output of the previous one. Useful when several independent\n * transformations must be applied to the same state slice in a single atomic\n * update, avoiding multiple `setState` calls.\n *\n * @example\n * ```ts\n * // Apply two independent array mutations in one atomic setState call.\n * ctx.setState(\n * compose<AnimalsStateModel>(\n * patch({ zebras: append<string>([action.zebraName]) }),\n * patch({ pandas: removeItem<string>(name => name === action.pandaToRemove) })\n * )\n * );\n * ```\n */\nexport function compose<T>(...operators: NoInfer<StateOperator<T>[]>): StateOperator<T> {\n return function composeOperator(existing: ExistingState<T>): T {\n return operators.reduce(\n (accumulator, operator) => operator(accumulator as ExistingState<T>),\n existing as T\n );\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\nimport { isStateOperator, isPredicate, Predicate } from './utils';\n\nfunction retrieveValue<T>(\n operatorOrValue: StateOperator<T> | T,\n existing: ExistingState<T>\n): T {\n // Delegate to the operator so a derived transformation can be applied\n // rather than substituting a static value.\n if (isStateOperator(operatorOrValue)) {\n const value = operatorOrValue(existing);\n return value as T;\n }\n\n // No else branch was provided, so leave the state unchanged.\n if (operatorOrValue === undefined) {\n return existing as T;\n }\n\n return operatorOrValue as T;\n}\n\n/**\n * Applies one of two operators (or values) based on a condition, keeping\n * conditional logic out of action handlers and inside the state mutation\n * pipeline where it belongs.\n *\n * @param condition - A boolean or a predicate receiving the current state value.\n * Use a predicate when the decision depends on the existing state rather than\n * external data available at dispatch time.\n * @param trueOperatorOrValue - Applied when `condition` is truthy.\n * @param elseOperatorOrValue - Applied when `condition` is falsy. Omit to\n * leave the state unchanged in the false branch.\n *\n * @example\n * ```ts\n * // Only add a panda when the list has fewer than 5 — the cap is enforced\n * // inside the operator so the action handler stays free of branching logic.\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * pandas: iif(\n * pandas => pandas.length < 5,\n * append<string>([action.payload])\n * )\n * })\n * );\n * ```\n */\nexport function iif<T>(\n condition: NoInfer<Predicate<T>> | boolean,\n trueOperatorOrValue: NoInfer<StateOperator<T> | T>,\n elseOperatorOrValue?: NoInfer<StateOperator<T> | T>\n): StateOperator<T> {\n return function iifOperator(existing: ExistingState<T>): T {\n // Normalise to a boolean so both plain booleans and predicates\n // share the same resolution path below.\n let result = !!condition;\n // Predicates receive the current state value so the decision can be\n // based on live state rather than values captured at dispatch time.\n if (isPredicate(condition)) {\n result = condition(existing as T);\n }\n\n if (result) {\n return retrieveValue<T>(trueOperatorOrValue as StateOperator<T> | T, existing);\n }\n\n return retrieveValue<T>(elseOperatorOrValue! as StateOperator<T> | T, existing);\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isArray } from './utils';\n\n/**\n * Inserts an item into an array without mutating the original, satisfying\n * NGXS's immutability requirement. Handles the case where the array property\n * does not exist yet, so callers do not need to initialise it first.\n *\n * @param value - The item to insert. A `null` or `undefined` value is a no-op\n * so that callers can pass through optional data safely.\n * @param beforePosition - Index before which to insert. Omit (or pass a\n * non-positive number) to prepend to the beginning of the array.\n *\n * @example\n * ```ts\n * // Prepend a new zebra (no position = insert at index 0).\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * zebras: insertItem<string>(action.payload)\n * })\n * );\n * ```\n *\n * @example\n * ```ts\n * // Insert before index 2, shifting subsequent items right.\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * zebras: insertItem<string>(action.payload, 2)\n * })\n * );\n * ```\n */\nexport function insertItem<T>(value: NoInfer<T>, beforePosition?: number): StateOperator<T[]> {\n return function insertItemOperator(existing: ExistingState<T[]>): T[] {\n // `== null` covers both `null` and `undefined` while letting falsy\n // values like `0` or `false` through, where `!value` would not.\n if (value == null && existing) {\n return existing as T[];\n }\n\n // The array property may not have been initialised yet; treat it as\n // empty so callers don't have to guard against that case themselves.\n if (!isArray(existing)) {\n return [value as unknown as T];\n }\n\n const clone = existing.slice();\n\n let index = 0;\n\n // `> 0` rather than `>= 0` intentionally: non-numeric values coerce\n // to NaN and fail this check, so no explicit `isNumber` call is needed.\n if (beforePosition! > 0) {\n index = beforePosition!;\n }\n\n clone.splice(index, 0, value as unknown as T);\n return clone;\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isStateOperator } from './utils';\n\ntype NotUndefined<T> = T extends undefined ? never : T;\n\nexport type ɵPatchSpec<T> = { [P in keyof T]?: T[P] | StateOperator<NotUndefined<T[P]>> };\n\n/**\n * Applies a partial update to a state object, only cloning it when at least\n * one property actually changes. This preserves referential equality for\n * unchanged states, preventing unnecessary re-renders in `OnPush` components\n * and keeping memoized selectors from recalculating.\n *\n * Each property in `patchObject` can itself be a state operator, enabling\n * nested immutable updates without manually spreading every level of the tree.\n *\n * @example\n * ```ts\n * // Add an optional property to a state slice without touching existing ones.\n * ctx.setState(\n * patch<AnimalsStateModel>({ monkeys: [] })\n * );\n * ```\n *\n * @example\n * ```ts\n * // Deep update — specify explicit types at each level so TypeScript can\n * // catch property name mistakes in nested patches.\n * ctx.setState(\n * patch<AddressStateModel>({\n * country: patch<AddressStateModel['country']>({\n * city: patch<AddressStateModel['country']['city']>({\n * address: patch<AddressStateModel['country']['city']['address']>({\n * line1: action.line1\n * })\n * })\n * })\n * })\n * );\n * ```\n */\nexport function patch<T extends Record<string, any>>(\n patchObject: NoInfer<ɵPatchSpec<T>>\n): StateOperator<T> {\n return function patchStateOperator(existing: ExistingState<T>): T {\n let clone = null;\n for (const k in patchObject) {\n const newValue = patchObject[k];\n const existingPropValue = existing?.[k];\n const newPropValue = isStateOperator(newValue)\n ? newValue(<any>existingPropValue)\n : newValue;\n if (newPropValue !== existingPropValue) {\n if (!clone) {\n clone = { ...(<any>existing) };\n }\n clone[k] = newPropValue;\n }\n }\n return clone || existing;\n };\n}\n","import { patch, type ɵPatchSpec } from './patch';\nimport type { ExistingState, NoInfer, StateOperator } from './types';\n\n/**\n * Like `patch`, but safe to call when the state slice is `null` or\n * `undefined`. Treats a missing slice as an empty object so the patch is\n * applied against a clean baseline rather than throwing. Useful for lazily\n * initialised state properties or optional sub-states that may not have been\n * set yet.\n *\n * @example\n * ```ts\n * // Update a nested preferences slice that starts as null — no prior\n * // null-check needed; safePatch treats null as an empty object.\n * ctx.setState(\n * patch<UserStateModel>({\n * preferences: safePatch<UserPreferences>({ theme: action.theme })\n * })\n * );\n * ```\n */\nexport function safePatch<T extends object>(\n patchSpec: NoInfer<ɵPatchSpec<T>>\n): StateOperator<T> {\n const patcher = patch(patchSpec as ɵPatchSpec<T>) as unknown as StateOperator<\n Readonly<NonNullable<T>>\n >;\n return function patchSafely(existing: ExistingState<T>): T {\n return patcher(existing ?? ({} as ExistingState<Readonly<NonNullable<T>>>));\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\n\nimport { isStateOperator, isPredicate, isNumber, invalidIndex, Predicate } from './utils';\n\n/**\n * Replaces or transforms a single array element without cloning elements that\n * did not change, preserving referential equality for the rest of the array.\n * Returns the original array reference when nothing changed, keeping\n * memoized selectors and `OnPush` components from re-rendering unnecessarily.\n *\n * @param selector - The index to update, or a predicate used to locate the\n * item. Prefer a predicate when the item's position may have shifted since the\n * index was last known.\n * @param operatorOrValue - The replacement value, or a state operator applied\n * to the existing element when a derived update is needed.\n *\n * @example\n * ```ts\n * // Rename a panda — locate it by current name so the index doesn't need\n * // to be known ahead of time.\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * pandas: updateItem<string>(\n * name => name === action.payload.name,\n * action.payload.newName\n * )\n * })\n * );\n * ```\n */\nexport function updateItem<T>(\n selector: number | NoInfer<Predicate<T>>,\n operatorOrValue: NoInfer<T> | NoInfer<StateOperator<T>>\n): StateOperator<T[]> {\n return function updateItemOperator(existing: ExistingState<T[]>): T[] {\n let index = -1;\n\n if (isPredicate(selector)) {\n index = existing.findIndex(selector as Predicate<T>);\n } else if (isNumber(selector)) {\n index = selector;\n }\n\n if (invalidIndex(index)) {\n return existing as T[];\n }\n\n let value: T = null!;\n // Resolve the new value before touching the array so we can bail out\n // early and skip the clone when nothing actually changed.\n const theOperatorOrValue = operatorOrValue as T | StateOperator<T>;\n if (isStateOperator(theOperatorOrValue)) {\n value = theOperatorOrValue(existing[index] as ExistingState<T>);\n } else {\n value = theOperatorOrValue;\n }\n\n // Return the original reference to prevent memoized selectors and\n // OnPush components from reacting to a no-op update.\n if (value === existing[index]) {\n return existing as T[];\n }\n\n const clone = existing.slice();\n clone[index] = value as T;\n return clone;\n };\n}\n","import { ExistingState, NoInfer, StateOperator } from './types';\nimport { isPredicate, isNumber, invalidIndex, Predicate } from './utils';\n\n/**\n * Removes a single element from an array without mutating the original.\n * Returns the original array reference when no matching item is found, so\n * memoized selectors are not invalidated by a no-op removal.\n *\n * @param selector - The index to remove, or a predicate used to locate the\n * item. Prefer a predicate when the item's position is not guaranteed to be\n * stable across concurrent state updates.\n *\n * @example\n * ```ts\n * // Remove a panda by name — a predicate is safer than a hard-coded index\n * // because the array order may change between dispatch and execution.\n * ctx.setState(\n * patch<AnimalsStateModel>({\n * pandas: removeItem<string>(name => name === action.payload)\n * })\n * );\n * ```\n */\nexport function removeItem<T>(selector: number | NoInfer<Predicate<T>>): StateOperator<T[]> {\n return function removeItemOperator(existing: ExistingState<T[]>): T[] {\n let index = -1;\n\n if (isPredicate(selector)) {\n index = existing.findIndex(selector);\n } else if (isNumber(selector)) {\n index = selector;\n }\n\n if (invalidIndex(index)) {\n return existing as T[];\n }\n\n const clone = existing.slice();\n clone.splice(index, 1);\n return clone;\n };\n}\n","/**\n * @module\n * @description\n * Entry point for all public APIs of this package.\n */\nexport { append } from './append';\nexport { compose } from './compose';\nexport { iif } from './iif';\nexport { insertItem } from './insert-item';\nexport { patch, type ɵPatchSpec } from './patch';\nexport { safePatch } from './safe-patch';\nexport { isStateOperator, isPredicate, type Predicate } from './utils';\nexport { updateItem } from './update-item';\nexport { removeItem } from './remove-item';\nexport type { ExistingState, NoInfer, StateOperator } from './types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/fesm2022/ngxs-store.mjs
CHANGED
|
@@ -1056,22 +1056,48 @@ function simplePatch(value) {
|
|
|
1056
1056
|
};
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
1059
|
+
class StateContextDestroyedError extends Error {
|
|
1060
|
+
path;
|
|
1061
|
+
name = 'StateContextDestroyedError';
|
|
1062
|
+
constructor(path) {
|
|
1063
|
+
super(typeof ngDevMode !== 'undefined' && ngDevMode
|
|
1064
|
+
? `Attempted to interact with state after the injector has been destroyed. State path: "${path}". ` +
|
|
1065
|
+
`This can happen in server-side rendering when the app is destroyed before all async operations complete, ` +
|
|
1066
|
+
`e.g. inside a finalize() operator that runs after the injector has been destroyed.`
|
|
1067
|
+
: '');
|
|
1068
|
+
this.path = path;
|
|
1069
|
+
Object.setPrototypeOf(this, StateContextDestroyedError.prototype);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1059
1072
|
/**
|
|
1060
|
-
*
|
|
1073
|
+
* Creates `StateContext` instances scoped to a specific state path.
|
|
1074
|
+
* Each action handler receives one of these contexts so it can read and
|
|
1075
|
+
* write only its own slice of the global state tree.
|
|
1061
1076
|
* @ignore
|
|
1062
1077
|
*/
|
|
1063
1078
|
class StateContextFactory {
|
|
1064
1079
|
_injector = inject(EnvironmentInjector);
|
|
1065
1080
|
_internalStateOperations = inject(InternalStateOperations);
|
|
1081
|
+
// Resolved lazily via `setErrorHandler` to avoid a cyclic dependency when
|
|
1082
|
+
// the ErrorHandler itself injects the Store.
|
|
1083
|
+
_errorHandler = null;
|
|
1066
1084
|
/**
|
|
1067
1085
|
* Create the state context
|
|
1068
1086
|
*/
|
|
1069
1087
|
createStateContext(path, abortSignal) {
|
|
1070
1088
|
const injector = this._injector;
|
|
1089
|
+
const errorHandler = this._errorHandler;
|
|
1071
1090
|
const root = this._internalStateOperations.getRootStateOperations();
|
|
1072
1091
|
return {
|
|
1073
1092
|
abortSignal,
|
|
1074
1093
|
getState() {
|
|
1094
|
+
if (injector.destroyed) {
|
|
1095
|
+
// Only report — do not return early. Returning `undefined as T` would
|
|
1096
|
+
// be a breaking change for callers that assume getState() always
|
|
1097
|
+
// returns a value. handleError is just for observability (e.g. Rollbar);
|
|
1098
|
+
// the state data is still readable even after the injector is gone.
|
|
1099
|
+
errorHandler?.handleError(new StateContextDestroyedError(path));
|
|
1100
|
+
}
|
|
1075
1101
|
const currentAppState = root.getState();
|
|
1076
1102
|
return getState(currentAppState, path);
|
|
1077
1103
|
},
|
|
@@ -1079,15 +1105,7 @@ class StateContextFactory {
|
|
|
1079
1105
|
// If the injector has been destroyed (e.g. app destroyed mid-action),
|
|
1080
1106
|
// skip the state update to avoid writing to completed subjects.
|
|
1081
1107
|
if (injector.destroyed) {
|
|
1082
|
-
|
|
1083
|
-
// concern on the server, and silently dropping state mutations is
|
|
1084
|
-
// critical enough to always surface in server logs.
|
|
1085
|
-
if ((typeof ngDevMode !== 'undefined' && ngDevMode) ||
|
|
1086
|
-
(typeof ngServerMode !== 'undefined' && ngServerMode)) {
|
|
1087
|
-
console.warn(`Attempted to patchState after injector has been destroyed. Value: ${JSON.stringify(value)}, state path: "${path}". ` +
|
|
1088
|
-
`This can happen in server-side rendering when the app is destroyed before all async operations complete, ` +
|
|
1089
|
-
`e.g. inside a finalize() operator that runs after the injector has been destroyed.`);
|
|
1090
|
-
}
|
|
1108
|
+
errorHandler?.handleError(new StateContextDestroyedError(path));
|
|
1091
1109
|
return;
|
|
1092
1110
|
}
|
|
1093
1111
|
const currentAppState = root.getState();
|
|
@@ -1097,15 +1115,7 @@ class StateContextFactory {
|
|
|
1097
1115
|
setState(value) {
|
|
1098
1116
|
// Same guard as patchState — no-op if the injector is already destroyed.
|
|
1099
1117
|
if (injector.destroyed) {
|
|
1100
|
-
|
|
1101
|
-
// concern on the server, and silently dropping state mutations is
|
|
1102
|
-
// critical enough to always surface in server logs.
|
|
1103
|
-
if ((typeof ngDevMode !== 'undefined' && ngDevMode) ||
|
|
1104
|
-
(typeof ngServerMode !== 'undefined' && ngServerMode)) {
|
|
1105
|
-
console.warn(`Attempted to setState after injector has been destroyed. Value: ${JSON.stringify(value)}, state path: "${path}". ` +
|
|
1106
|
-
`This can happen in server-side rendering when the app is destroyed before all async operations complete, ` +
|
|
1107
|
-
`e.g. inside a finalize() operator that runs after the injector has been destroyed.`);
|
|
1108
|
-
}
|
|
1118
|
+
errorHandler?.handleError(new StateContextDestroyedError(path));
|
|
1109
1119
|
return;
|
|
1110
1120
|
}
|
|
1111
1121
|
const currentAppState = root.getState();
|
|
@@ -1121,6 +1131,14 @@ class StateContextFactory {
|
|
|
1121
1131
|
}
|
|
1122
1132
|
};
|
|
1123
1133
|
}
|
|
1134
|
+
/** @internal */
|
|
1135
|
+
setErrorHandler() {
|
|
1136
|
+
// Called from `LifecycleStateManager.ngxsBootstrap` rather than at
|
|
1137
|
+
// construction time. If we injected ErrorHandler in the constructor,
|
|
1138
|
+
// an ErrorHandler that itself injects Store would create a circular
|
|
1139
|
+
// dependency — deferring the lookup breaks the cycle.
|
|
1140
|
+
this._errorHandler ??= this._injector.get(ErrorHandler);
|
|
1141
|
+
}
|
|
1124
1142
|
/** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: StateContextFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1125
1143
|
/** @nocollapse */ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: StateContextFactory, providedIn: 'root' });
|
|
1126
1144
|
}
|
|
@@ -1131,13 +1149,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
|
|
|
1131
1149
|
function setStateValue(root, currentAppState, newValue, path) {
|
|
1132
1150
|
const newAppState = setValue(currentAppState, path, newValue);
|
|
1133
1151
|
root.setState(newAppState);
|
|
1152
|
+
// Note: this returns the full app state rather than just the patched slice.
|
|
1153
|
+
// That's a long-standing quirk (present since the original state-factory.ts)
|
|
1154
|
+
// and fixing it would be a breaking change — tracked for a future release.
|
|
1134
1155
|
return newAppState;
|
|
1135
|
-
// In doing this refactoring I noticed that there is a 'bug' where the
|
|
1136
|
-
// application state is returned instead of this state slice.
|
|
1137
|
-
// This has worked this way since the beginning see:
|
|
1138
|
-
// https://github.com/ngxs/store/blame/324c667b4b7debd8eb979006c67ca0ae347d88cd/src/state-factory.ts
|
|
1139
|
-
// This needs to be fixed, but is a 'breaking' change.
|
|
1140
|
-
// I will do this fix in a subsequent PR and we can decide how to handle it.
|
|
1141
1156
|
}
|
|
1142
1157
|
function setStateFromOperator(root, currentAppState, stateOperator, path) {
|
|
1143
1158
|
const local = getState(currentAppState, path);
|
|
@@ -1769,6 +1784,7 @@ class LifecycleStateManager {
|
|
|
1769
1784
|
inject(DestroyRef).onDestroy(() => this._abortController.abort());
|
|
1770
1785
|
}
|
|
1771
1786
|
ngxsBootstrap(action, results) {
|
|
1787
|
+
this._stateContextFactory.setErrorHandler();
|
|
1772
1788
|
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
|
1773
1789
|
if (action instanceof InitState) {
|
|
1774
1790
|
this._initStateHasBeenDispatched = true;
|
|
@@ -2687,5 +2703,5 @@ function ɵprovideNgxsInternalStateTokens() {
|
|
|
2687
2703
|
* Generated bundle index. Do not edit.
|
|
2688
2704
|
*/
|
|
2689
2705
|
|
|
2690
|
-
export { Action, ActionDirector, ActionStatus, Actions, AsyncReturnType, NgxsConfig, NgxsDevelopmentModule, NgxsModule, NgxsSimpleChange, NgxsUnhandledActionsLogger, NgxsUnhandledErrorHandler, Select, Selector, SelectorOptions, State, Store, createDispatchMap, createModelSelector, createPickSelector, createPropertySelectors, createSelectMap, createSelector, dispatch, lazyProvider, ofAction, ofActionCanceled, ofActionCompleted, ofActionDispatched, ofActionErrored, ofActionSuccessful, provideStates, provideStore, registerNgxsPlugin, select, withNgxsDevelopmentOptions, withNgxsNoopExecutionStrategy, withNgxsPendingTasks, withNgxsPlugin, withNgxsPreboot, NgxsFeatureModule as ɵNgxsFeatureModule, NgxsRootModule as ɵNgxsRootModule, ɵprovideNgxsInternalStateTokens };
|
|
2706
|
+
export { Action, ActionDirector, ActionStatus, Actions, AsyncReturnType, NgxsConfig, NgxsDevelopmentModule, NgxsModule, NgxsSimpleChange, NgxsUnhandledActionsLogger, NgxsUnhandledErrorHandler, Select, Selector, SelectorOptions, State, StateContextDestroyedError, Store, createDispatchMap, createModelSelector, createPickSelector, createPropertySelectors, createSelectMap, createSelector, dispatch, lazyProvider, ofAction, ofActionCanceled, ofActionCompleted, ofActionDispatched, ofActionErrored, ofActionSuccessful, provideStates, provideStore, registerNgxsPlugin, select, withNgxsDevelopmentOptions, withNgxsNoopExecutionStrategy, withNgxsPendingTasks, withNgxsPlugin, withNgxsPreboot, NgxsFeatureModule as ɵNgxsFeatureModule, NgxsRootModule as ɵNgxsRootModule, ɵprovideNgxsInternalStateTokens };
|
|
2691
2707
|
//# sourceMappingURL=ngxs-store.mjs.map
|