ng-simple-state 21.1.4 → 21.1.5
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 +99 -70
- package/fesm2022/ng-simple-state.mjs +106 -166
- package/fesm2022/ng-simple-state.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ng-simple-state.d.ts +62 -71
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# NgSimpleState [](https://www.npmjs.com/package/ng-simple-state) [](https://qlty.sh/gh/nigrosimone/projects/ng-simple-state)
|
|
2
2
|
|
|
3
3
|
Simple state management in Angular with only Services and RxJS or Signal.
|
|
4
4
|
|
|
@@ -31,7 +31,6 @@ npm i ng-simple-state
|
|
|
31
31
|
| *serializeState* | A function used to serialize the state to a string. | `JSON.stringify` |
|
|
32
32
|
| *deserializeState* | A function used to deserialize the state from a string. | `JSON.parse` |
|
|
33
33
|
| *plugins* | Array of plugins to extend store functionality. | `[]` |
|
|
34
|
-
| *enableImmer* | Enable Immer-style immutable updates. | `false` |
|
|
35
34
|
| *immerProduce* | Custom Immer produce function for immutable updates. | undefined |
|
|
36
35
|
|
|
37
36
|
_Side note: each store can be override the global configuration implementing `storeConfig()` method (see "Override global config")._
|
|
@@ -1154,7 +1153,6 @@ Usage in component:
|
|
|
1154
1153
|
selector: 'app-profile',
|
|
1155
1154
|
template: `
|
|
1156
1155
|
<p>Full Name: {{ store.fullName() }}</p>
|
|
1157
|
-
<p>Is Adult: {{ store.isAdult() ? 'Yes' : 'No' }}</p>
|
|
1158
1156
|
<input [value]="firstName()" (input)="updateFirstName($event)" placeholder="First name" />
|
|
1159
1157
|
<input [value]="lastName()" (input)="updateLastName($event)" placeholder="Last name" />
|
|
1160
1158
|
`
|
|
@@ -1283,89 +1281,83 @@ const myCustomPlugin: NgSimpleStatePlugin = {
|
|
|
1283
1281
|
};
|
|
1284
1282
|
```
|
|
1285
1283
|
|
|
1286
|
-
###
|
|
1284
|
+
### Transactions
|
|
1287
1285
|
|
|
1288
|
-
|
|
1286
|
+
Execute operations with automatic rollback on error using `withTransaction`:
|
|
1289
1287
|
|
|
1290
1288
|
```ts
|
|
1291
|
-
import {
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
@Injectable({ providedIn: 'root' })
|
|
1302
|
-
export class FormStore extends NgSimpleStateBaseSignalStore<FormState> {
|
|
1289
|
+
import { withTransaction } from 'ng-simple-state';
|
|
1290
|
+
|
|
1291
|
+
// Transaction with automatic rollback on error
|
|
1292
|
+
await withTransaction(store, async (tx) => {
|
|
1293
|
+
store.setState({ step: 1 });
|
|
1294
|
+
await apiCall(); // If this fails, state rolls back automatically
|
|
1295
|
+
store.setState({ step: 2 });
|
|
1296
|
+
tx.commit(); // Explicit commit (optional - auto-commits if not called)
|
|
1297
|
+
});
|
|
1303
1298
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1299
|
+
// Manual rollback example
|
|
1300
|
+
await withTransaction(store, async (tx) => {
|
|
1301
|
+
store.setState({ processing: true });
|
|
1302
|
+
const result = await riskyOperation();
|
|
1303
|
+
|
|
1304
|
+
if (!result.success) {
|
|
1305
|
+
tx.rollback(); // Manually rollback to initial state
|
|
1306
|
+
return;
|
|
1306
1307
|
}
|
|
1308
|
+
|
|
1309
|
+
store.setState({ data: result.data });
|
|
1310
|
+
tx.commit();
|
|
1311
|
+
});
|
|
1312
|
+
```
|
|
1307
1313
|
|
|
1308
|
-
|
|
1309
|
-
return { name: '', email: '', phone: '', isValid: false };
|
|
1310
|
-
}
|
|
1314
|
+
### Debounced Updates
|
|
1311
1315
|
|
|
1312
|
-
|
|
1313
|
-
return this.selectState();
|
|
1314
|
-
}
|
|
1316
|
+
Rate-limit state updates with `createDebouncedUpdater`. Only the last update within the time window is applied:
|
|
1315
1317
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
batchState(() => {
|
|
1319
|
-
this.setState({ name });
|
|
1320
|
-
this.setState({ email });
|
|
1321
|
-
this.setState({ phone });
|
|
1322
|
-
this.setState({ isValid: this.validateForm(name, email, phone) });
|
|
1323
|
-
}); // Single emission with all changes
|
|
1324
|
-
}
|
|
1318
|
+
```ts
|
|
1319
|
+
import { createDebouncedUpdater } from 'ng-simple-state';
|
|
1325
1320
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1321
|
+
// Create a debounced updater with 300ms delay
|
|
1322
|
+
const { update, flush, cancel } = createDebouncedUpdater<MyState>(
|
|
1323
|
+
(state) => store.setState(state),
|
|
1324
|
+
300
|
|
1325
|
+
);
|
|
1330
1326
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1327
|
+
// Rapid calls - only the last one is applied after 300ms
|
|
1328
|
+
update({ searchQuery: 'a' });
|
|
1329
|
+
update({ searchQuery: 'ab' });
|
|
1330
|
+
update({ searchQuery: 'abc' }); // Only this is applied after 300ms
|
|
1334
1331
|
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1332
|
+
// Force immediate update
|
|
1333
|
+
flush();
|
|
1334
|
+
|
|
1335
|
+
// Cancel any pending update
|
|
1336
|
+
cancel();
|
|
1339
1337
|
```
|
|
1340
1338
|
|
|
1341
|
-
|
|
1339
|
+
### Throttled Updates
|
|
1340
|
+
|
|
1341
|
+
Limit update frequency with `createThrottledUpdater`. At most one update per time window:
|
|
1342
1342
|
|
|
1343
1343
|
```ts
|
|
1344
|
-
|
|
1345
|
-
selector: 'app-form',
|
|
1346
|
-
template: `
|
|
1347
|
-
<form (ngSubmit)="submitForm()">
|
|
1348
|
-
<input [(ngModel)]="name" name="name" placeholder="Name" />
|
|
1349
|
-
<input [(ngModel)]="email" name="email" placeholder="Email" />
|
|
1350
|
-
<input [(ngModel)]="phone" name="phone" placeholder="Phone" />
|
|
1351
|
-
<button type="submit">Save All (Batched)</button>
|
|
1352
|
-
</form>
|
|
1353
|
-
<p>Form Valid: {{ form().isValid ? 'Yes' : 'No' }}</p>
|
|
1354
|
-
`
|
|
1355
|
-
})
|
|
1356
|
-
export class FormComponent {
|
|
1357
|
-
store = inject(FormStore);
|
|
1358
|
-
form = this.store.selectForm();
|
|
1359
|
-
|
|
1360
|
-
name = '';
|
|
1361
|
-
email = '';
|
|
1362
|
-
phone = '';
|
|
1344
|
+
import { createThrottledUpdater } from 'ng-simple-state';
|
|
1363
1345
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1346
|
+
// Create a throttled updater with 100ms delay
|
|
1347
|
+
const { update, cancel } = createThrottledUpdater<MyState>(
|
|
1348
|
+
(state) => store.setState(state),
|
|
1349
|
+
100
|
|
1350
|
+
);
|
|
1351
|
+
|
|
1352
|
+
// First call executes immediately, subsequent calls are throttled
|
|
1353
|
+
update({ scrollPosition: 100 }); // Executes immediately
|
|
1354
|
+
update({ scrollPosition: 150 }); // Queued
|
|
1355
|
+
update({ scrollPosition: 200 }); // Replaces queued update
|
|
1356
|
+
|
|
1357
|
+
// After 100ms, { scrollPosition: 200 } is applied
|
|
1358
|
+
|
|
1359
|
+
// Cancel pending throttled update
|
|
1360
|
+
cancel();
|
|
1369
1361
|
```
|
|
1370
1362
|
|
|
1371
1363
|
### Immer-style Updates
|
|
@@ -1508,6 +1500,43 @@ Features available in Redux DevTools:
|
|
|
1508
1500
|
|
|
1509
1501
|

|
|
1510
1502
|
|
|
1503
|
+
### Integration with ng-http-caching
|
|
1504
|
+
|
|
1505
|
+
`ng-simple-state` can be used as a reactive storage backend for [ng-http-caching](https://www.npmjs.com/package/ng-http-caching). This integration allows you to manage the HTTP cache through `ng-simple-state` stores, providing full visibility and control over the cache state within your DevTools or application state.
|
|
1506
|
+
|
|
1507
|
+
1. Install `ng-http-caching`:
|
|
1508
|
+
|
|
1509
|
+
```bash
|
|
1510
|
+
npm i ng-http-caching
|
|
1511
|
+
```
|
|
1512
|
+
|
|
1513
|
+
2. Configure `ng-http-caching` to use the `ng-simple-state` adapter in your `bootstrapApplication`:
|
|
1514
|
+
|
|
1515
|
+
```ts
|
|
1516
|
+
import { provideNgHttpCaching } from 'ng-http-caching';
|
|
1517
|
+
import { withNgHttpCachingNgSimpleState } from 'ng-http-caching/ng-simple-state';
|
|
1518
|
+
|
|
1519
|
+
bootstrapApplication(AppComponent, {
|
|
1520
|
+
providers: [
|
|
1521
|
+
provideNgHttpCaching({
|
|
1522
|
+
store: withNgHttpCachingNgSimpleState(),
|
|
1523
|
+
}),
|
|
1524
|
+
// ... other providers
|
|
1525
|
+
]
|
|
1526
|
+
});
|
|
1527
|
+
```
|
|
1528
|
+
|
|
1529
|
+
You can customize the store configuration (e.g., enable persistence or change the store name) by passing a configuration object to the adapter:
|
|
1530
|
+
|
|
1531
|
+
```ts
|
|
1532
|
+
provideNgHttpCaching({
|
|
1533
|
+
store: withNgHttpCachingNgSimpleState({
|
|
1534
|
+
storeName: 'MyCacheStore',
|
|
1535
|
+
// ... other NgSimpleStateStoreConfig options
|
|
1536
|
+
}),
|
|
1537
|
+
})
|
|
1538
|
+
```
|
|
1539
|
+
|
|
1511
1540
|
## Alternatives
|
|
1512
1541
|
|
|
1513
1542
|
Aren't you satisfied? there are some valid alternatives:
|
|
@@ -21,11 +21,11 @@ class NgSimpleStateModule {
|
|
|
21
21
|
],
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
25
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.
|
|
26
|
-
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.
|
|
24
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
25
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateModule }); }
|
|
26
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateModule }); }
|
|
27
27
|
}
|
|
28
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
28
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateModule, decorators: [{
|
|
29
29
|
type: NgModule
|
|
30
30
|
}] });
|
|
31
31
|
|
|
@@ -50,9 +50,9 @@ class NgSimpleStateDevTool {
|
|
|
50
50
|
this.historyIdCounter = 0;
|
|
51
51
|
this.maxHistorySize = 100;
|
|
52
52
|
/** Current history position for time-travel */
|
|
53
|
-
this.currentPositionSig = signal(-1, ...(ngDevMode ? [{ debugName: "currentPositionSig" }] : []));
|
|
53
|
+
this.currentPositionSig = signal(-1, ...(ngDevMode ? [{ debugName: "currentPositionSig" }] : /* istanbul ignore next */ []));
|
|
54
54
|
/** Paused state - when true, state changes are not recorded */
|
|
55
|
-
this.isPausedSig = signal(false, ...(ngDevMode ? [{ debugName: "isPausedSig" }] : []));
|
|
55
|
+
this.isPausedSig = signal(false, ...(ngDevMode ? [{ debugName: "isPausedSig" }] : /* istanbul ignore next */ []));
|
|
56
56
|
if (this.globalDevtools) {
|
|
57
57
|
inject(NgZone).runOutsideAngular(() => {
|
|
58
58
|
this.localDevTool = this.globalDevtools.connect({
|
|
@@ -404,10 +404,10 @@ class NgSimpleStateDevTool {
|
|
|
404
404
|
}
|
|
405
405
|
return NgSimpleStateDevTool.computeDiff(last.prevState, last.state);
|
|
406
406
|
}
|
|
407
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
408
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.
|
|
407
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateDevTool, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
408
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateDevTool, providedIn: 'root' }); }
|
|
409
409
|
}
|
|
410
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
410
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateDevTool, decorators: [{
|
|
411
411
|
type: Injectable,
|
|
412
412
|
args: [{ providedIn: 'root' }]
|
|
413
413
|
}], ctorParameters: () => [] });
|
|
@@ -822,7 +822,9 @@ class NgSimpleStateBaseCommonStore {
|
|
|
822
822
|
return undefined;
|
|
823
823
|
}
|
|
824
824
|
// avoid function call if not necessary
|
|
825
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
825
826
|
this.devTool && this.devToolSend(state, resolvedActionName);
|
|
827
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
826
828
|
this.storage && this.statePersist(state);
|
|
827
829
|
// Notify plugins after change
|
|
828
830
|
this.notifyPluginsAfterChange(currState, state, resolvedActionName);
|
|
@@ -869,7 +871,9 @@ class NgSimpleStateBaseCommonStore {
|
|
|
869
871
|
return undefined;
|
|
870
872
|
}
|
|
871
873
|
// avoid function call if not necessary
|
|
874
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
872
875
|
this.devTool && this.devToolSend(newState, resolvedActionName);
|
|
876
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
873
877
|
this.storage && this.statePersist(newState);
|
|
874
878
|
// Notify plugins after change
|
|
875
879
|
this.notifyPluginsAfterChange(currState, newState, resolvedActionName);
|
|
@@ -925,11 +929,11 @@ class NgSimpleStateBaseCommonStore {
|
|
|
925
929
|
this.storage.setItem(this.storeName, state);
|
|
926
930
|
}
|
|
927
931
|
}
|
|
928
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
929
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.
|
|
930
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.
|
|
932
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseCommonStore, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
933
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: NgSimpleStateBaseCommonStore, isStandalone: true, ngImport: i0 }); }
|
|
934
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseCommonStore }); }
|
|
931
935
|
}
|
|
932
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
936
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseCommonStore, decorators: [{
|
|
933
937
|
type: Injectable
|
|
934
938
|
}, {
|
|
935
939
|
type: Directive
|
|
@@ -982,18 +986,29 @@ class NgSimpleStateBaseRxjsStore extends NgSimpleStateBaseCommonStore {
|
|
|
982
986
|
return this.state$.pipe(map(state => selectFn(state)), distinctUntilChanged(comparator ?? this.comparator), observeOn(asyncScheduler));
|
|
983
987
|
}
|
|
984
988
|
/**
|
|
985
|
-
* Create an effect
|
|
989
|
+
* Create an effect from an observable
|
|
986
990
|
* @param name Unique effect name
|
|
987
|
-
* @param
|
|
991
|
+
* @param source$ Observable source
|
|
992
|
+
* @param effectFn Effect function that receives the observable value
|
|
988
993
|
*/
|
|
989
|
-
|
|
994
|
+
createEffectFromObservable(name, source$, effectFn) {
|
|
990
995
|
// Cleanup existing effect with same name
|
|
991
996
|
this.destroyEffect(name);
|
|
992
|
-
const subscription =
|
|
993
|
-
|
|
997
|
+
const subscription = source$
|
|
998
|
+
.pipe(takeUntil(this.destroy$))
|
|
999
|
+
.subscribe(value => {
|
|
1000
|
+
effectFn(value);
|
|
994
1001
|
});
|
|
995
1002
|
this.registeredEffects.set(name, subscription);
|
|
996
1003
|
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Create an effect that reacts to state changes
|
|
1006
|
+
* @param name Unique effect name
|
|
1007
|
+
* @param effectFn Effect function that receives current state
|
|
1008
|
+
*/
|
|
1009
|
+
createEffect(name, effectFn) {
|
|
1010
|
+
this.createEffectFromObservable(name, this.state$, effectFn);
|
|
1011
|
+
}
|
|
997
1012
|
/**
|
|
998
1013
|
* Create an effect that reacts to selected state changes
|
|
999
1014
|
* @param name Unique effect name
|
|
@@ -1001,11 +1016,7 @@ class NgSimpleStateBaseRxjsStore extends NgSimpleStateBaseCommonStore {
|
|
|
1001
1016
|
* @param effectFn Effect function that receives selected value
|
|
1002
1017
|
*/
|
|
1003
1018
|
createSelectorEffect(name, selector, effectFn) {
|
|
1004
|
-
this.
|
|
1005
|
-
const subscription = this.selectState(selector).pipe(takeUntil(this.destroy$)).subscribe(value => {
|
|
1006
|
-
effectFn(value);
|
|
1007
|
-
});
|
|
1008
|
-
this.registeredEffects.set(name, subscription);
|
|
1019
|
+
this.createEffectFromObservable(name, this.selectState(selector), effectFn);
|
|
1009
1020
|
}
|
|
1010
1021
|
/**
|
|
1011
1022
|
* Destroy a specific effect by name
|
|
@@ -1091,11 +1102,11 @@ class NgSimpleStateBaseRxjsStore extends NgSimpleStateBaseCommonStore {
|
|
|
1091
1102
|
}
|
|
1092
1103
|
return false;
|
|
1093
1104
|
}
|
|
1094
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
1095
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.
|
|
1096
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.
|
|
1105
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseRxjsStore, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1106
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: NgSimpleStateBaseRxjsStore, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
|
|
1107
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseRxjsStore }); }
|
|
1097
1108
|
}
|
|
1098
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1109
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseRxjsStore, decorators: [{
|
|
1099
1110
|
type: Injectable
|
|
1100
1111
|
}, {
|
|
1101
1112
|
type: Directive
|
|
@@ -1106,7 +1117,7 @@ class NgSimpleStateBaseSignalStore extends NgSimpleStateBaseCommonStore {
|
|
|
1106
1117
|
constructor() {
|
|
1107
1118
|
super(...arguments);
|
|
1108
1119
|
this.stackPoint = 4;
|
|
1109
|
-
this.stateSig = signal(this.firstState, ...(ngDevMode ? [{ debugName: "stateSig" }] : []));
|
|
1120
|
+
this.stateSig = signal(this.firstState, ...(ngDevMode ? [{ debugName: "stateSig" }] : /* istanbul ignore next */ []));
|
|
1110
1121
|
this.stateSigRo = this.stateSig.asReadonly();
|
|
1111
1122
|
this.registeredEffects = new Map();
|
|
1112
1123
|
this.injector = inject(Injector);
|
|
@@ -1151,7 +1162,7 @@ class NgSimpleStateBaseSignalStore extends NgSimpleStateBaseCommonStore {
|
|
|
1151
1162
|
* @returns WritableSignal that is linked to the store state
|
|
1152
1163
|
*/
|
|
1153
1164
|
linkedState(options) {
|
|
1154
|
-
const sourceSignal = computed(() => options.source(this.stateSig()), ...(ngDevMode ? [{ debugName: "sourceSignal" }] : []));
|
|
1165
|
+
const sourceSignal = computed(() => options.source(this.stateSig()), ...(ngDevMode ? [{ debugName: "sourceSignal" }] : /* istanbul ignore next */ []));
|
|
1155
1166
|
return linkedSignal({
|
|
1156
1167
|
source: sourceSignal,
|
|
1157
1168
|
computation: (source, previous) => {
|
|
@@ -1163,26 +1174,36 @@ class NgSimpleStateBaseSignalStore extends NgSimpleStateBaseCommonStore {
|
|
|
1163
1174
|
});
|
|
1164
1175
|
}
|
|
1165
1176
|
/**
|
|
1166
|
-
* Create an effect
|
|
1177
|
+
* Create an effect from a signal
|
|
1167
1178
|
* @param name Unique effect name
|
|
1168
|
-
* @param
|
|
1179
|
+
* @param sourceSignal Signal source
|
|
1180
|
+
* @param effectFn Effect function that receives the signal value
|
|
1169
1181
|
* @returns EffectRef for cleanup
|
|
1170
1182
|
*/
|
|
1171
|
-
|
|
1183
|
+
createEffectFromSignal(name, sourceSignal, effectFn) {
|
|
1172
1184
|
// Cleanup existing effect with same name
|
|
1173
1185
|
this.destroyEffect(name);
|
|
1174
1186
|
let cleanup;
|
|
1175
1187
|
const effectRef = runInInjectionContext(this.injector, () => effect(() => {
|
|
1176
|
-
const
|
|
1188
|
+
const value = sourceSignal();
|
|
1177
1189
|
// Run previous cleanup
|
|
1178
1190
|
if (cleanup) {
|
|
1179
1191
|
cleanup();
|
|
1180
1192
|
}
|
|
1181
|
-
cleanup = effectFn(
|
|
1193
|
+
cleanup = effectFn(value);
|
|
1182
1194
|
}));
|
|
1183
1195
|
this.registeredEffects.set(name, effectRef);
|
|
1184
1196
|
return effectRef;
|
|
1185
1197
|
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Create an effect that reacts to state changes
|
|
1200
|
+
* @param name Unique effect name
|
|
1201
|
+
* @param effectFn Effect function that receives current state
|
|
1202
|
+
* @returns EffectRef for cleanup
|
|
1203
|
+
*/
|
|
1204
|
+
createEffect(name, effectFn) {
|
|
1205
|
+
return this.createEffectFromSignal(name, this.stateSig, effectFn);
|
|
1206
|
+
}
|
|
1186
1207
|
/**
|
|
1187
1208
|
* Create an effect that reacts to selected state changes
|
|
1188
1209
|
* @param name Unique effect name
|
|
@@ -1191,18 +1212,8 @@ class NgSimpleStateBaseSignalStore extends NgSimpleStateBaseCommonStore {
|
|
|
1191
1212
|
* @returns EffectRef for cleanup
|
|
1192
1213
|
*/
|
|
1193
1214
|
createSelectorEffect(name, selector, effectFn) {
|
|
1194
|
-
this.
|
|
1195
|
-
|
|
1196
|
-
let cleanup;
|
|
1197
|
-
const effectRef = runInInjectionContext(this.injector, () => effect(() => {
|
|
1198
|
-
const value = selectedSignal();
|
|
1199
|
-
if (cleanup) {
|
|
1200
|
-
cleanup();
|
|
1201
|
-
}
|
|
1202
|
-
cleanup = effectFn(value);
|
|
1203
|
-
}));
|
|
1204
|
-
this.registeredEffects.set(name, effectRef);
|
|
1205
|
-
return effectRef;
|
|
1215
|
+
const selectedSignal = computed(() => selector(this.stateSig()), ...(ngDevMode ? [{ debugName: "selectedSignal" }] : /* istanbul ignore next */ []));
|
|
1216
|
+
return this.createEffectFromSignal(name, selectedSignal, effectFn);
|
|
1206
1217
|
}
|
|
1207
1218
|
/**
|
|
1208
1219
|
* Destroy a specific effect by name
|
|
@@ -1288,11 +1299,11 @@ class NgSimpleStateBaseSignalStore extends NgSimpleStateBaseCommonStore {
|
|
|
1288
1299
|
}
|
|
1289
1300
|
return false;
|
|
1290
1301
|
}
|
|
1291
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.
|
|
1292
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.
|
|
1293
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.
|
|
1302
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseSignalStore, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1303
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: NgSimpleStateBaseSignalStore, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
|
|
1304
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseSignalStore }); }
|
|
1294
1305
|
}
|
|
1295
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.
|
|
1306
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: NgSimpleStateBaseSignalStore, decorators: [{
|
|
1296
1307
|
type: Injectable
|
|
1297
1308
|
}, {
|
|
1298
1309
|
type: Directive
|
|
@@ -1340,121 +1351,6 @@ function provideNgSimpleStatePlugins(plugins) {
|
|
|
1340
1351
|
}]);
|
|
1341
1352
|
}
|
|
1342
1353
|
|
|
1343
|
-
/**
|
|
1344
|
-
* Batch updates manager for grouping multiple state changes
|
|
1345
|
-
* into a single emission
|
|
1346
|
-
*/
|
|
1347
|
-
class NgSimpleStateBatchManager {
|
|
1348
|
-
constructor() {
|
|
1349
|
-
this.batching = false;
|
|
1350
|
-
this.pendingUpdates = new Map();
|
|
1351
|
-
this.isBatchingSig = signal(false, ...(ngDevMode ? [{ debugName: "isBatchingSig" }] : []));
|
|
1352
|
-
}
|
|
1353
|
-
/**
|
|
1354
|
-
* Get singleton instance
|
|
1355
|
-
*/
|
|
1356
|
-
static getInstance() {
|
|
1357
|
-
if (!NgSimpleStateBatchManager.instance) {
|
|
1358
|
-
NgSimpleStateBatchManager.instance = new NgSimpleStateBatchManager();
|
|
1359
|
-
}
|
|
1360
|
-
return NgSimpleStateBatchManager.instance;
|
|
1361
|
-
}
|
|
1362
|
-
/**
|
|
1363
|
-
* Check if currently batching
|
|
1364
|
-
*/
|
|
1365
|
-
get isBatching() {
|
|
1366
|
-
return this.isBatchingSig.asReadonly();
|
|
1367
|
-
}
|
|
1368
|
-
/**
|
|
1369
|
-
* Start a batch operation
|
|
1370
|
-
*/
|
|
1371
|
-
startBatch() {
|
|
1372
|
-
this.batching = true;
|
|
1373
|
-
this.isBatchingSig.set(true);
|
|
1374
|
-
}
|
|
1375
|
-
/**
|
|
1376
|
-
* End batch operation and flush all pending updates
|
|
1377
|
-
*/
|
|
1378
|
-
endBatch() {
|
|
1379
|
-
this.batching = false;
|
|
1380
|
-
this.isBatchingSig.set(false);
|
|
1381
|
-
this.flush();
|
|
1382
|
-
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Check if currently in batch mode
|
|
1385
|
-
*/
|
|
1386
|
-
isInBatch() {
|
|
1387
|
-
return this.batching;
|
|
1388
|
-
}
|
|
1389
|
-
/**
|
|
1390
|
-
* Queue an update for batching
|
|
1391
|
-
* @param storeId Unique store identifier
|
|
1392
|
-
* @param updateFn Update function to execute
|
|
1393
|
-
*/
|
|
1394
|
-
queueUpdate(storeId, updateFn) {
|
|
1395
|
-
if (!this.pendingUpdates.has(storeId)) {
|
|
1396
|
-
this.pendingUpdates.set(storeId, []);
|
|
1397
|
-
}
|
|
1398
|
-
this.pendingUpdates.get(storeId).push(updateFn);
|
|
1399
|
-
}
|
|
1400
|
-
/**
|
|
1401
|
-
* Flush all pending updates
|
|
1402
|
-
*/
|
|
1403
|
-
flush() {
|
|
1404
|
-
const updates = new Map(this.pendingUpdates);
|
|
1405
|
-
this.pendingUpdates.clear();
|
|
1406
|
-
updates.forEach((fns, storeId) => {
|
|
1407
|
-
// Execute only the last update for each store
|
|
1408
|
-
// since intermediate states are not needed
|
|
1409
|
-
const lastUpdate = fns[fns.length - 1];
|
|
1410
|
-
if (lastUpdate) {
|
|
1411
|
-
lastUpdate();
|
|
1412
|
-
}
|
|
1413
|
-
});
|
|
1414
|
-
}
|
|
1415
|
-
/**
|
|
1416
|
-
* Clear pending updates without executing
|
|
1417
|
-
*/
|
|
1418
|
-
clearPending() {
|
|
1419
|
-
this.pendingUpdates.clear();
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
/**
|
|
1423
|
-
* Execute multiple state updates in a batch
|
|
1424
|
-
* Only the final state is emitted
|
|
1425
|
-
*
|
|
1426
|
-
* @example
|
|
1427
|
-
* ```ts
|
|
1428
|
-
* batchState(() => {
|
|
1429
|
-
* store.setState({ count: 1 });
|
|
1430
|
-
* store.setState({ count: 2 });
|
|
1431
|
-
* store.setState({ count: 3 });
|
|
1432
|
-
* }); // Only emits once with count: 3
|
|
1433
|
-
* ```
|
|
1434
|
-
*/
|
|
1435
|
-
function batchState(updateFn) {
|
|
1436
|
-
const manager = NgSimpleStateBatchManager.getInstance();
|
|
1437
|
-
manager.startBatch();
|
|
1438
|
-
try {
|
|
1439
|
-
return updateFn();
|
|
1440
|
-
}
|
|
1441
|
-
finally {
|
|
1442
|
-
manager.endBatch();
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
/**
|
|
1446
|
-
* Execute multiple state updates in a batch asynchronously
|
|
1447
|
-
*/
|
|
1448
|
-
async function batchStateAsync(updateFn) {
|
|
1449
|
-
const manager = NgSimpleStateBatchManager.getInstance();
|
|
1450
|
-
manager.startBatch();
|
|
1451
|
-
try {
|
|
1452
|
-
return await updateFn();
|
|
1453
|
-
}
|
|
1454
|
-
finally {
|
|
1455
|
-
manager.endBatch();
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
1354
|
/**
|
|
1459
1355
|
* Transaction wrapper for atomic state updates
|
|
1460
1356
|
* Rolls back on error
|
|
@@ -1504,6 +1400,8 @@ class StateTransaction {
|
|
|
1504
1400
|
*
|
|
1505
1401
|
* @example
|
|
1506
1402
|
* ```ts
|
|
1403
|
+
* import { withTransaction } from 'ng-simple-state';
|
|
1404
|
+
*
|
|
1507
1405
|
* await withTransaction(store, async (tx) => {
|
|
1508
1406
|
* store.setState({ step: 1 });
|
|
1509
1407
|
* await apiCall(); // If this fails, state rolls back
|
|
@@ -1531,6 +1429,27 @@ async function withTransaction(store, fn) {
|
|
|
1531
1429
|
/**
|
|
1532
1430
|
* Debounce state updates
|
|
1533
1431
|
* Only the last update within the time window is applied
|
|
1432
|
+
*
|
|
1433
|
+
* @example
|
|
1434
|
+
* ```ts
|
|
1435
|
+
* import { createDebouncedUpdater } from 'ng-simple-state';
|
|
1436
|
+
*
|
|
1437
|
+
* // Create a debounced updater with 300ms delay
|
|
1438
|
+
* const { update, flush, cancel } = createDebouncedUpdater<MyState>(
|
|
1439
|
+
* (state) => store.setState(state),
|
|
1440
|
+
* 300
|
|
1441
|
+
* );
|
|
1442
|
+
*
|
|
1443
|
+
* // Rapid calls - only the last one is applied after 300ms
|
|
1444
|
+
* update({ searchQuery: 'a' });
|
|
1445
|
+
* update({ searchQuery: 'ab' });
|
|
1446
|
+
* update({ searchQuery: 'abc' }); // Only this is applied after 300ms
|
|
1447
|
+
*
|
|
1448
|
+
* // Force immediate update
|
|
1449
|
+
* flush();
|
|
1450
|
+
*
|
|
1451
|
+
* // Cancel any pending update
|
|
1452
|
+
* cancel();
|
|
1534
1453
|
*/
|
|
1535
1454
|
function createDebouncedUpdater(updateFn, delay = 100) {
|
|
1536
1455
|
let timeoutId = null;
|
|
@@ -1564,6 +1483,27 @@ function createDebouncedUpdater(updateFn, delay = 100) {
|
|
|
1564
1483
|
/**
|
|
1565
1484
|
* Throttle state updates
|
|
1566
1485
|
* At most one update per time window
|
|
1486
|
+
*
|
|
1487
|
+
* @example
|
|
1488
|
+
* ```ts
|
|
1489
|
+
* import { createThrottledUpdater } from 'ng-simple-state';
|
|
1490
|
+
*
|
|
1491
|
+
* // Create a throttled updater with 100ms delay
|
|
1492
|
+
* const { update, cancel } = createThrottledUpdater<MyState>(
|
|
1493
|
+
* (state) => store.setState(state),
|
|
1494
|
+
* 100
|
|
1495
|
+
* );
|
|
1496
|
+
*
|
|
1497
|
+
* // First call executes immediately, subsequent calls are throttled
|
|
1498
|
+
* update({ scrollPosition: 100 }); // Executes immediately
|
|
1499
|
+
* update({ scrollPosition: 150 }); // Queued
|
|
1500
|
+
* update({ scrollPosition: 200 }); // Replaces queued update
|
|
1501
|
+
*
|
|
1502
|
+
* // After 100ms, { scrollPosition: 200 } is applied
|
|
1503
|
+
*
|
|
1504
|
+
* // Cancel pending throttled update
|
|
1505
|
+
* cancel();
|
|
1506
|
+
```
|
|
1567
1507
|
*/
|
|
1568
1508
|
function createThrottledUpdater(updateFn, delay = 100) {
|
|
1569
1509
|
let lastCall = 0;
|
|
@@ -1608,5 +1548,5 @@ function createThrottledUpdater(updateFn, delay = 100) {
|
|
|
1608
1548
|
* Generated bundle index. Do not edit.
|
|
1609
1549
|
*/
|
|
1610
1550
|
|
|
1611
|
-
export { NG_SIMPLE_STATE_PLUGINS, NG_SIMPLE_STATE_UNDO_REDO, NG_SIMPLE_STORE_CONFIG, NgSimpleStateBaseCommonStore, NgSimpleStateBaseRxjsStore, NgSimpleStateBaseSignalStore,
|
|
1551
|
+
export { NG_SIMPLE_STATE_PLUGINS, NG_SIMPLE_STATE_UNDO_REDO, NG_SIMPLE_STORE_CONFIG, NgSimpleStateBaseCommonStore, NgSimpleStateBaseRxjsStore, NgSimpleStateBaseSignalStore, NgSimpleStateDevTool, NgSimpleStateLocalStorage, NgSimpleStateModule, NgSimpleStateSessionStorage, StateTransaction, createDebouncedUpdater, createThrottledUpdater, persistPlugin, provideNgSimpleState, provideNgSimpleStatePlugins, undoRedoPlugin, withTransaction };
|
|
1612
1552
|
//# sourceMappingURL=ng-simple-state.mjs.map
|