@angular-architects/ngrx-toolkit 19.0.1 → 19.1.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.
package/README.md CHANGED
@@ -9,6 +9,6 @@ NgRx Toolkit is a set of extensions to the NgRx Signals Store, like
9
9
  - Storage Sync: Synchronize the Store with Web Storage
10
10
  - Redux Connector: Map NgRx Store Actions to a present Signal Store
11
11
 
12
- For a more detailed guide on installation, setup, and usage, head to the [**Documentation**](https://angular-architects.github.io/ngrx-toolkit/).
12
+ For a more detailed guide on installation, setup, and usage, head to the [**Documentation**](https://ngrx-toolkit.angulararchitects.io//).
13
13
 
14
- ## https://angular-architects.github.io/ngrx-toolkit
14
+ ## https://ngrx-toolkit.angulararchitects.io/
@@ -42,10 +42,10 @@ class SignalReduxStore {
42
42
  resultMethod: mapper.resultMethod
43
43
  }));
44
44
  }
45
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: SignalReduxStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
46
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: SignalReduxStore, providedIn: 'root' }); }
45
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: SignalReduxStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
46
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: SignalReduxStore, providedIn: 'root' }); }
47
47
  }
48
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: SignalReduxStore, decorators: [{
48
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: SignalReduxStore, decorators: [{
49
49
  type: Injectable,
50
50
  args: [{
51
51
  providedIn: 'root'
@@ -1,6 +1,6 @@
1
1
  import { getState, signalStoreFeature, withMethods, withHooks, watchState, patchState as patchState$1, withState, withComputed, withProps } from '@ngrx/signals';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, PLATFORM_ID, Injectable, signal, effect, computed, isSignal, untracked } from '@angular/core';
3
+ import { inject, PLATFORM_ID, Injectable, signal, effect, InjectionToken, computed, isSignal, untracked, isDevMode as isDevMode$1 } from '@angular/core';
4
4
  import { isPlatformBrowser, isPlatformServer } from '@angular/common';
5
5
  import { Subject } from 'rxjs';
6
6
  import { setAllEntities, addEntity, updateEntity, removeEntity } from '@ngrx/signals/entities';
@@ -106,7 +106,9 @@ class DevtoolsSyncer {
106
106
  let storeName = name;
107
107
  const names = Object.values(this.#stores).map((store) => store.name);
108
108
  if (names.includes(storeName)) {
109
- const { options } = throwIfNull(Object.values(this.#stores).find((store) => store.name === storeName));
109
+ // const { options } = throwIfNull(
110
+ // Object.values(this.#stores).find((store) => store.name === storeName)
111
+ // );
110
112
  if (!options.indexNames) {
111
113
  throw new Error(`An instance of the store ${storeName} already exists. \
112
114
  Enable automatic indexing via withDevTools('${storeName}', { indexNames: true }), or rename it upon instantiation.`);
@@ -166,10 +168,10 @@ Enable automatic indexing via withDevTools('${storeName}', { indexNames: true })
166
168
  }, {});
167
169
  this.#trackers.forEach((tracker) => tracker.notifyRenamedStore(id));
168
170
  }
169
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevtoolsSyncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
170
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevtoolsSyncer, providedIn: 'root' }); }
171
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: DevtoolsSyncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
172
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: DevtoolsSyncer, providedIn: 'root' }); }
171
173
  }
172
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DevtoolsSyncer, decorators: [{
174
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: DevtoolsSyncer, decorators: [{
173
175
  type: Injectable,
174
176
  args: [{ providedIn: 'root' }]
175
177
  }], ctorParameters: () => [] });
@@ -214,17 +216,17 @@ class DefaultTracker {
214
216
  });
215
217
  }
216
218
  }
217
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DefaultTracker, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
218
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DefaultTracker, providedIn: 'root' }); }
219
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: DefaultTracker, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
220
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: DefaultTracker, providedIn: 'root' }); }
219
221
  }
220
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: DefaultTracker, decorators: [{
222
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: DefaultTracker, decorators: [{
221
223
  type: Injectable,
222
224
  args: [{ providedIn: 'root' }]
223
225
  }] });
224
226
 
225
- const existingNames = new Map();
226
227
  const renameDevtoolsMethodName = '___renameDevtoolsName';
227
228
  const uniqueDevtoolsId = '___uniqueDevtoolsId';
229
+ const EXISTING_NAMES = new InjectionToken('Array contain existing names for the signal stores', { factory: () => [], providedIn: 'root' });
228
230
  /**
229
231
  * Adds this store as a feature state to the Redux DevTools.
230
232
  *
@@ -239,10 +241,6 @@ const uniqueDevtoolsId = '___uniqueDevtoolsId';
239
241
  * @param features features to extend or modify the behavior of the Devtools
240
242
  */
241
243
  function withDevtools(name, ...features) {
242
- if (existingNames.has(name)) {
243
- throw new Error(`The store "${name}" has already been registered in the DevTools. Duplicate registration is not allowed.`);
244
- }
245
- existingNames.set(name, true);
246
244
  return signalStoreFeature(withMethods(() => {
247
245
  const syncer = inject(DevtoolsSyncer);
248
246
  const id = syncer.getNextId();
@@ -382,10 +380,10 @@ class GlitchTrackerService {
382
380
  this.#callback({ [id]: getState(this.#stores[id].store) });
383
381
  }
384
382
  }
385
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: GlitchTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
386
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: GlitchTrackerService, providedIn: 'root' }); }
383
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: GlitchTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
384
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: GlitchTrackerService, providedIn: 'root' }); }
387
385
  }
388
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: GlitchTrackerService, decorators: [{
386
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.4", ngImport: i0, type: GlitchTrackerService, decorators: [{
389
387
  type: Injectable,
390
388
  args: [{ providedIn: 'root' }]
391
389
  }] });
@@ -464,6 +462,98 @@ function payload() {
464
462
  return {};
465
463
  }
466
464
  const noPayload = {};
465
+ /**
466
+ * Creates a reducer function to separate the reducer logic into another file.
467
+ *
468
+ * ```typescript
469
+ * interface FlightState {
470
+ * flights: Flight[];
471
+ * effect1: boolean;
472
+ * effect2: boolean;
473
+ * }
474
+ *
475
+ * const initialState: FlightState = {
476
+ * flights: [],
477
+ * effect1: false,
478
+ * effect2: false,
479
+ * };
480
+ *
481
+ * const actions = {
482
+ * init: noPayload,
483
+ * updateEffect1: payload<{ value: boolean }>(),
484
+ * updateEffect2: payload<{ value: boolean }>(),
485
+ * };
486
+ *
487
+ * const reducer = createReducer<FlightState, typeof actions>((actions, on) => {
488
+ * on(actions.updateEffect1, (state, { value }) => {
489
+ * patchState(state, { effect1: value });
490
+ * });
491
+ *
492
+ * on(actions.updateEffect2, (state, { value }) => {
493
+ * patchState(state, { effect2: value });
494
+ * });
495
+ * });
496
+ *
497
+ * signalStore(
498
+ * withState(initialState),
499
+ * withRedux({
500
+ * actions,
501
+ * reducer,
502
+ * })
503
+ * );
504
+ * ```
505
+ * @param reducerFactory
506
+ */
507
+ function createReducer(reducerFactory) {
508
+ return reducerFactory;
509
+ }
510
+ /**
511
+ * Creates the effects function to separate the effects logic into another file.
512
+ *
513
+ * ```typescript
514
+ * interface FlightState {
515
+ * flights: Flight[];
516
+ * effect1: boolean;
517
+ * effect2: boolean;
518
+ * }
519
+ *
520
+ * const initialState: FlightState = {
521
+ * flights: [],
522
+ * effect1: false,
523
+ * effect2: false,
524
+ * };
525
+ *
526
+ * const actions = {
527
+ * init: noPayload,
528
+ * updateEffect1: payload<{ value: boolean }>(),
529
+ * updateEffect2: payload<{ value: boolean }>(),
530
+ * };
531
+ *
532
+ * const effects = createEffects(actions, (actions, create) => {
533
+ * return {
534
+ * init1$: create(actions.init).pipe(
535
+ * map(() => actions.updateEffect1({ value: true }))
536
+ * ),
537
+ * init2$: create(actions.init).pipe(
538
+ * map(() => actions.updateEffect2({ value: true }))
539
+ * ),
540
+ * };
541
+ * });
542
+ *
543
+ * signalStore(
544
+ * withState(initialState),
545
+ * withRedux({
546
+ * actions,
547
+ * effects,
548
+ * })
549
+ * );
550
+ * ```
551
+ * @param actions
552
+ * @param effectsFactory
553
+ */
554
+ function createEffects(actions, effectsFactory) {
555
+ return effectsFactory;
556
+ }
467
557
  function createActionFns(actionFnSpecs, reducerRegistry, effectsRegistry, state) {
468
558
  const actionFns = {};
469
559
  for (const type in actionFnSpecs) {
@@ -553,7 +643,7 @@ function withRedux(redux) {
553
643
  const { methods } = processRedux(redux.actions, redux.reducer, redux.effects, store);
554
644
  return {
555
645
  ...store,
556
- methods,
646
+ methods: { ...store.methods, ...methods },
557
647
  };
558
648
  };
559
649
  }
@@ -995,7 +1085,7 @@ function withStorageSync(configOrKey) {
995
1085
  * This feature implements the local pagination.
996
1086
  */
997
1087
  function withPagination(options) {
998
- const { pageKey, pageSizeKey, entitiesKey, selectedPageEntitiesKey, totalCountKey, pageCountKey, pageNavigationArrayMaxKey, pageNavigationArrayKey, setPageSizeKey, nextPageKey, previousPageKey, lastPageKey, firstPageKey, gotoPageKey, hasNextPageKey, hasPreviousPageKey, } = createPaginationKeys(options);
1088
+ const { pageKey, pageSizeKey, entitiesKey, selectedPageEntitiesKey, totalCountKey, pageCountKey, pageNavigationArrayMaxKey, pageNavigationArrayKey, hasNextPageKey, hasPreviousPageKey, } = createPaginationKeys(options);
999
1089
  return signalStoreFeature(withState({
1000
1090
  [pageKey]: 0,
1001
1091
  [pageSizeKey]: 10,
@@ -1029,30 +1119,6 @@ function withPagination(options) {
1029
1119
  return page() > 1;
1030
1120
  }),
1031
1121
  };
1032
- }), withMethods((store) => {
1033
- return {
1034
- [setPageSizeKey]: (size) => {
1035
- patchState$1(store, setPageSize(size, options));
1036
- },
1037
- [nextPageKey]: () => {
1038
- patchState$1(store, nextPage(options));
1039
- },
1040
- [previousPageKey]: () => {
1041
- patchState$1(store, previousPage(options));
1042
- },
1043
- [lastPageKey]: () => {
1044
- const lastPage = store[pageCountKey]();
1045
- if (lastPage === 0)
1046
- return;
1047
- patchState$1(store, gotoPage(lastPage - 1, options));
1048
- },
1049
- [firstPageKey]: () => {
1050
- patchState$1(store, firstPage());
1051
- },
1052
- [gotoPageKey]: (page) => {
1053
- patchState$1(store, gotoPage(page, options));
1054
- },
1055
- };
1056
1122
  }));
1057
1123
  }
1058
1124
  function gotoPage(page, options) {
@@ -1116,24 +1182,6 @@ function createPaginationKeys(options) {
1116
1182
  const pageNavigationArrayKey = options?.collection
1117
1183
  ? `${options.collection}PageNavigationArray`
1118
1184
  : 'pageNavigationArray';
1119
- const setPageSizeKey = options?.collection
1120
- ? `set${capitalize(options.collection)}PageSize`
1121
- : 'setPageSize';
1122
- const nextPageKey = options?.collection
1123
- ? `next${capitalize(options.collection)}Page`
1124
- : 'nextPage';
1125
- const previousPageKey = options?.collection
1126
- ? `previous${capitalize(options.collection)}Page`
1127
- : 'previousPage';
1128
- const lastPageKey = options?.collection
1129
- ? `last${capitalize(options.collection)}Page`
1130
- : 'lastPage';
1131
- const firstPageKey = options?.collection
1132
- ? `first${capitalize(options.collection)}Page`
1133
- : 'firstPage';
1134
- const gotoPageKey = options?.collection
1135
- ? `goto${capitalize(options.collection)}Page`
1136
- : 'gotoPage';
1137
1185
  const hasNextPageKey = options?.collection
1138
1186
  ? `hasNext${capitalize(options.collection)}Page`
1139
1187
  : 'hasNextPage';
@@ -1149,12 +1197,6 @@ function createPaginationKeys(options) {
1149
1197
  pageCountKey,
1150
1198
  pageNavigationArrayKey,
1151
1199
  pageNavigationArrayMaxKey,
1152
- setPageSizeKey,
1153
- nextPageKey,
1154
- previousPageKey,
1155
- lastPageKey,
1156
- firstPageKey,
1157
- gotoPageKey,
1158
1200
  hasNextPageKey,
1159
1201
  hasPreviousPageKey,
1160
1202
  };
@@ -1231,9 +1273,171 @@ function setResetState(store, state) {
1231
1273
  store.__setResetState__(state);
1232
1274
  }
1233
1275
 
1276
+ /**
1277
+ * Deep freezes a state object along its properties with primitive values
1278
+ * on the first level.
1279
+ *
1280
+ * The reason for this is that the final state is a merge of all
1281
+ * root properties of all states, i.e. `withState`,....
1282
+ *
1283
+ * Since the root object will not be part of the state (shadow clone),
1284
+ * we are not freezing it.
1285
+ */
1286
+ function deepFreeze(target,
1287
+ // if empty all properties will be frozen
1288
+ propertyNamesToBeFrozen,
1289
+ // also means that we are on the first level
1290
+ isRoot = true) {
1291
+ const runPropertyNameCheck = propertyNamesToBeFrozen.length > 0;
1292
+ for (const key of Reflect.ownKeys(target)) {
1293
+ if (runPropertyNameCheck && !propertyNamesToBeFrozen.includes(key)) {
1294
+ continue;
1295
+ }
1296
+ const propValue = target[key];
1297
+ if (isRecordLike(propValue) && !Object.isFrozen(propValue)) {
1298
+ Object.freeze(propValue);
1299
+ deepFreeze(propValue, [], false);
1300
+ }
1301
+ else if (isRoot) {
1302
+ Object.defineProperty(target, key, {
1303
+ value: propValue,
1304
+ writable: false,
1305
+ configurable: false,
1306
+ });
1307
+ }
1308
+ }
1309
+ }
1310
+ function isRecordLike(target) {
1311
+ return typeof target === 'object' && target !== null;
1312
+ }
1313
+
1314
+ // necessary wrapper function to test prod mode
1315
+ function isDevMode() {
1316
+ return isDevMode$1();
1317
+ }
1318
+
1319
+ function withImmutableState(stateOrFactory, options) {
1320
+ const immutableState = typeof stateOrFactory === 'function' ? stateOrFactory() : stateOrFactory;
1321
+ const stateKeys = Reflect.ownKeys(immutableState);
1322
+ const applyFreezing = isDevMode() || options?.enableInProduction === true;
1323
+ return signalStoreFeature(withState(immutableState), withHooks((store) => ({
1324
+ onInit() {
1325
+ if (!applyFreezing) {
1326
+ return;
1327
+ }
1328
+ /**
1329
+ * `immutableState` will be initially frozen. That is because
1330
+ * of potential mutations outside the SignalStore
1331
+ *
1332
+ * ```ts
1333
+ * const initialState = {id: 1};
1334
+ * signalStore(withImmutableState(initialState));
1335
+ *
1336
+ * initialState.id = 2; // must throw immutability
1337
+ * ```
1338
+ */
1339
+ Object.freeze(immutableState);
1340
+ watchState(store, (state) => {
1341
+ deepFreeze(state, stateKeys);
1342
+ });
1343
+ },
1344
+ })));
1345
+ }
1346
+
1347
+ /**
1348
+ * Allows to pass properties, methods, or signals from a SignalStore
1349
+ * to a feature.
1350
+ *
1351
+ * Typically, a `signalStoreFeature` can have input constraints on
1352
+ *
1353
+ * ```typescript
1354
+ * function withSum(a: Signal<number>, b: Signal<number>) {
1355
+ * return signalStoreFeature(
1356
+ * withComputed(() => ({
1357
+ * sum: computed(() => a() + b())
1358
+ * }))
1359
+ * );
1360
+ * }
1361
+ *
1362
+ * signalStore(
1363
+ * withState({ a: 1, b: 2 }),
1364
+ * withFeatureFactory((store) => withSum(store.a, store.b))
1365
+ * );
1366
+ * ```
1367
+ * @param factoryFn
1368
+ */
1369
+ function withFeatureFactory(factoryFn) {
1370
+ return (store) => {
1371
+ const storeForFactory = {
1372
+ ...store['stateSignals'],
1373
+ ...store['props'],
1374
+ ...store['methods'],
1375
+ };
1376
+ const feature = factoryFn(storeForFactory);
1377
+ return feature(store);
1378
+ };
1379
+ }
1380
+
1381
+ /**
1382
+ * `withConditional` activates a feature based on a given condition.
1383
+ *
1384
+ * **Use Cases**
1385
+ * - Conditionally activate features based on the **store state** or other criteria.
1386
+ * - Choose between **two different implementations** of a feature.
1387
+ *
1388
+ * **Type Constraints**
1389
+ * Both features must have **exactly the same state, props, and methods**.
1390
+ * Otherwise, a type error will occur.
1391
+ *
1392
+ *
1393
+ * **Usage**
1394
+ *
1395
+ * ```typescript
1396
+ * const withUser = signalStoreFeature(
1397
+ * withState({ id: 1, name: 'Konrad' }),
1398
+ * withHooks(store => ({
1399
+ * onInit() {
1400
+ * // user loading logic
1401
+ * }
1402
+ * }))
1403
+ * );
1404
+ *
1405
+ * function withFakeUser() {
1406
+ * return signalStoreFeature(
1407
+ * withState({ id: 0, name: 'anonymous' })
1408
+ * );
1409
+ * }
1410
+ *
1411
+ * signalStore(
1412
+ * withMethods(() => ({
1413
+ * useRealUser: () => true
1414
+ * })),
1415
+ * withConditional((store) => store.useRealUser(), withUser, withFakeUser)
1416
+ * )
1417
+ * ```
1418
+ *
1419
+ * @param condition - A function that determines which feature to activate based on the store state.
1420
+ * @param featureIfTrue - The feature to activate if the condition evaluates to `true`.
1421
+ * @param featureIfFalse - The feature to activate if the condition evaluates to `false`.
1422
+ * @returns A `SignalStoreFeature` that applies the selected feature based on the condition.
1423
+ */
1424
+ function withConditional(condition, featureIfTrue, featureIfFalse) {
1425
+ return (store) => {
1426
+ const conditionStore = {
1427
+ ...store['stateSignals'],
1428
+ ...store['props'],
1429
+ ...store['methods'],
1430
+ };
1431
+ return condition(conditionStore)
1432
+ ? featureIfTrue(store)
1433
+ : featureIfFalse(store);
1434
+ };
1435
+ }
1436
+ const emptyFeature = signalStoreFeature(withState({}));
1437
+
1234
1438
  /**
1235
1439
  * Generated bundle index. Do not edit.
1236
1440
  */
1237
1441
 
1238
- export { capitalize, createPageArray, firstPage, getCallStateKeys, getDataServiceKeys, getUndoRedoKeys, gotoPage, nextPage, noPayload, patchState, payload, previousPage, renameDevtoolsName, setError, setLoaded, setLoading, setMaxPageNavigationArrayItems, setPageSize, setResetState, updateState, withCallState, withDataService, withDevToolsStub, withDevtools, withDisabledNameIndices, withGlitchTracking, withMapper, withPagination, withRedux, withReset, withStorageSync, withUndoRedo };
1442
+ export { capitalize, createEffects, createPageArray, createReducer, emptyFeature, firstPage, getCallStateKeys, getDataServiceKeys, getUndoRedoKeys, gotoPage, nextPage, noPayload, patchState, payload, previousPage, renameDevtoolsName, setError, setLoaded, setLoading, setMaxPageNavigationArrayItems, setPageSize, setResetState, updateState, withCallState, withConditional, withDataService, withDevToolsStub, withDevtools, withDisabledNameIndices, withFeatureFactory, withGlitchTracking, withImmutableState, withMapper, withPagination, withRedux, withReset, withStorageSync, withUndoRedo };
1239
1443
  //# sourceMappingURL=angular-architects-ngrx-toolkit.mjs.map