Skip to content

Commit ea77578

Browse files
committed
fix: storage syncing for feature stores
1 parent 8aef73f commit ea77578

File tree

3 files changed

+469
-31
lines changed

3 files changed

+469
-31
lines changed

README.md

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
![bundle size](https://img.shields.io/bundlephobia/minzip/ngrx-store-localstorage)
44
![npm weekly downloads](https://img.shields.io/npm/dw/ngrx-store-localstorage)
5-
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
5+
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
66
[![CircleCI](https://circleci.com/gh/btroncone/ngrx-store-localstorage.svg?style=svg)](https://circleci.com/gh/btroncone/ngrx-store-localstorage)
77

88
Simple syncing between ngrx store and local or session storage.
@@ -47,6 +47,33 @@ const metaReducers: Array<MetaReducer<any, any>> = [localStorageSyncReducer];
4747
export class MyAppModule {}
4848
```
4949

50+
3. For feature modules, enable the `forFeature` flag
51+
52+
```ts
53+
import { NgModule } from '@angular/core';
54+
import { BrowserModule } from '@angular/platform-browser';
55+
import { StoreModule, ActionReducerMap, ActionReducer, MetaReducer } from '@ngrx/store';
56+
import { localStorageSync } from 'ngrx-store-localstorage';
57+
import { featureReducer } from './reducer';
58+
59+
export function localStorageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
60+
return localStorageSync({keys: ['todos'], forFeature: true})(reducer);
61+
}
62+
const metaReducers: Array<MetaReducer<any, any>> = [localStorageSyncReducer];
63+
64+
@NgModule({
65+
imports: [
66+
BrowserModule,
67+
StoreModule.forFeature(
68+
'feature',
69+
featureReducer,
70+
{metaReducers}
71+
)
72+
]
73+
})
74+
export class MyFeatureModule {}
75+
```
76+
5077
## API
5178

5279
### `localStorageSync(config: LocalStorageConfig): Reducer`
@@ -97,25 +124,26 @@ An interface defining the configuration attributes to bootstrap `localStorageSyn
97124
* `syncCondition` (optional) `(state) => boolean`: When set, sync to storage medium will only occur when this function returns a true boolean. Example: `(state) => state.config.syncToStorage` will check the state tree under config.syncToStorage and if true, it will sync to the storage. If undefined or false it will not sync to storage. Often useful for "remember me" options in login.
98125
* `checkStorageAvailability` \(*boolean? = false*): Specify if the storage availability checking is expected, i.e. for server side rendering / Universal.
99126
* `mergeReducer` (optional) `(state: any, rehydratedState: any, action: any) => any`: Defines the reducer to use to merge the rehydrated state from storage with the state from the ngrx store. If unspecified, defaults to performing a full deepmerge on an `INIT_ACTION` or an `UPDATE_ACTION`.
127+
* `forFeature` \(*boolean? = false*): Specify if the storage sync should be performed for a feature store.
100128

101129
### Usage
102130

103131
#### Key Prefix
104132

105133
```ts
106134
localStorageSync({
107-
keys: ['todos', 'visibilityFilter'],
108-
storageKeySerializer: (key) => `cool_${key}`,
135+
keys: ['todos', 'visibilityFilter'],
136+
storageKeySerializer: (key) => `cool_${key}`,
109137
});
110-
```
138+
```
111139
In above example `Storage` will use keys `cool_todos` and `cool_visibilityFilter` keys to store `todos` and `visibilityFilter` slices of state). The key itself is used by default - `(key) => key`.
112140

113141
#### Target Depth Configuration
114142

115143
```ts
116144
localStorageSync({
117145
keys: [
118-
{ feature1: [{ slice11: ['slice11_1'], slice14: ['slice14_2'] }] },
146+
{ feature1: [{ slice11: ['slice11_1'], slice14: ['slice14_2'] }] },
119147
{ feature2: ['slice21'] }
120148
],
121149
});

projects/lib/src/lib/index.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ export const rehydrateApplicationState = (
4242
keys: Keys,
4343
storage: Storage,
4444
storageKeySerializer: (key: string) => string,
45-
restoreDates: boolean
45+
restoreDates: boolean,
46+
forFeature: boolean
4647
) => {
4748
return (keys as any[]).reduce((acc, curr) => {
4849
let key = curr;
@@ -93,8 +94,10 @@ export const rehydrateApplicationState = (
9394
raw = JSON.parse(stateSlice, reviver);
9495
}
9596

96-
return Object.assign({}, acc, {
97-
[key]: deserialize ? deserialize(raw) : raw,
97+
const rehydratedState = deserialize ? deserialize(raw) : raw;
98+
99+
return forFeature ? rehydratedState : Object.assign({}, acc, {
100+
[key]: rehydratedState,
98101
});
99102
}
100103
}
@@ -132,7 +135,8 @@ export const syncStateUpdate = (
132135
storage: Storage,
133136
storageKeySerializer: (key: string | number) => string,
134137
removeOnUndefined: boolean,
135-
syncCondition?: (state: any) => any
138+
syncCondition?: (state: any) => any,
139+
forFeature?: boolean
136140
) => {
137141
if (syncCondition) {
138142
try {
@@ -149,14 +153,14 @@ export const syncStateUpdate = (
149153
}
150154

151155
keys.forEach((key: string | KeyConfiguration | Options | ((key: string, value: any) => any)): void => {
152-
let stateSlice = state[key as string];
156+
let stateSlice = state?.[key as string];
153157
let replacer;
154158
let space: string | number;
155159
let encrypt;
156160

157161
if (typeof key === 'object') {
158162
let name = Object.keys(key)[0];
159-
stateSlice = state[name];
163+
stateSlice = forFeature ? state : state[name];
160164

161165
if (typeof stateSlice !== 'undefined' && key[name]) {
162166
// use serialize function if specified.
@@ -188,14 +192,16 @@ export const syncStateUpdate = (
188192
}
189193

190194
/*
191-
Replacer and space arguments to pass to JSON.stringify.
192-
If these fields don't exist, undefined will be passed.
193-
*/
195+
Replacer and space arguments to pass to JSON.stringify.
196+
If these fields don't exist, undefined will be passed.
197+
*/
194198
replacer = key[name].replacer;
195199
space = key[name].space;
196200
}
197201

198202
key = name;
203+
} else if (typeof key === 'string') {
204+
stateSlice = forFeature ? state : state[key];
199205
}
200206

201207
if (typeof stateSlice !== 'undefined' && storage !== undefined) {
@@ -262,7 +268,7 @@ export const localStorageSync = (config: LocalStorageConfig) => (reducer: any) =
262268

263269
const stateKeys = validateStateKeys(config.keys);
264270
const rehydratedState = config.rehydrate
265-
? rehydrateApplicationState(stateKeys, config.storage, config.storageKeySerializer, config.restoreDates)
271+
? rehydrateApplicationState(stateKeys, config.storage, config.storageKeySerializer, config.restoreDates, config.forFeature)
266272
: undefined;
267273

268274
return function (state: any, action: any) {
@@ -289,7 +295,8 @@ export const localStorageSync = (config: LocalStorageConfig) => (reducer: any) =
289295
config.storage,
290296
config.storageKeySerializer as (key: string | number) => string,
291297
config.removeOnUndefined,
292-
config.syncCondition
298+
config.syncCondition,
299+
config.forFeature
293300
);
294301
}
295302

@@ -307,6 +314,7 @@ export interface LocalStorageConfig {
307314
syncCondition?: (state: any) => any;
308315
checkStorageAvailability?: boolean;
309316
mergeReducer?: (state: any, rehydratedState: any, action: any) => any;
317+
forFeature?: boolean;
310318
}
311319

312320
interface KeyConfiguration {

0 commit comments

Comments
 (0)