Skip to content

Commit f60bedc

Browse files
docs: improve docs for withStorageSync (#203)
1 parent 1340b1f commit f60bedc

File tree

1 file changed

+138
-27
lines changed

1 file changed

+138
-27
lines changed

docs/docs/with-storage-sync.md

Lines changed: 138 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,159 @@
22
title: withStorageSync()
33
---
44

5-
```typescript
6-
import { withStorageSync } from '@angular-architects/ngrx-toolkit';
7-
```
8-
9-
`withStorageSync` adds automatic or manual synchronization with Web Storage (`localstorage`/`sessionstorage`).
5+
`withStorageSync` synchronizes state with Web Storage (`localStorage`/`sessionStorage`) and IndexedDB (via an async strategy).
106

117
:::warning
12-
As Web Storage only works in browser environments it will fallback to a stub implementation on server environments.
8+
As Web Storage and IndexedDB only work in browser environments, it will fallback to a stub implementation on server environments.
139
:::
1410

1511
Example:
1612

1713
```typescript
1814
import { withStorageSync } from '@angular-architects/ngrx-toolkit';
1915

20-
const SyncStore = signalStore(
21-
withStorageSync<User>({
22-
key: 'synced', // key used when writing to/reading from storage
23-
autoSync: false, // read from storage on init and write on state changes - `true` by default
24-
select: (state: User) => Partial<User>, // projection to keep specific slices in sync
25-
parse: (stateString: string) => State, // custom parsing from storage - `JSON.parse` by default
26-
stringify: (state: User) => string, // custom stringification - `JSON.stringify` by default
27-
storage: () => sessionstorage, // factory to select storage to sync with
16+
const UserStore = signalStore(
17+
withState({ name: 'John' }),
18+
// automatically synchronizes state to localStorage on each change via the key 'user'
19+
withStorageSync('user'),
20+
);
21+
```
22+
23+
## Auto Sync
24+
25+
By default, `withStorageSync` reads from storage on initialization and writes on every subsequent state change. You can customize or disable this behavior via the `autoSync` option.
26+
27+
```typescript
28+
const UserStore = signalStore(
29+
withState({ name: 'John' }),
30+
withStorageSync({
31+
key: 'user',
32+
autoSync: false, // Disable automatic synchronization
33+
}),
34+
);
35+
```
36+
37+
With auto sync disabled, you control synchronization manually. The following methods are available: `readFromStorage`, `writeToStorage`, `clearStorage`.
38+
39+
```typescript
40+
const store = inject(UserStore);
41+
42+
store.readFromStorage(); // Read from storage (e.g., on init)
43+
44+
// ...update state as needed...
45+
store.writeToStorage(); // Persist the current state to storage
46+
47+
store.clearStorage(); // Remove the stored value
48+
```
49+
50+
Notes:
51+
52+
- When `autoSync: true` (default):
53+
- On init, the store reads the saved state from storage (if present) and patches it into the store.
54+
- On each state change, the state is written to storage.
55+
- When `autoSync: false`:
56+
- No automatic read/write occurs; call the exposed methods to sync at your preferred times.
57+
- With async storage strategies (e.g., IndexedDB), ensure writes that depend on persisted data happen after the initial read. Use `store.whenSynced()` or disable auto sync and orchestrate manually.
58+
59+
## Serialization (parse/stringify)
60+
61+
`withStorageSync` uses `JSON.stringify` to write and `JSON.parse` to read by default. You can customize both to control how data is stored and restored.
62+
63+
- `stringify: (state) => string`: transforms the state into a string for storage
64+
- `parse: (stateString) => object`: transforms the stored string back into an object that will be patched into the store
65+
66+
Example (handling special types):
67+
68+
```typescript
69+
const UserStore = signalStore(
70+
withState({ name: 'John', birthday: new Date('1990-01-01') }),
71+
withStorageSync({
72+
key: 'user',
73+
stringify: (state) => JSON.stringify({ ...state, birthday: state.birthday.toISOString() }),
74+
parse: (stateString) => {
75+
const serialized = JSON.parse(stateString);
76+
return {
77+
...serialized,
78+
birthday: new Date(serialized.birthday),
79+
};
80+
},
81+
}),
82+
);
83+
```
84+
85+
## Select (synchronize only what you need)
86+
87+
Use `select` to persist only a subset of your state instead of the whole object. By default, the entire state is persisted.
88+
89+
Behavior:
90+
91+
- `select` runs before `stringify` during writes.
92+
- On reads, the result of `parse` is passed to `patchState(...)`. Return a subset that matches your store's shape; only those keys will be updated.
93+
94+
Example (persist only name and birthday):
95+
96+
```typescript
97+
const UserStore = signalStore(
98+
withState({ name: 'John', birthday: new Date('1990-01-01'), sessionToken: 'secret' }),
99+
withStorageSync({
100+
key: 'user',
101+
// Only persist the public fields; omit sensitive/ephemeral data
102+
select: ({ name, birthday }) => ({ name, birthday }),
28103
}),
29104
);
30105
```
31106

107+
## Session Storage
108+
109+
Use `withSessionStorage()` to synchronize with `sessionStorage` instead of `localStorage`.
110+
111+
```typescript
112+
import { withSessionStorage, withStorageSync } from '@angular-architects/ngrx-toolkit';
113+
114+
const UserStore = signalStore(withState({ name: 'John' }), withStorageSync('user', withSessionStorage()));
115+
```
116+
117+
Notes:
118+
119+
- Session storage is cleared when the page session ends (e.g., tab closes) and is scoped per-tab.
120+
- Prefer `withSessionStorage()` over the deprecated `storage` option in the config.
121+
122+
## IndexedDB (async storage)
123+
124+
Use `withIndexedDB()` to synchronize with IndexedDB. Because IndexedDB is asynchronous, all reads and writes are performed asynchronously. You must wait for the initial read during app initialization (via `whenSynced()`), and we recommend disabling auto sync for predictable sequencing and better DX (avoids sprinkling `whenSynced()` after each change).
125+
32126
```typescript
33-
@Component(...)
34-
public class SyncedStoreComponent {
35-
private syncStore = inject(SyncStore);
127+
import { withIndexedDB, withStorageSync } from '@angular-architects/ngrx-toolkit';
128+
import { withHooks, patchState } from '@ngrx/signals';
129+
130+
// Recommended: disable autoSync to control sequencing explicitly
131+
const UserStore = signalStore(
132+
withState({ name: 'John', birthday: new Date('1990-01-01') }),
133+
withStorageSync({ key: 'user', autoSync: false }, withIndexedDB()),
134+
withHooks({
135+
async onInit(store) {
136+
// Ensure initial state is read from IndexedDB before any writes
137+
await store.readFromStorage();
138+
},
139+
}),
140+
);
141+
```
36142

37-
updateFromStorage(): void {
38-
this.syncStore.readFromStorage(); // reads the stored item from storage and patches the state
39-
}
143+
If you keep `autoSync: true`, wait for the initial read before performing writes that depend on persisted data. Also, because every `patchState` triggers an async write, call `whenSynced()` after state changes when subsequent logic relies on the persisted result.
40144

41-
updateStorage(): void {
42-
this.syncStore.writeToStorage(); // writes the current state to storage
43-
}
145+
```typescript
146+
const UserStore = signalStore(
147+
withState({ name: 'John', birthday: new Date('1990-01-01') }),
148+
withStorageSync({ key: 'user' }, withIndexedDB()), // autoSync defaults to true
149+
);
44150

45-
clearStorage(): void {
46-
this.syncStore.clearStorage(); // clears the stored item in storage
47-
}
48-
}
151+
const store = inject(UserStore);
152+
await store.whenSynced(); // wait on initialization
153+
// ... patch state ...
154+
patchState(store, { birthday: new Date() });
155+
await store.whenSynced(); // ensure the write completed before dependent logic
49156
```
157+
158+
Notes:
159+
160+
- Methods are async with IndexedDB: `readFromStorage()`, `writeToStorage()`, and `clearStorage()` return `Promise<void>`.

0 commit comments

Comments
 (0)