Skip to content

Commit

Permalink
Merge pull request #114 from moniuch/feature/mergedeep-rehydrate-no-m…
Browse files Browse the repository at this point in the history
…utation

Feature/mergedeep rehydrate no mutation
  • Loading branch information
bufke authored Jan 19, 2019
2 parents 7b98fb7 + 381b6fc commit 8434a2d
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 34 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,8 @@
"typescript": "^2.1.4",
"zone.js": "^0.7.7"
},
"typings": "./dist/index.d.ts"
"typings": "./dist/index.d.ts",
"dependencies": {
"lodash.merge": "^4.6.1"
}
}
55 changes: 54 additions & 1 deletion spec/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,30 @@ describe('ngrxLocalStorage', () => {
expect(finalState.state instanceof TypeA).toBeFalsy();
});

it('filtered - multiple keys at root - should properly revive partial state', function () {
const s = new MockStorage();
const skr = mockStorageKeySerializer;

// state at any given moment, subject to sync selectively
const nestedState = {
app: { app1: true, app2: [1, 2], app3: { any: 'thing' } },
feature1: { slice11: true, slice12: [1, 2], slice13: { any: 'thing' } },
feature2: { slice21: true, slice22: [1, 2], slice23: { any: 'thing' } },
};

// test selective write to storage
syncStateUpdate(nestedState, [
{ 'feature1': ['slice11', 'slice12'] },
{ 'feature2': ['slice21', 'slice22'] },
], s, skr, false);

const raw1 = s.getItem('feature1');
expect(raw1).toEqual(jasmine.arrayContaining(['slice11', 'slice12']));

const raw2 = s.getItem('feature2');
expect(raw2).toEqual(jasmine.arrayContaining(['slice21', 'slice22']));
});

it('reviver', () => {
// Use the reviver option to restore including classes

Expand Down Expand Up @@ -433,4 +457,33 @@ describe('ngrxLocalStorage', () => {
const finalState = metaReducer(reducer)(initialState, action);
expect(finalState.state.astring).toEqual(initialState.state.astring);
});
});

it('should merge selectively saved state and rehydrated state', () => {
const initialState = {
app: { app1: false, app2: [], app3: {} },
feature1: { slice11: false, slice12: [], slice13: {} },
feature2: { slice21: false, slice22: [], slice23: {} },
};

// A legit case where state is saved in chunks rather than as a single object
localStorage.setItem('feature1', JSON.stringify({ slice11: true, slice12: [1, 2] }));
localStorage.setItem('feature2', JSON.stringify({ slice21: true, slice22: [1, 2] }));

// Set up reducers
const reducer = (state = initialState, action) => state;
const metaReducer = localStorageSync({keys: [
{'feature1': ['slice11', 'slice12']},
{'feature2': ['slice21', 'slice22']},
], rehydrate: true});

const action = {type: INIT_ACTION};

// Resultant state should merge the rehydrated partial state and our initial state
const finalState = metaReducer(reducer)(initialState, action);
expect(finalState).toEqual({
app: { app1: false, app2: [], app3: {} },
feature1: { slice11: true, slice12: [1, 2], slice13: {} },
feature2: { slice21: true, slice22: [1, 2], slice23: {} },
});
});
});
61 changes: 29 additions & 32 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as merge from 'lodash.merge';

const INIT_ACTION = '@ngrx/store/init';
const UPDATE_ACTION = '@ngrx/store/update-reducers';
const detectDate = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/;
Expand Down Expand Up @@ -237,39 +239,34 @@ export const localStorageSync = (config: LocalStorageConfig) => (
)
: undefined;

return function(state = rehydratedState, action: any) {
/*
Handle case where state is rehydrated AND initial state is supplied.
Any additional state supplied will override rehydrated state for the given key.
*/
if (
(action.type === INIT_ACTION || action.type === UPDATE_ACTION) &&
rehydratedState
) {
if (state) {
Object.keys(state).forEach(function (key) {
if (state[key] instanceof Array && rehydratedState[key] instanceof Array) {
state[key] = rehydratedState[key];
} else if (typeof state[key] === 'object'
&& typeof rehydratedState[key] === 'object') {
state[key] = Object.assign({}, state[key], rehydratedState[key]);
} else {
state[key] = rehydratedState[key];
}
});
} else {
state = Object.assign({}, state, rehydratedState);
}
return function (state, action: any) {
let nextState;

// If state arrives undefined, we need to let it through the supplied reducer
// in order to get a complete state as defined by user
if ((action.type === INIT_ACTION) && !state) {
nextState = reducer(state, action);
} else {
nextState = { ...state };
}

if ((action.type === INIT_ACTION || action.type === UPDATE_ACTION) && rehydratedState) {
nextState = merge({}, nextState, rehydratedState);
}
const nextState = reducer(state, action);
syncStateUpdate(
nextState,
stateKeys,
config.storage,
config.storageKeySerializer,
config.removeOnUndefined,
config.syncCondition
);

nextState = reducer(nextState, action);

if (action.type !== INIT_ACTION) {
syncStateUpdate(
nextState,
stateKeys,
config.storage,
config.storageKeySerializer,
config.removeOnUndefined,
config.syncCondition,
);
}

return nextState;
};
};
Expand Down

0 comments on commit 8434a2d

Please sign in to comment.