Skip to content
Highly configurable state syncing (with include/exclude keys) between the @ngrx/store and localstorage/sessionstorage
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.vscode
lib updates README Mar 31, 2019
.gitignore
.npmignore
.travis.yml added husky Mar 21, 2019
CHANGELOG.md
ISSUE_TEMPLATE.md
LICENSE
README.md updates README Mar 31, 2019
_config.yml
jest.config.js
package-lock.json
package.json
tsconfig.json
tslint.json

README.md

@larscom/ngrx-store-storagesync

npm-release git-release travis build license

Highly configurable state syncing between the @ngrx/store and localstorage/sessionstorage.

State sync

You can sync only the objects you need, allowing you to exclude/include deeply nested keys.
You can sync different 'feature' states to different storage locations. For example:

  • feature1 to sessionStorage
  • feature2 to localStorage

Dependencies

@larscom/ngrx-store-storagesync depends on @ngrx/store and Angular 2+.

Installation

npm i --save @larscom/ngrx-store-storagesync

Usage

1. Wrap storageSync in an exported function and include it in your meta-reducers array in StoreModule.forRoot

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule, ActionReducerMap, ActionReducer, MetaReducer } from '@ngrx/store';
import { routerReducer } from '@ngrx/router-store';
import { storageSync } from '@larscom/ngrx-store-storagesync';
import * as fromFeature1 from './feature/reducer';
import * as fromFeature2 from './feature2/reducer';
import * as fromFeature3 from './feature3/reducer';

export const reducers: ActionReducerMap<IState> = {
  router: routerReducer,
  feature1: fromFeature1.reducer,
  feature2: fromFeature2.reducer,
  // feature3 does not get synced to storage at all
  feature3: fromFeature3.reducer
};

export function storageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
  return storageSync<IState>({
    features: [
      // save only router state to sessionStorage
      { stateKey: 'router', storageForFeature: window.sessionStorage },

      // exclude key 'success' inside 'auth' and key 'loading' inside 'feature1'
      { stateKey: 'feature1', excludeKeys: ['auth.success', 'loading'] },

      // only include key 'success' inside 'auth' and all keys with 'loading' inside 'feature2'
      { stateKey: 'feature2', includeKeys: ['auth.success', 'loading'] }
    ],
    // defaults to localStorage
    storage: window.localStorage
  })(reducer);
}

const metaReducers: Array<MetaReducer<any, any>> = [storageSyncReducer];

@NgModule({
  imports: [BrowserModule, StoreModule.forRoot(reducers, { metaReducers })]
})
export class AppModule {}

Deserializing

By default the state gets deserialized and parsed by JSON.parse with an ISO date reviver. This means that your ISO date objects gets stored as string, and restored as Date

If you do not want this behaviour, you can implement your own deserialize function.

Configuration

export interface IStorageSyncOptions<T> {
  /**
   * By default, states are not synced, provide the feature states you want to sync.
   */
  features: Array<IFeatureOptions<T>>;
  /**
   * Provide the storage type to sync the state to, it can be any storage which implements the 'Storage' interface.
   */
  storage: Storage;
  /**
   * Function that gets executed on a storage error
   * @param error the error that occurred
   */
  storageError?: (error: any) => void;
  /**
   * Pull initial state from storage on startup
   * @default rehydrate true
   */
  rehydrate?: boolean;
  /**
   * Serializer for storage keys
   * @param key the storage item key
   * @default storageKeySerializer (key: string) => key
   */
  storageKeySerializer?: (key: string) => string;
  /**
   * Custom state merge function after rehydration (by default it does a deep merge)
   * @param state the next state
   * @param rehydratedState the state resolved from a storage location
   * @default rehydrateStateMerger (state: T, rehydratedState: T) => deepMerge(state, rehydratedState)
   */
  rehydrateStateMerger?: (state: T, rehydratedState: T) => T;
}
export interface IFeatureOptions<T> {
  /**
   * The name of the feature state
   */
  stateKey: string;
  /**
   * Filter out properties that exist on the feature state.
   * Can't be used together with includeKeys
   * @see includeKeys
   * @throws StorageSyncError if includeKeys is also present
   */
  excludeKeys?: string[];
  /**
   * Only sync these properties on the feature state
   * Can't be used together with excludeKeys
   * @see excludeKeys
   * @throws StorageSyncError if excludeKeys is also present
   */
  includeKeys?: string[];
  /**
   * Provide the storage type to sync the feature state to,
   * it can be any storage which implements the 'Storage' interface.
   *
   * It will override the storage property in StorageSyncOptions
   * @see IStorageSyncOptions
   */
  storageForFeature?: Storage;
  /**
   * Sync to storage will only occur when this function returns true
   * @param featureState the next feature state
   * @param state the next state
   * @default shouldSync(featureState: Partial<T>, state: T) => true
   */
  shouldSync?: (featureState: Partial<T>, state: T) => boolean;
  /**
   * Serializer for storage keys (feature state),
   * it will override the global storageKeySerializer for this feature
   * @param key the storage item key
   * @default storageKeySerializerForFeature(key: string) => key
   */
  storageKeySerializerForFeature?: (key: string) => string;
  /**
   * Serializer for the feature state (before saving to a storage location)
   * @param featureState the next feature state
   * @default serialize(featureState: Partial<T>) => JSON.stringify(featureState)
   */
  serialize?: (featureState: Partial<T>) => string;
  /**
   * Deserializer for the feature state (after getting the state from a storage location)
   *
   * ISO Date objects which are stored as a string gets revived as Date object by default.
   * @param featureState the feature state retrieved from a storage location
   * @default deserialize (featureState: string) => JSON.Parse(featureState)
   */
  deserialize?: (featureState: string) => Partial<T>;
}
You can’t perform that action at this time.