@angular-architects/ngrx-toolkit 19.2.3 → 19.4.0

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.
Files changed (123) hide show
  1. package/eslint.config.cjs +43 -0
  2. package/jest.config.ts +22 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +4 -21
  5. package/project.json +37 -0
  6. package/redux-connector/docs/README.md +131 -0
  7. package/redux-connector/index.ts +6 -0
  8. package/redux-connector/ng-package.json +5 -0
  9. package/redux-connector/src/lib/create-redux.ts +102 -0
  10. package/redux-connector/src/lib/model.ts +89 -0
  11. package/redux-connector/src/lib/rxjs-interop/redux-method.ts +66 -0
  12. package/redux-connector/src/lib/signal-redux-store.ts +59 -0
  13. package/redux-connector/src/lib/util.ts +22 -0
  14. package/{index.d.ts → src/index.ts} +36 -4
  15. package/src/lib/assertions/assertions.ts +9 -0
  16. package/{lib/devtools/features/with-disabled-name-indicies.d.ts → src/lib/devtools/features/with-disabled-name-indicies.ts} +5 -1
  17. package/{lib/devtools/features/with-glitch-tracking.d.ts → src/lib/devtools/features/with-glitch-tracking.ts} +6 -1
  18. package/{lib/devtools/features/with-mapper.d.ts → src/lib/devtools/features/with-mapper.ts} +7 -1
  19. package/src/lib/devtools/internal/current-action-names.ts +1 -0
  20. package/src/lib/devtools/internal/default-tracker.ts +60 -0
  21. package/src/lib/devtools/internal/devtools-feature.ts +37 -0
  22. package/src/lib/devtools/internal/devtools-syncer.service.ts +202 -0
  23. package/src/lib/devtools/internal/glitch-tracker.service.ts +61 -0
  24. package/src/lib/devtools/internal/models.ts +29 -0
  25. package/{lib/devtools/provide-devtools-config.d.ts → src/lib/devtools/provide-devtools-config.ts} +16 -4
  26. package/src/lib/devtools/rename-devtools-name.ts +21 -0
  27. package/src/lib/devtools/tests/action-name.spec.ts +48 -0
  28. package/src/lib/devtools/tests/basic.spec.ts +111 -0
  29. package/src/lib/devtools/tests/connecting.spec.ts +37 -0
  30. package/src/lib/devtools/tests/helpers.spec.ts +43 -0
  31. package/src/lib/devtools/tests/naming.spec.ts +216 -0
  32. package/src/lib/devtools/tests/provide-devtools-config.spec.ts +25 -0
  33. package/src/lib/devtools/tests/types.spec.ts +19 -0
  34. package/src/lib/devtools/tests/update-state.spec.ts +29 -0
  35. package/src/lib/devtools/tests/with-devtools.spec.ts +5 -0
  36. package/src/lib/devtools/tests/with-glitch-tracking.spec.ts +272 -0
  37. package/src/lib/devtools/tests/with-mapper.spec.ts +69 -0
  38. package/src/lib/devtools/update-state.ts +38 -0
  39. package/{lib/devtools/with-dev-tools-stub.d.ts → src/lib/devtools/with-dev-tools-stub.ts} +2 -1
  40. package/src/lib/devtools/with-devtools.ts +81 -0
  41. package/src/lib/flattening-operator.ts +42 -0
  42. package/src/lib/immutable-state/deep-freeze.ts +43 -0
  43. package/src/lib/immutable-state/is-dev-mode.ts +6 -0
  44. package/src/lib/immutable-state/tests/with-immutable-state.spec.ts +260 -0
  45. package/src/lib/immutable-state/with-immutable-state.ts +115 -0
  46. package/src/lib/mutation/http-mutation.spec.ts +473 -0
  47. package/src/lib/mutation/http-mutation.ts +172 -0
  48. package/src/lib/mutation/mutation.ts +26 -0
  49. package/src/lib/mutation/rx-mutation.spec.ts +594 -0
  50. package/src/lib/mutation/rx-mutation.ts +208 -0
  51. package/src/lib/shared/prettify.ts +3 -0
  52. package/{lib/shared/signal-store-models.d.ts → src/lib/shared/signal-store-models.ts} +8 -4
  53. package/src/lib/shared/throw-if-null.ts +7 -0
  54. package/src/lib/storage-sync/features/with-indexed-db.ts +81 -0
  55. package/src/lib/storage-sync/features/with-local-storage.ts +58 -0
  56. package/src/lib/storage-sync/internal/indexeddb.service.ts +124 -0
  57. package/src/lib/storage-sync/internal/local-storage.service.ts +19 -0
  58. package/src/lib/storage-sync/internal/models.ts +62 -0
  59. package/src/lib/storage-sync/internal/session-storage.service.ts +18 -0
  60. package/src/lib/storage-sync/tests/indexeddb.service.spec.ts +99 -0
  61. package/src/lib/storage-sync/tests/with-storage-async.spec.ts +308 -0
  62. package/src/lib/storage-sync/tests/with-storage-sync.spec.ts +268 -0
  63. package/src/lib/storage-sync/with-storage-sync.ts +233 -0
  64. package/src/lib/with-call-state.spec.ts +42 -0
  65. package/src/lib/with-call-state.ts +195 -0
  66. package/src/lib/with-conditional.spec.ts +125 -0
  67. package/{lib/with-conditional.d.ts → src/lib/with-conditional.ts} +31 -7
  68. package/src/lib/with-data-service.spec.ts +564 -0
  69. package/src/lib/with-data-service.ts +433 -0
  70. package/src/lib/with-feature-factory.spec.ts +69 -0
  71. package/{lib/with-feature-factory.d.ts → src/lib/with-feature-factory.ts} +32 -4
  72. package/src/lib/with-mutations.spec.ts +537 -0
  73. package/src/lib/with-mutations.ts +146 -0
  74. package/src/lib/with-pagination.spec.ts +90 -0
  75. package/src/lib/with-pagination.ts +353 -0
  76. package/src/lib/with-redux.spec.ts +258 -0
  77. package/src/lib/with-redux.ts +387 -0
  78. package/src/lib/with-reset.spec.ts +112 -0
  79. package/src/lib/with-reset.ts +62 -0
  80. package/src/lib/with-undo-redo.spec.ts +287 -0
  81. package/src/lib/with-undo-redo.ts +199 -0
  82. package/src/test-setup.ts +8 -0
  83. package/tsconfig.json +29 -0
  84. package/tsconfig.lib.json +17 -0
  85. package/tsconfig.lib.prod.json +9 -0
  86. package/tsconfig.spec.json +17 -0
  87. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs +0 -119
  88. package/fesm2022/angular-architects-ngrx-toolkit-redux-connector.mjs.map +0 -1
  89. package/fesm2022/angular-architects-ngrx-toolkit.mjs +0 -1787
  90. package/fesm2022/angular-architects-ngrx-toolkit.mjs.map +0 -1
  91. package/lib/assertions/assertions.d.ts +0 -2
  92. package/lib/devtools/internal/current-action-names.d.ts +0 -1
  93. package/lib/devtools/internal/default-tracker.d.ts +0 -13
  94. package/lib/devtools/internal/devtools-feature.d.ts +0 -24
  95. package/lib/devtools/internal/devtools-syncer.service.d.ts +0 -35
  96. package/lib/devtools/internal/glitch-tracker.service.d.ts +0 -18
  97. package/lib/devtools/internal/models.d.ts +0 -24
  98. package/lib/devtools/rename-devtools-name.d.ts +0 -7
  99. package/lib/devtools/update-state.d.ts +0 -15
  100. package/lib/devtools/with-devtools.d.ts +0 -24
  101. package/lib/immutable-state/deep-freeze.d.ts +0 -11
  102. package/lib/immutable-state/is-dev-mode.d.ts +0 -1
  103. package/lib/immutable-state/with-immutable-state.d.ts +0 -60
  104. package/lib/shared/throw-if-null.d.ts +0 -1
  105. package/lib/storage-sync/features/with-indexed-db.d.ts +0 -2
  106. package/lib/storage-sync/features/with-local-storage.d.ts +0 -3
  107. package/lib/storage-sync/internal/indexeddb.service.d.ts +0 -29
  108. package/lib/storage-sync/internal/local-storage.service.d.ts +0 -8
  109. package/lib/storage-sync/internal/models.d.ts +0 -45
  110. package/lib/storage-sync/internal/session-storage.service.d.ts +0 -8
  111. package/lib/storage-sync/with-storage-sync.d.ts +0 -45
  112. package/lib/with-call-state.d.ts +0 -58
  113. package/lib/with-data-service.d.ts +0 -109
  114. package/lib/with-pagination.d.ts +0 -98
  115. package/lib/with-redux.d.ts +0 -147
  116. package/lib/with-reset.d.ts +0 -29
  117. package/lib/with-undo-redo.d.ts +0 -31
  118. package/redux-connector/index.d.ts +0 -2
  119. package/redux-connector/src/lib/create-redux.d.ts +0 -13
  120. package/redux-connector/src/lib/model.d.ts +0 -40
  121. package/redux-connector/src/lib/rxjs-interop/redux-method.d.ts +0 -14
  122. package/redux-connector/src/lib/signal-redux-store.d.ts +0 -11
  123. package/redux-connector/src/lib/util.d.ts +0 -5
@@ -0,0 +1,433 @@
1
+ import { ProviderToken, Signal, computed, inject } from '@angular/core';
2
+ import {
3
+ EmptyFeatureResult,
4
+ SignalStoreFeature,
5
+ WritableStateSource,
6
+ patchState,
7
+ signalStoreFeature,
8
+ withComputed,
9
+ withMethods,
10
+ withState,
11
+ } from '@ngrx/signals';
12
+ import {
13
+ EntityId,
14
+ NamedEntityState,
15
+ addEntity,
16
+ removeEntity,
17
+ setAllEntities,
18
+ updateEntity,
19
+ } from '@ngrx/signals/entities';
20
+ import { EntityState } from './shared/signal-store-models';
21
+ import {
22
+ CallState,
23
+ NamedCallStateSlice,
24
+ getCallStateKeys,
25
+ setError,
26
+ setLoaded,
27
+ setLoading,
28
+ } from './with-call-state';
29
+
30
+ export type Filter = Record<string, unknown>;
31
+ export type Entity = { id: EntityId };
32
+
33
+ export interface DataService<E extends Entity, F extends Filter> {
34
+ load(filter: F): Promise<E[]>;
35
+
36
+ loadById(id: EntityId): Promise<E>;
37
+
38
+ create(entity: E): Promise<E>;
39
+
40
+ update(entity: E): Promise<E>;
41
+
42
+ updateAll(entity: E[]): Promise<E[]>;
43
+
44
+ delete(entity: E): Promise<void>;
45
+ }
46
+
47
+ export function capitalize(str: string): string {
48
+ return str ? str[0].toUpperCase() + str.substring(1) : str;
49
+ }
50
+
51
+ export function getDataServiceKeys(options: { collection?: string }) {
52
+ const filterKey = options.collection
53
+ ? `${options.collection}Filter`
54
+ : 'filter';
55
+ const selectedIdsKey = options.collection
56
+ ? `selected${capitalize(options.collection)}Ids`
57
+ : 'selectedIds';
58
+ const selectedEntitiesKey = options.collection
59
+ ? `selected${capitalize(options.collection)}Entities`
60
+ : 'selectedEntities';
61
+
62
+ const updateFilterKey = options.collection
63
+ ? `update${capitalize(options.collection)}Filter`
64
+ : 'updateFilter';
65
+ const updateSelectedKey = options.collection
66
+ ? `updateSelected${capitalize(options.collection)}Entities`
67
+ : 'updateSelected';
68
+ const loadKey = options.collection
69
+ ? `load${capitalize(options.collection)}Entities`
70
+ : 'load';
71
+
72
+ const currentKey = options.collection
73
+ ? `current${capitalize(options.collection)}`
74
+ : 'current';
75
+ const loadByIdKey = options.collection
76
+ ? `load${capitalize(options.collection)}ById`
77
+ : 'loadById';
78
+ const setCurrentKey = options.collection
79
+ ? `setCurrent${capitalize(options.collection)}`
80
+ : 'setCurrent';
81
+ const createKey = options.collection
82
+ ? `create${capitalize(options.collection)}`
83
+ : 'create';
84
+ const updateKey = options.collection
85
+ ? `update${capitalize(options.collection)}`
86
+ : 'update';
87
+ const updateAllKey = options.collection
88
+ ? `updateAll${capitalize(options.collection)}`
89
+ : 'updateAll';
90
+ const deleteKey = options.collection
91
+ ? `delete${capitalize(options.collection)}`
92
+ : 'delete';
93
+
94
+ // TODO: Take these from @ngrx/signals/entities, when they are exported
95
+ const entitiesKey = options.collection
96
+ ? `${options.collection}Entities`
97
+ : 'entities';
98
+ const entityMapKey = options.collection
99
+ ? `${options.collection}EntityMap`
100
+ : 'entityMap';
101
+ const idsKey = options.collection ? `${options.collection}Ids` : 'ids';
102
+
103
+ return {
104
+ filterKey,
105
+ selectedIdsKey,
106
+ selectedEntitiesKey,
107
+ updateFilterKey,
108
+ updateSelectedKey,
109
+ loadKey,
110
+ entitiesKey,
111
+ entityMapKey,
112
+ idsKey,
113
+
114
+ currentKey,
115
+ loadByIdKey,
116
+ setCurrentKey,
117
+ createKey,
118
+ updateKey,
119
+ updateAllKey,
120
+ deleteKey,
121
+ };
122
+ }
123
+
124
+ export type NamedDataServiceState<
125
+ E extends Entity,
126
+ F extends Filter,
127
+ Collection extends string,
128
+ > = {
129
+ [K in Collection as `${K}Filter`]: F;
130
+ } & {
131
+ [K in Collection as `selected${Capitalize<K>}Ids`]: Record<EntityId, boolean>;
132
+ } & {
133
+ [K in Collection as `current${Capitalize<K>}`]: E;
134
+ };
135
+
136
+ export type DataServiceState<E extends Entity, F extends Filter> = {
137
+ filter: F;
138
+ selectedIds: Record<EntityId, boolean>;
139
+ current: E;
140
+ };
141
+
142
+ export type DataServiceComputed<E extends Entity> = {
143
+ selectedEntities: Signal<E[]>;
144
+ };
145
+
146
+ export type NamedDataServiceComputed<
147
+ E extends Entity,
148
+ Collection extends string,
149
+ > = {
150
+ [K in Collection as `selected${Capitalize<K>}Entities`]: Signal<E[]>;
151
+ };
152
+
153
+ export type NamedDataServiceMethods<
154
+ E extends Entity,
155
+ F extends Filter,
156
+ Collection extends string,
157
+ > = {
158
+ [K in Collection as `update${Capitalize<K>}Filter`]: (filter: F) => void;
159
+ } & {
160
+ [K in Collection as `updateSelected${Capitalize<K>}Entities`]: (
161
+ id: EntityId,
162
+ selected: boolean,
163
+ ) => void;
164
+ } & {
165
+ [K in Collection as `load${Capitalize<K>}Entities`]: () => Promise<void>;
166
+ } & {
167
+ [K in Collection as `setCurrent${Capitalize<K>}`]: (entity: E) => void;
168
+ } & {
169
+ [K in Collection as `load${Capitalize<K>}ById`]: (
170
+ id: EntityId,
171
+ ) => Promise<void>;
172
+ } & {
173
+ [K in Collection as `create${Capitalize<K>}`]: (entity: E) => Promise<void>;
174
+ } & {
175
+ [K in Collection as `update${Capitalize<K>}`]: (entity: E) => Promise<void>;
176
+ } & {
177
+ [K in Collection as `updateAll${Capitalize<K>}`]: (
178
+ entity: E[],
179
+ ) => Promise<void>;
180
+ } & {
181
+ [K in Collection as `delete${Capitalize<K>}`]: (entity: E) => Promise<void>;
182
+ };
183
+
184
+ export type DataServiceMethods<E extends Entity, F extends Filter> = {
185
+ updateFilter: (filter: F) => void;
186
+ updateSelected: (id: EntityId, selected: boolean) => void;
187
+ load: () => Promise<void>;
188
+
189
+ setCurrent(entity: E): void;
190
+ loadById(id: EntityId): Promise<void>;
191
+ create(entity: E): Promise<void>;
192
+ update(entity: E): Promise<void>;
193
+ updateAll(entities: E[]): Promise<void>;
194
+ delete(entity: E): Promise<void>;
195
+ };
196
+
197
+ export function withDataService<
198
+ E extends Entity,
199
+ F extends Filter,
200
+ Collection extends string,
201
+ >(options: {
202
+ dataServiceType: ProviderToken<DataService<E, F>>;
203
+ filter: F;
204
+ collection: Collection;
205
+ }): SignalStoreFeature<
206
+ EmptyFeatureResult & {
207
+ state: NamedCallStateSlice<Collection> & NamedEntityState<E, Collection>;
208
+ },
209
+ {
210
+ state: NamedDataServiceState<E, F, Collection>;
211
+ props: NamedDataServiceComputed<E, Collection>;
212
+ methods: NamedDataServiceMethods<E, F, Collection>;
213
+ }
214
+ >;
215
+ export function withDataService<E extends Entity, F extends Filter>(options: {
216
+ dataServiceType: ProviderToken<DataService<E, F>>;
217
+ filter: F;
218
+ }): SignalStoreFeature<
219
+ EmptyFeatureResult & { state: { callState: CallState } & EntityState<E> },
220
+ {
221
+ state: DataServiceState<E, F>;
222
+ props: DataServiceComputed<E>;
223
+ methods: DataServiceMethods<E, F>;
224
+ }
225
+ >;
226
+
227
+ export function withDataService<
228
+ E extends Entity,
229
+ F extends Filter,
230
+ Collection extends string,
231
+ >(options: {
232
+ dataServiceType: ProviderToken<DataService<E, F>>;
233
+ filter: F;
234
+ collection?: Collection;
235
+ }): /* eslint-disable @typescript-eslint/no-explicit-any */
236
+ SignalStoreFeature<any, any> {
237
+ const { dataServiceType, filter, collection: prefix } = options;
238
+ const {
239
+ entitiesKey,
240
+ filterKey,
241
+ loadKey,
242
+ selectedEntitiesKey,
243
+ selectedIdsKey,
244
+ updateFilterKey,
245
+ updateSelectedKey,
246
+
247
+ currentKey,
248
+ createKey,
249
+ updateKey,
250
+ updateAllKey,
251
+ deleteKey,
252
+ loadByIdKey,
253
+ setCurrentKey,
254
+ } = getDataServiceKeys(options);
255
+
256
+ const { callStateKey } = getCallStateKeys({ collection: prefix });
257
+
258
+ return signalStoreFeature(
259
+ withState(() => ({
260
+ [filterKey]: filter,
261
+ [selectedIdsKey]: {} as Record<EntityId, boolean>,
262
+ [currentKey]: undefined as E | undefined,
263
+ })),
264
+ withComputed((store: Record<string, unknown>) => {
265
+ const entities = store[entitiesKey] as Signal<E[]>;
266
+ const selectedIds = store[selectedIdsKey] as Signal<
267
+ Record<EntityId, boolean>
268
+ >;
269
+
270
+ return {
271
+ [selectedEntitiesKey]: computed(() =>
272
+ entities().filter((e) => selectedIds()[e.id]),
273
+ ),
274
+ };
275
+ }),
276
+ withMethods(
277
+ (store: Record<string, unknown> & WritableStateSource<object>) => {
278
+ const dataService = inject(dataServiceType);
279
+ return {
280
+ [updateFilterKey]: (filter: F): void => {
281
+ patchState(store, { [filterKey]: filter });
282
+ },
283
+ [updateSelectedKey]: (id: EntityId, selected: boolean): void => {
284
+ patchState(store, (state: Record<string, unknown>) => ({
285
+ [selectedIdsKey]: {
286
+ ...(state[selectedIdsKey] as Record<EntityId, boolean>),
287
+ [id]: selected,
288
+ },
289
+ }));
290
+ },
291
+ [loadKey]: async (): Promise<void> => {
292
+ const filter = store[filterKey] as Signal<F>;
293
+ (() =>
294
+ store[callStateKey] && patchState(store, setLoading(prefix)))();
295
+
296
+ try {
297
+ const result = await dataService.load(filter());
298
+ patchState(
299
+ store,
300
+ prefix
301
+ ? setAllEntities(result, { collection: prefix })
302
+ : setAllEntities(result),
303
+ );
304
+ (() =>
305
+ store[callStateKey] && patchState(store, setLoaded(prefix)))();
306
+ } catch (e) {
307
+ (() =>
308
+ store[callStateKey] &&
309
+ patchState(store, setError(e, prefix)))();
310
+ throw e;
311
+ }
312
+ },
313
+ [loadByIdKey]: async (id: EntityId): Promise<void> => {
314
+ (() =>
315
+ store[callStateKey] && patchState(store, setLoading(prefix)))();
316
+
317
+ try {
318
+ const current = await dataService.loadById(id);
319
+ (() =>
320
+ store[callStateKey] && patchState(store, setLoaded(prefix)))();
321
+ patchState(store, { [currentKey]: current });
322
+ } catch (e) {
323
+ (() =>
324
+ store[callStateKey] &&
325
+ patchState(store, setError(e, prefix)))();
326
+ throw e;
327
+ }
328
+ },
329
+ [setCurrentKey]: (current: E): void => {
330
+ patchState(store, { [currentKey]: current });
331
+ },
332
+ [createKey]: async (entity: E): Promise<void> => {
333
+ patchState(store, { [currentKey]: entity });
334
+ (() =>
335
+ store[callStateKey] && patchState(store, setLoading(prefix)))();
336
+
337
+ try {
338
+ const created = await dataService.create(entity);
339
+ patchState(store, { [currentKey]: created });
340
+ patchState(
341
+ store,
342
+ prefix
343
+ ? addEntity(created, { collection: prefix })
344
+ : addEntity(created),
345
+ );
346
+ (() =>
347
+ store[callStateKey] && patchState(store, setLoaded(prefix)))();
348
+ } catch (e) {
349
+ (() =>
350
+ store[callStateKey] &&
351
+ patchState(store, setError(e, prefix)))();
352
+ throw e;
353
+ }
354
+ },
355
+ [updateKey]: async (entity: E): Promise<void> => {
356
+ patchState(store, { [currentKey]: entity });
357
+ (() =>
358
+ store[callStateKey] && patchState(store, setLoading(prefix)))();
359
+
360
+ try {
361
+ const updated = await dataService.update(entity);
362
+ patchState(store, { [currentKey]: updated });
363
+
364
+ const updateArg = {
365
+ id: updated.id,
366
+ changes: updated,
367
+ };
368
+
369
+ const updater = (collection: string) =>
370
+ updateEntity(updateArg, { collection });
371
+
372
+ patchState(
373
+ store,
374
+ prefix ? updater(prefix) : updateEntity(updateArg),
375
+ );
376
+ (() =>
377
+ store[callStateKey] && patchState(store, setLoaded(prefix)))();
378
+ } catch (e) {
379
+ (() =>
380
+ store[callStateKey] &&
381
+ patchState(store, setError(e, prefix)))();
382
+ throw e;
383
+ }
384
+ },
385
+ [updateAllKey]: async (entities: E[]): Promise<void> => {
386
+ (() =>
387
+ store[callStateKey] && patchState(store, setLoading(prefix)))();
388
+
389
+ try {
390
+ const result = await dataService.updateAll(entities);
391
+ patchState(
392
+ store,
393
+ prefix
394
+ ? setAllEntities(result, { collection: prefix })
395
+ : setAllEntities(result),
396
+ );
397
+ (() =>
398
+ store[callStateKey] && patchState(store, setLoaded(prefix)))();
399
+ } catch (e) {
400
+ (() =>
401
+ store[callStateKey] &&
402
+ patchState(store, setError(e, prefix)))();
403
+ throw e;
404
+ }
405
+ },
406
+ [deleteKey]: async (entity: E): Promise<void> => {
407
+ patchState(store, { [currentKey]: entity });
408
+ (() =>
409
+ store[callStateKey] && patchState(store, setLoading(prefix)))();
410
+
411
+ try {
412
+ await dataService.delete(entity);
413
+ patchState(store, { [currentKey]: undefined });
414
+ patchState(
415
+ store,
416
+ prefix
417
+ ? removeEntity(entity.id, { collection: prefix })
418
+ : removeEntity(entity.id),
419
+ );
420
+ (() =>
421
+ store[callStateKey] && patchState(store, setLoaded(prefix)))();
422
+ } catch (e) {
423
+ (() =>
424
+ store[callStateKey] &&
425
+ patchState(store, setError(e, prefix)))();
426
+ throw e;
427
+ }
428
+ },
429
+ };
430
+ },
431
+ ),
432
+ );
433
+ }
@@ -0,0 +1,69 @@
1
+ import { computed, Signal } from '@angular/core';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import {
4
+ getState,
5
+ patchState,
6
+ signalStore,
7
+ signalStoreFeature,
8
+ withComputed,
9
+ withMethods,
10
+ withState,
11
+ } from '@ngrx/signals';
12
+ import { lastValueFrom, of } from 'rxjs';
13
+ import { withFeatureFactory } from './with-feature-factory';
14
+
15
+ type User = {
16
+ id: number;
17
+ name: string;
18
+ };
19
+
20
+ function withMyEntity<Entity>(loadMethod: (id: number) => Promise<Entity>) {
21
+ return signalStoreFeature(
22
+ withState({
23
+ currentId: 1 as number | undefined,
24
+ entity: undefined as undefined | Entity,
25
+ }),
26
+ withMethods((store) => ({
27
+ async load(id: number) {
28
+ const entity = await loadMethod(1);
29
+ patchState(store, { entity, currentId: id });
30
+ },
31
+ })),
32
+ );
33
+ }
34
+
35
+ describe('withFeatureFactory', () => {
36
+ it('should allow a sum feature', () => {
37
+ function withSum(a: Signal<number>, b: Signal<number>) {
38
+ return signalStoreFeature(
39
+ withComputed(() => ({ sum: computed(() => a() + b()) })),
40
+ );
41
+ }
42
+ signalStore(
43
+ withState({ a: 1, b: 2 }),
44
+ withFeatureFactory((store) => withSum(store.a, store.b)),
45
+ );
46
+ });
47
+
48
+ it('should allow to pass elements from a SignalStore to a feature', async () => {
49
+ const UserStore = signalStore(
50
+ { providedIn: 'root' },
51
+ withMethods(() => ({
52
+ findById(id: number) {
53
+ return of({ id: 1, name: 'Konrad' });
54
+ },
55
+ })),
56
+ withFeatureFactory((store) => {
57
+ const loader = (id: number) => lastValueFrom(store.findById(id));
58
+ return withMyEntity<User>(loader);
59
+ }),
60
+ );
61
+
62
+ const userStore = TestBed.inject(UserStore);
63
+ await userStore.load(1);
64
+ expect(getState(userStore)).toEqual({
65
+ currentId: 1,
66
+ entity: { id: 1, name: 'Konrad' },
67
+ });
68
+ });
69
+ });
@@ -1,5 +1,15 @@
1
- import { SignalStoreFeature, SignalStoreFeatureResult, StateSignals } from '@ngrx/signals';
2
- type StoreForFactory<Input extends SignalStoreFeatureResult> = StateSignals<Input['state']> & Input['props'] & Input['methods'];
1
+ import {
2
+ SignalStoreFeature,
3
+ SignalStoreFeatureResult,
4
+ StateSignals,
5
+ } from '@ngrx/signals';
6
+
7
+ type StoreForFactory<Input extends SignalStoreFeatureResult> = StateSignals<
8
+ Input['state']
9
+ > &
10
+ Input['props'] &
11
+ Input['methods'];
12
+
3
13
  /**
4
14
  * @deprecated Use `import { withFeature } from '@ngrx/signals'` instead, starting with `ngrx/signals` 19.1: https://ngrx.io/guide/signals/signal-store/custom-store-features#connecting-a-custom-feature-with-the-store
5
15
  *
@@ -24,5 +34,23 @@ type StoreForFactory<Input extends SignalStoreFeatureResult> = StateSignals<Inpu
24
34
  * ```
25
35
  * @param factoryFn
26
36
  */
27
- export declare function withFeatureFactory<Input extends SignalStoreFeatureResult, Output extends SignalStoreFeatureResult>(factoryFn: (store: StoreForFactory<Input>) => SignalStoreFeature<Input, Output>): SignalStoreFeature<Input, Output>;
28
- export {};
37
+ export function withFeatureFactory<
38
+ Input extends SignalStoreFeatureResult,
39
+ Output extends SignalStoreFeatureResult,
40
+ >(
41
+ factoryFn: (
42
+ store: StoreForFactory<Input>,
43
+ ) => SignalStoreFeature<Input, Output>,
44
+ ): SignalStoreFeature<Input, Output> {
45
+ return (store) => {
46
+ const storeForFactory = {
47
+ ...store['stateSignals'],
48
+ ...store['props'],
49
+ ...store['methods'],
50
+ } as StoreForFactory<Input>;
51
+
52
+ const feature = factoryFn(storeForFactory);
53
+
54
+ return feature(store);
55
+ };
56
+ }