Git Product home page Git Product logo

ngrx-store-localstorage's Introduction

ngrx-store-localstorage

bundle size npm weekly downloads semantic-release CircleCI

Simple syncing between ngrx store and local or session storage.

Dependencies

ngrx-store-localstorage depends on @ngrx/store and Angular 12+.

Usage

npm install ngrx-store-localstorage --save
  1. Wrap localStorageSync in an exported function.
  2. Include 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 { localStorageSync } from 'ngrx-store-localstorage';
import { reducers } from './reducers';


const reducers: ActionReducerMap<IState> = {todos, visibilityFilter};

export function localStorageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
  return localStorageSync({keys: ['todos']})(reducer);
}
const metaReducers: Array<MetaReducer<any, any>> = [localStorageSyncReducer];

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

API

localStorageSync(config: LocalStorageConfig): Reducer

Provide state (reducer) keys to sync with local storage. Returns a meta-reducer.

Arguments

  • config An object that matches with the LocalStorageConfig interface, keys is the only required property.

LocalStorageConfig

An interface defining the configuration attributes to bootstrap localStorageSync. The following are properties which compose LocalStorageConfig:

  • keys (required) State keys to sync with local storage. The keys can be defined in two different formats:

    • string[]: Array of strings representing the state (reducer) keys. Full state will be synced (e.g. localStorageSync({keys: ['todos']})).

    • object[]: Array of objects where for each object the key represents the state key and the value represents custom serialize/deserialize options. This can be one of the following:

      • An array of properties which should be synced. This allows for the partial state sync (e.g. localStorageSync({keys: [{todos: ['name', 'status'] }, ... ]})).

      • A reviver function as specified in the JSON.parse documentation.

      • An object with properties that specify one or more of the following:

        • serialize: A function that takes a state object and returns a plain json object to pass to json.stringify.

        • deserialize: A function that takes that takes the raw JSON from JSON.parse and builds a state object.

        • replacer: A replacer function as specified in the JSON.stringify documentation.

        • space: The space value to pass JSON.stringify.

        • reviver: A reviver function as specified in the JSON.parse documentation.

        • filter: An array of properties which should be synced (same format as the stand-alone array specified above).

        • encrypt: A function that takes a state string and returns an encrypted version of that string. e.g. (state: string) => btoa(state)

        • decrypt: A function that takes a state string and returns a decrypted version of that string. e.g. (state: string) => atob(state)

  • rehydrate (optional) boolean: Pull initial state from local storage on startup, this will default to false.

  • storage (optional) Storage: Specify an object that conforms to the Web Storage API interface to use, this will default to localStorage.

  • removeOnUndefined (optional) boolean: Specify if the state is removed from the storage when the new value is undefined, this will default to false.

  • storageKeySerializer (optional) (key: string) => string: Custom serialize function for storage keys, used to avoid Storage conflicts.

  • restoreDates (boolean? = true): Restore serialized date objects. If you work directly with ISO date strings, set this option to false.

  • 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.

  • checkStorageAvailability (boolean? = false): Specify if the storage availability checking is expected, i.e. for server side rendering / Universal.

  • 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.

Usage

Key Prefix

localStorageSync({
  keys: ['todos', 'visibilityFilter'], 
  storageKeySerializer: (key) => `cool_${key}`, 
});

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.

Target Depth Configuration

localStorageSync({
  keys: [
      { feature1: [{ slice11: ['slice11_1'], slice14: ['slice14_2'] }] }, 
      { feature2: ['slice21'] }
  ],
});

In this example, feature1.slice11.slice11_1, feature1.slice14.slice14_2, and feature2.slice21 will be synced to localStorage.feature1 and localStorage.feature2.

Known Workarounds

  1. Sync state across multiple tabs

Release Notes / Changelog

From version 10 onwards, check GitHub Releases for release notes. For older versions check the CHANGELOG.md.

Contributing

See CONTRIBUTING.md for instructions on how to contribute.

ngrx-store-localstorage's People

Contributors

aanaya-deltacom avatar andreevwork avatar arturhun avatar bblackwo avatar btroncone avatar bufke avatar dependabot[bot] avatar ernestomancebo avatar etatuev avatar filipvh avatar gravllift avatar jmetev1 avatar jondewoo avatar mkuklis avatar mmmichl avatar nhaesler avatar nicorivat avatar nirving avatar papaiatis avatar piotter121 avatar plisovyi avatar rambo-bertelsmann avatar rdlabo avatar rowandh avatar saulshanabrook avatar sf31 avatar shahmirn avatar tanyagray avatar wdunn001 avatar yngvebn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ngrx-store-localstorage's Issues

Objects become Json Objects

I'm storing js objects into the ngrx store.. This is working OK, but when these are injected back by the localstorge library these aren't objects anymore but json objects. I want to be able to turn these json objects back to my own objects just before they are injected back into the store.

Is there any callback function, or point where I can do this?

Setting up issue. Types of property 'metaReducers' are incompatible.

When I am setting up the thing getting an error Types of property 'metaReducers' are incompatible. ActionReducer<any, any>[]' is not assignable to type 'MetaReducer<State, Action>[]'.

import { reducers } from './store/reducers';
export function localStorageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
  return localStorageSync({keys: ['auth']})(reducer);
}
const metaReducers: Array<ActionReducer<any, any>> = [localStorageSyncReducer];

@NgModule({
  imports: [
    StoreModule.forRoot(
        reducers,
        {metaReducers} //error here
    )
  ]
})

my reducers looks like

export const reducers: ActionReducerMap<State> = {
  layout: fromLayout.reducer,
  routerReducer: fromRouter.routerReducer,
  appAlert: fromSnackbar.reducer,
  auth: fromAuth.reducer,
  sites: fromSites.reducer
};

Any ideas?

Am on @ngrx/[email protected],

ngrx 4 + prod flag

I was able to integrate localstorage and it works when running with ng serve'. However, running the ng build --prod` I get the following exception:

ERROR in Error encountered resolving symbol values statically. Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler

The way I add a localstoragesync is (app.module.ts):

StoreModule.forRoot(reducers , {
       metaReducers: [
         localStorageSync({keys: ['filter', 'auth']})
       ]
     })

As soon as I remove metaReducers, it builds successfully

Exception: Call to Node module failed with error: Prerendering failed because of error: ReferenceError: localStorage is not defined

I am getting this error and not sure how to get around it. From what I can see in the other examples, nobody seems to be passing a localStorage parameter to localStorageSync like the doc says. Also, the example on the main page doesn't do this either.

Exception: Call to Node module failed with error: Prerendering failed because of error: ReferenceError: localStorage is not defined
at Object.exports.localStorageSync (C:\Users\jordanmc\Source\Repos\Xbox.Ambassadors\Microsoft.Ambassadors.Admin\Microsoft.Ambassadors.Admin\node_modules\ngrx-store-localstorage\dist\index.js:104:41)

Any ideas what I am missing?

Thanks!

const developmentReducer: ActionReducer<State> = compose(
    localStorageSync(['ambassador', 'adminUser'], true),
    storeLogger({
        level: 'info',
        collapsed: true,
    }),
    combineReducers)(reducers);

init state patrly from localstorage and patrly from initial state

I would like to use localStorageSync in a way that it will partly pull the state from localstoarge and partly from the initial state definition.
For example, assuming that this is my reducer:

export interface LoginState {
    permissions: Permissions [];
    userId: number;
    created: number;
};

And syncing it with localstorage like that:
localStorageSync([{login: [โ€˜permissionsโ€™]}], true)

The problem is that the state contains now only the permissions object, without the default values I declared for userId & created.

Is there any way doing it?
What about the revive method mentioned in doc? I havenโ€™t seen any example of how to use it.

Thanks in advance,
Lior

Storage on demand

Let's say I want to save my data from the Store to Storage. but after some logic time something change and I don't want to save to the Storage.

For example: "Remember me" at login.
After login I get the token then I save it to the Store (and to the Storage).
But when I turn off "Remember me" I want to save the token to the Store (and NOT to the Storage)

ERROR in Error encountered resolving symbol values statically. Function calls are not supported.

The error message:

ERROR in Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function (position 6:54 in the original .ts file), resolving symbol authReducer in /Users/tyrion/devel/saveup-policy/src/app/saveup-common/common-core/common-auth/auth-store/auth-reducer.ts, resolving symbol CommonAuthModule.reducers in /Users/tyrion/devel/saveup-policy/src/app/saveup-common/common-core/common-auth/common-auth.module.ts, resolving symbol CoreModule.reducers in /Users/tyrion/devel/saveup-policy/src/app/core/core.module.ts, resolving symbol AppModule.reducers in /Users/tyrion/devel/saveup-policy/src/app/app.module.ts, resolving symbol AppModule.reducers in /Users/tyrion/devel/saveup-policy/src/app/app.module.ts, resolving symbol AppModule.reducers in /Users/tyrion/devel/saveup-policy/src/app/app.module.ts, resolving symbol AppModule in /Users/tyrion/devel/saveup-policy/src/app/app.module.ts, resolving symbol AppModule in /Users/tyrion/devel/saveup-policy/src/app/app.module.ts, resolving symbol AppModule in /Users/tyrion/devel/saveup-policy/src/app/app.module.ts

My app.module.ts:

import {BrowserModule} from "@angular/platform-browser";
import {NgModule} from "@angular/core";
import {StoreModule, ActionReducer, combineReducers} from "@ngrx/store";
import {StoreDevtoolsModule} from "@ngrx/store-devtools";
import {CoreModule} from "./core/core.module";
import {AppSharedModule} from "./shared/shared.module";
import {CommonCoreModule} from "./saveup-common/common-core/common-core.module";
import {AppComponent} from "./app.component";
import {AppRoutingModule} from "./app-routing.module";
import {ClientModule} from "./client/client.module";

@NgModule({
    declarations: [
        AppComponent,
    ],
    imports: [
        BrowserModule,
        CommonCoreModule,
        CoreModule,
        AppRoutingModule,
        AppSharedModule,
        StoreModule.provideStore(AppModule.reducers())
    ],
    exports: [AppSharedModule],
    providers: [],
    bootstrap: [AppComponent]
})

export class AppModule {

    static reducers(): ActionReducer<any> {

        return combineReducers(Object.assign({},
            CoreModule.reducers(),
            ClientModule.reducers()
        ));
    }
}

My auth-reducer:

import { Action, ActionReducer } from '@ngrx/store';
import { AuthActionType } from './auth-action-type';
import { AuthModel } from '../shared/auth.model';
import { Deserializer } from '../../../common-shared/utils/deserializer';

export const authReducer: ActionReducer<AuthModel> = (state: AuthModel = undefined, action: Action) => {

  switch (action.type) {

    case AuthActionType.SET_AUTH:
      return action.payload ? Object.assign({}, Deserializer.deserializeFlat(action.payload, AuthModel)) : undefined;
    case AuthActionType.RESET_AUTH:
      return undefined;
    default:
      return state;
  }
};

Can anybody help?

localStorageSyncAndClean errors

When I use localStorageSync it works fine:

localStorageSync(['auth'], true)

But if I use localStorageSyncAndClean I get errors:

localStorageSyncAndClean(['auth'], true, true)

The error I get is:

Unhandled Promise rejection: Cannot read property 'currentSlot' of undefined ; Zone: <root> ; Task: Promise.then ; Value: TypeError: Cannot read property 'currentSlot' of undefined
    at MapSubscriber.project (task.effects.ts:39)
    at MapSubscriber._next (map.js:77)
    at MapSubscriber.Subscriber.next (Subscriber.js:89)
    at State.BehaviorSubject._subscribe (BehaviorSubject.js:28)
    at State.Observable._trySubscribe (Observable.js:57)
    at State.Subject._trySubscribe (Subject.js:97)

Basically this is referring to when I do this:

    @Effect()
    goToSlotAfterChange$ = this.store
        .select(state => state.tasks.currentSlot)
        .map(slot => {
            return slot ?
                go(['/' + slot]) :
                go(['/'])
        });

So, it is saying tasks is null. It seems like it says this because the store was not initialized. So, the problem here is that when I use localStorageSyncAndClean, the store doesn't appear to be initialized. I am not sure why that is, but I can look further into this later.

Angular Universal Support

As i tried out this (really nice and helpful) library i got surprised with the fact, that Angular Universal threw an Error.
lcoalstorage is not defined

It seems that this library doesnt support Angular Universal. I think many would appreciate support for it.

I solved it in the meanwhile with:

const customStorage: Storage = {
  length: 0,
  clear: function (): void {
    if (window && window.localStorage) {
      window.localStorage.clear();
      this.length = window.localStorage.length;
    }
  },
  getItem: function (key: string): string | null {
    try{
      return window.localStorage.getItem(key);
    }catch{
      return null;
    }
  },
  key: function (index: number): string | null {
    try {
      return window.localStorage.key(index);
    }catch{
      return null;
    }
  },
  removeItem: function (key: string): void {
    try {
      window.localStorage.removeItem(key);
      this.length = window.localStorage.length;
    }catch{
        return ;
      }
  },
  setItem: function (key: string, data: string): void {
    try{
      window.localStorage.setItem(key, data);
      this.length = window.localStorage.length;
    }catch{
        return ;
      }
  }
};

export function localStorageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
  return localStorageSync({keys: [{auth: ["token"]},{shopping_cart: ["products"]}], rehydrate: true, storage: customStorage})(reducer);
}

Incorrect sync on load

For example, i add localStorageSync:

localStorageSync([{teamParams: ['category']}], true)'

And when i change key category it's create object in localstorage {category: 'foo'},
but when i reload page it replace my reducer init object on {category: 'foo'}, instead just adding category field in it.

Storage conflicts

I've faced an issue with Storage conflicts when I had more than one application under same domain name, because sub apps had several same keys, which result to Storage keys overwrite upon localStorageSync.

Not sure if this should be handled by library, but I've created PR #37 with possible solution, custom serialize functions to make keys unique by using prefix or any other modifier.

What do you think about it?

ngrx 4.0 and StoreModule.forFeature

Need a way to correctly serialize separately loaded slices of state (I mean from StoreModule.forFeature(...) in lazy loaded module). Problem is that currently we must to set keys which will be serialized at the metaReducer creation, but we can't know lazy loaded keys. Liar, we can make a hack and hardcode all keys at the start. But what if we have dynamic modules? (my case)

Other way is to apply metaReducer for each forFeature state, but then we cannot access root state, but rather see only 2nd level objects.

Example (pseudo):

class RootState {
  core: ...
  auth: ...
  // here will be lazy loaded state, not known at this stage
  // lazy1: ...
}

StoreModule.forRoot (RootState, meta: sync(keys: ['core', 'auth']) ) // root state object available at metaReducer, everything is OK

class LazyState {
  page1: ...
  page2: ...
}

StoreModule.forFeature ('lazy', LazyState, meta: sync(keys ['lazy']) ) //we want to set only top level keys to avoid storage key conflicts, and reduce count of serialized objects

But what happens - rootState.lazy object available at syncStateUpdate \ rehydrate instead of rootState. So, metaReducer tried to iterate over keys (['lazy'] in that case), but available state object has only page1 and page2 props. Other words, metaReducers for forFeature has access only for current 'child' state.

Possible workaround is to make "keys" options of localstorage-sync available "globally", to be able to extend it at runtime. Currently validateStateKeys break that workaround because of map, which create new array, and we cannot access it by initial reference. I can prepare PR which change map to forEach (not really matters for that func, but we can keep the array ref). But it sounds VERY ugly. Do you have some thoughts, guys?

UPD:
Currently my workaround is to store child states for lazy loaded modules.
And use my previously submitted customKeySerializer to avoid naming conflicts between child states :)
I set function like

const prefix = 'somePrefix';
const func = (key) =>'${prefix}_${key}'

and set prefix as root for root slices, and featureName for lazy loaded states, together with passing child states as keys.
For previous example (pseudo, again):

StoreModule.forRoot (RootState, meta: sync(keys: ['core', 'auth'], prefix:'root'))
StoreModule.forFeature ('lazy1', LazyState, meta: sync(keys: ['page1', 'page2'], prefix: 'lazy1') )
StoreModule.forFeature ('lazy2', LazyState, meta: sync(keys: ['page1', 'page2'], prefix: 'lazy2') )

In the end I have next localStorage keys:

root_core
root_auth
lazy1_page1  // should be just inside 'lazy1' prop
lazy1_page2  // should be just inside 'lazy1' prop
lazy2_page1  // should be just inside 'lazy2' prop
lazy2_page2  // should be just inside 'lazy2' prop

It works, but problem is if lazy loaded keys has a lot of child keys, we need to run serialize func and access Storage for many times, and it's a much slower than write single object at once.

Setting state value to undefined

If the state is set to undefined by reducer (case: logout), the value is not removed/update in localStorage. I am doing it wrong? The only option right now is to remove manually from the local storage in effect?

Override the init state

I think there is should be an option to override the init state with the storage.

for example:
I have auth state and I did login and got a token.
When I refresh the browser this token is disappeared.
So I should save it to the localStorage.

But my init state have this: token: null;.

So I think there is should be in LocalStorageConfig option to override the init state..

@btroncone are you agree?

Rehydration loses Object class

How to reproduce :

  • Add an object (for instance obj) of a certain Typescript class (for instance class Foo) to the saved state
  • Set ngrx-store-localstorage to save the corresponding state/property and activate rehydration
  • Refresh the page

How it should work :

  • Rehydration should keep the class of the instance :

obj instanceof Foo should return true
obj.constructor.name should return Foo

How it works right now :

  • obj instanceof Foo returns false
  • obj.constructor.name returns Object

[AOT] ngc: Error: Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol AppModule

Hi! I recently upgraded to the 2.0.0 release version of Angular 2. I'm getting this error from ngc:

ngc: Error: Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol AppModule

I believe it has to do with this code in the NgModule definition:

StoreModule.provideStore(
        compose(
            localStorageSync(['todos']),
            combineReducers
        )({todos, visibilityFilter})

if I change it to this...

StoreModule.provideStore({todos, visibilityFilter})

it works fine. If I attempt to do what the error says, and replace the function with a reference to an exported function it still does not work:

import { Store, StoreModule } from '@ngrx/store';
import { todos, visibilityFilter } from './reducers';
import { NgModule } from '@angular/core'

export const reducers = compose(
            localStorageSync(['todos']),
            combineReducers
        )({todos, visibilityFilter});

@NgModule({
  imports: [
    BrowserModule,
    StoreModule.provideStore(reducers)
  ]
})
export class MyAppModule {}

Do you have any ideas?

Can this pattern be used for large data?

In my opinion, synching the store and the local storage will work for small ToDo app where the state is very minimum. But for a real application like a catalog management, you have thousands of products. You can't bring-in all those products to the store. The local storage can have all the products, but the store can have only the products which have been searched by the user.

What is your opinion on this?

rehydrate: true option gives error

Operating System: Manjaro Linux 17.0.5
Kernel: 4.9.51-1-MANJARO
Node: 8.5.0
NPM: 5.4.2
Browser: Chromium 61.0.3163.91
Angular: 4.4.3
ngrx/store: 4.0.3
ngrx-store-localstorage: 0.2.2
zone.js: 0.8.17

My app works fine, but when I add the rehydrate: true option, I get the error below when I try to refresh the page after I've logged in. I can provide more code or information if required.

Sample reducer

import * as OrganizationActions from '../actions/organization';

const initialState = '';

export function reducer(state: string = initialState, action: OrganizationActions.Actions): string {
  switch (action.type) {
    case OrganizationActions.ActionTypes.SET_ORGANIZATION:
      return action.payload;
    default:
      return state;
  }
}

Sample action

import { Action } from '@ngrx/store';

export const ActionTypes = {
  SET_ORGANIZATION: 'SET_ORGANIZATION'
};

export class SetOrganizationAction implements Action {
  readonly type = ActionTypes.SET_ORGANIZATION;
  constructor(public payload: string) { }
}

export type Actions = SetOrganizationAction;

Main reducer file

import * as Assessment from '../reducers/assessment';
import * as Organization from '../reducers/organization';
import * as Person from '../reducers/person';
import * as Team from '../reducers/team';
import * as Token from '../reducers/token';

import { ActionReducer, ActionReducerMap, MetaReducer } from '@ngrx/store';

import { State } from './../../models/state';
import { localStorageSync } from 'ngrx-store-localstorage';

export const appReducers: ActionReducerMap<State> = {
  token: Token.reducer,
  organization: Organization.reducer,
  team: Team.reducer,
  person: Person.reducer,
  assessment: Assessment.reducer
};

const localStorageSyncReducer = localStorageSync({
  keys: ['token', 'organization', 'team', 'person', 'assessment'],
  rehydrate: true
});

export function localStorageSyncMetaReducer(reducer: ActionReducer<any>): ActionReducer<any> {
  return localStorageSyncReducer(reducer);
}

export const metaReducers: MetaReducer<any>[] = [localStorageSyncMetaReducer];

Error

Unhandled Promise rejection: Unexpected token c in JSON at position 0 ; Zone: <root> ; Task: Promise.then ; Value: SyntaxError: Unexpected token c in JSON at position 0
    at Object.parse (<anonymous>)
    at vendor.bundle.js:10876
    at Array.reduce (<anonymous>)
    at Object.webpackJsonp.../../../../ngrx-store-localstorage/dist/index.js.exports.rehydrateApplicationState (vendor.bundle.js:10835)
    at vendor.bundle.js:10961
    at localStorageSyncMetaReducer (main.bundle.js:3238)
    at vendor.bundle.js:7072
    at Array.reduceRight (<anonymous>)
    at vendor.bundle.js:7072
    at new ReducerManager (vendor.bundle.js:7170) SyntaxError: Unexpected token c in JSON at position 0
    at Object.parse (<anonymous>)
    at http://localhost:4200/vendor.bundle.js:10876:28
    at Array.reduce (<anonymous>)
    at Object.webpackJsonp.../../../../ngrx-store-localstorage/dist/index.js.exports.rehydrateApplicationState (http://localhost:4200/vendor.bundle.js:10835:17)
    at http://localhost:4200/vendor.bundle.js:10961:54
    at localStorageSyncMetaReducer (http://localhost:4200/main.bundle.js:3238:12)
    at http://localhost:4200/vendor.bundle.js:7072:66
    at Array.reduceRight (<anonymous>)
    at http://localhost:4200/vendor.bundle.js:7072:21
    at new ReducerManager (http://localhost:4200/vendor.bundle.js:7170:39)

Save to storage only when slice of state changed

Hi,

Here is my fix for check for only when the slice of the state was changed then save it to storage.
because each time the store had change it's save to the storage even the slice has no change at all.

const INIT_ACTION = "@ngrx/store/init";
const detectDate = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/;

//correctly parse dates from local storage
const parseWithDates = (jsonData: string) => {
    return JSON.parse(jsonData, (key: any, value: any) => {
        if (typeof value === 'string' && (detectDate.test(value))) {
            return new Date(value);
        }
        return value;
    });
};

const validateStateKeys = (keys: any[]) => {
    return keys.map(key => {
        let attr = key;

        if (typeof key == 'object') {
            attr = Object.keys(key)[0];
        }

        if (typeof (attr) !== 'string') {
            throw new TypeError(
                `localStorageSync Unknown Parameter Type: `
                + `Expected type of string, got ${typeof attr}`
            );
        }
        return key;
    });
};

const rehydrateApplicationState = (keys: string[]) => {
    return keys.reduce((acc, curr) => {
        if (typeof curr == 'object') {
            curr = Object.keys(curr)[0];
        }
        let stateSlice = localStorage.getItem(curr);
        if (stateSlice) {
            return Object.assign({}, acc, { [curr]: parseWithDates(stateSlice) })
        }
        return acc;
    }, {});
};

const syncStateUpdate = (state, newState: any, keys: string[]) => {
    keys.forEach(key => {

        let stateNewSlice = newState[key];
        let stateSlice = state[key];

        if (JSON.stringify(stateSlice) === JSON.stringify(stateNewSlice)) return;

        if (typeof key == 'object') {
            let name = Object.keys(key)[0];
            stateNewSlice = state[name];

            if (key[name]) {
                stateNewSlice = key[name].reduce((memo, attr) => {
                    memo[attr] = stateNewSlice[attr];
                    return memo;
                }, {});
            }

            key = name;
        }

        if (typeof (stateNewSlice) !== 'undefined') {
            try {
                localStorage.setItem(key, JSON.stringify(stateNewSlice));
            } catch (e) {
                console.warn('Unable to save state to localStorage:', e);
            }
        }
    });
};

export const localStorageSync = (keys: any[], rehydrate: boolean = false) => (reducer: any) => {
    const stateKeys = validateStateKeys(keys);
    const rehydratedState = rehydrate ? rehydrateApplicationState(stateKeys) : 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 && rehydratedState) {
            state = Object.assign({}, state, rehydratedState);
        }
        const nextState = reducer(state, action);
        syncStateUpdate(state, nextState, stateKeys);
        return nextState;
    };
};

localForage

I believe the next iteration step for this library should be to implement localForage as a means for us to be able to choose different storage options.

Angular 4 support

Hello,

With new version of angular, there is a warning for unmet dependency:
warning "[email protected]" has incorrect peer dependency "@angular/core@^2.0.0-rc.5".

It's not an urgent issue, but still would be nice to get rid of that warning.

Thanks

Windows 10, Node 7.-x Npm 4.x - npm run build or npm start fails with same error

[email protected] build:dev c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes
webpack --config config/webpack.dev.js --progress --profile --colors --display-error-details --display-cached
4129ms o 90% optimize assetsError: Debug Failure. False expression: File has unknown extension.
at Object.assert (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:3323:23)
at Object.fail (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:3328:19)
at Object.extensionFromPath (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:3470:15)
at c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:66803:46
at Array.map (native)
at resolveModuleNamesWorker (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:66797:141)
at resolveModuleNamesReusingOldState (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:66924:24)
at processImportedModules (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:67834:35)
at findSourceFile (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:67744:17)
at processImportedModules (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:67863:25)
at findSourceFile (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:67744:17)
at processImportedModules (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:67863:25)
at findSourceFile (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:67744:17)
at processSourceFile (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:67647:27)
at processRootFile (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:67535:13)
at c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:66825:60
at Object.forEach (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:1443:30)
at Object.createProgram (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:66825:16)
at synchronizeHostData (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:85978:33)
at Object.getProgram (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\typescript\lib\typescript.js:86079:13)
at State.updateProgram (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\awesome-typescript-loader\dist.babel\host.js:241:42)
at c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\awesome-typescript-loader\dist.babel\instance.js:333:27
at Compiler. (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\awesome-typescript-loader\dist.babel\instance.js:354:15)
at Compiler.next (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\tapable\lib\Tapable.js:69:14)
at Compiler. (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\webpack\lib\CachePlugin.js:40:4)
at Compiler.applyPluginsAsync (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\tapable\lib\Tapable.js:71:13)
at Compiler. (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\webpack\lib\Compiler.js:400:9)
at Compilation. (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\webpack\lib\Compilation.js:577:13)
at Compilation.next (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\tapable\lib\Tapable.js:67:11)
at Compilation. (c:\Users\Folio\web\frontend\javascript\frameworks\angular-rxjs\rxheroes\node_modules\webpack\lib\ProgressPlugin.js:69:5)

Rehydrate dates

Great middleware, thanks for providing it!

I've noticed that Dates are saved as strings in localStroage, since they must be. But they are not rehydrated to actual dates when pulled out of localStorage. Would be nice if the middleware detected date strings and Date.parse'd them.

Use sessionStorage option

Would there be any major hurdles in having a config option to use sessionStorage rather than localStorage?

If that is what this option does, then feel free to close.

storage (optional) Storage: Specify an object that conforms to the Storage interface to use, this will default to localStorage.

Thanks!

Not saving to local storage

I am not sure what I am doing wrong localStorage is not being synced.

Here is my package.json:
{
"name": "digital-sales-desk",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"angular/animations": "^4.1.3",
"angular/common": "^4.0.0",
"angular/compiler": "^4.0.0",
"angular/core": "^4.0.0",
"angular/flex-layout": "^2.0.0-beta.8",
"angular/forms": "^4.0.0",
"angular/http": "^4.0.0",
"angular/material": "^2.0.0-beta.6",
"angular/platform-browser": "^4.0.0",
"angular/platform-browser-dynamic": "^4.0.0",
"angular/router": "^4.0.0",
"ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.26",
"ngrx/core": "^1.2.0",
"ngrx/store": "^2.2.2",
"core-js": "^2.4.1",
"jsog": "^1.0.7",
"ngrx-store-localstorage": "^0.1.8",
"rxjs": "^5.1.0",
"zone.js": "^0.8.4"
},
"devDependencies": {
"angular/cli": "1.0.6",
"angular/compiler-cli": "^4.0.0",
"types/jasmine": "2.5.38",
"types/node": "~6.0.60",
"codelyzer": "~2.0.0",
"jasmine-core": "~2.5.2",
"jasmine-spec-reporter": "~3.2.0",
"karma": "~1.4.1",
"karma-chrome-launcher": "~2.1.1",
"karma-cli": "~1.0.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"karma-coverage-istanbul-reporter": "^0.2.0",
"protractor": "~5.1.0",
"ts-node": "~2.0.0",
"tslint": "~4.5.0",
"typescript": "~2.2.0"
}
}

Removed at symbold from above because it was being stripped from this editor

Here is my app.module.ts:
`//Modules
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { FlexLayoutModule } from '@angular/flex-layout';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import * as material from '@angular/material';

//Other
import { Router } from './app.routing.module';
import { AppComponent } from './app.component';

//Redux
import { StoreModule, combineReducers } from '@ngrx/store';
import { compose } from '@ngrx/core';
import { localStorageSync } from 'ngrx-store-localstorage';

//Reducers
import {
InvSessionReducer,
InvLoaderReducer
} from './shared/reducers';

//Views
import { HomeComponent } from './views/home/home.component';
import { LoginComponent } from './views/login/login.component';

//Components
import { VehicleListComponent } from './components/vehicle-list/vehicle-list.component';
import { VehicleListEntryComponent } from './components/vehicle-list/vehicle-list-entry/vehicle-list-entry.component';
import { VehicleGridComponent } from './components/vehicle-grid/vehicle-grid.component';
import { VehicleCardComponent } from './components/vehicle-card/vehicle-card.component';
import { FilterBarComponent } from './components/filter-bar/filter-bar.component';
import { VehicleInformationComponent } from './components/vehicle-information/vehicle-information.component';
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
import { InventorySearchComponent } from './components/inventory-search/inventory-search.component';
import { LoaderComponent } from './components/loader/loader.component';

//Filters
import { VehicleStatusPipe } from './filters/vehicle-status.pipe';
import { VehicleIdPipe } from './filters/vehicle-id.pipe';
import { TranslatePipe } from './filters/translate.pipe';

//Services
import {
AppService,
HttpService,
LoggerService,
FilterService,
InventoryService
} from './shared/services';

//API
import { InventoryControllerApi } from 'api/InventoryControllerApi';
import { InventoryFilterControllerApi } from 'api/InventoryFilterControllerApi';

//Guards
import { AuthGuard } from './guards';

export function rootReducer(){
return compose(
localStorageSync({keys: ['invSessionState']}),
combineReducers
)({InvSessionReducer, InvLoaderReducer});
}

@NgModule({
declarations: [
AppComponent,
//views
LoginComponent,
HomeComponent,
//components
VehicleListEntryComponent,
VehicleListComponent,
VehicleGridComponent,
VehicleCardComponent,
FilterBarComponent,
VehicleInformationComponent,
PageNotFoundComponent,
InventorySearchComponent,
LoaderComponent,
//filters
VehicleIdPipe,
VehicleStatusPipe,
TranslatePipe,
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
BrowserAnimationsModule,
material.MdCardModule,
material.MdIconModule,
material.MdButtonModule,
material.MdGridListModule,
material.MdListModule,
material.MdCheckboxModule,
material.MdChipsModule,
material.MdProgressBarModule,
material.MdToolbarModule,
material.MdTabsModule,
material.MdRadioModule,
material.MdMenuModule,
material.MdToolbarModule,
material.MdInputModule,
FlexLayoutModule,
Router,
BrowserAnimationsModule,
NgbModule.forRoot(),
StoreModule.provideStore(rootReducer())
],
providers: [
AppService,
HttpService,
AuthGuard,
LoggerService,
FilterService,
InventoryService,
InventoryControllerApi,
InventoryFilterControllerApi
],
bootstrap: [AppComponent]
})

export class AppModule { }
`

Any idea what I could be doing wrong?

rehydrate only works when devtools intrument enabled?

I try to save the authentication information inside localStorage. Now, in development everything worked fine, but in production the information is not rehydrated.

In my root module imports I have the following line:

!environment.production ? StoreDevtoolsModule.instrument() : [],

When I change it to instrument in development as well as in production, suddenly also rehydration of the state works.

StoreDevtoolsModule.instrument(),

Do you have an idea what could cause the problem?

For completeness, here is how I activate ngrxLocalStorage (based on the ngrx example project in app/reducers/index.ts):

export function localStorageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
  return localStorageSync({ keys: ['auth'], rehydrate: true })(reducer);
}

export const metaReducers: ActionReducer<any, any>[] = !environment.production
  ? [localStorageSyncReducer, logger]
  : [localStorageSyncReducer];

Upgrade to ngrx 4.0.0

ngrx/store 4.0.0 was released today but ngrx-store-localstorage still has dependency to ^2.2.1 version

Where is the example?

I can't seem to find any examples on how to use this module. Is it missing?
Or is it really just these couple of lines?

export function localStorageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
  return localStorageSync({keys: ['todos']})(reducer);
}
const metaReducers: Array<ActionReducer<any, any>> = [localStorageSyncReducer];

Add (or detail) compatibility with Ionic Storage

I'm very much a newb here so maybe I'm missing something obvious, but I can't seem to use Ionic storage (https://ionicframework.com/docs/v2/storage/) with ngrx-store-localstorage, due to the fact that Ionic storage returns promises.
I honestly have no idea how much work that would entail, and unfortunately I can't offer a PR either - on the other hand, if there's any simple way this can be done without touching your code, I believe it would be a very useful addition to the readme.

Handling quota errors

I saw that the lib is properly doing try...catch on setItem, but currently there is no way to handle these errors. Browsers handle Web Storage quotas differently, with Safari having a particularly strict quota of 5MB. It would be nice to be able to pass an error handler so clients can determine what to drop in the case of QuotaError.

This problem is trickier than it first may seem. First, what data would you give the client? The state or state slice? Or accept a provider allowing dispatching? However, the bigger issue is that to make this effective, you'd need to be able to have conditional instead of static keys.

For example, let's say I'm storing base64 profile images as part of my state, and run up against the quota. I still want my app to default to storing these images, but they are not as useful as other slices, so if I hit a quota, I'd like to drop them. This means means that the profile images key would need to be conditional.

The simplest solution would probably be to just add something like an optional priority property to the key object. You could then change validateStateKeys to something like validatAndSortStateKeys which could sort by priority (where keys without a priority default to Infinity). This would allow you to keep the algorithm roughly the same, while prioritizing what gets stored and what can be safely discarded if an error is thrown.

Reviving nested states from localstorage results in loss of part of nested initial state values

Suppose I have this initial state:

{ 
   someKey: {}
   itemResults: {
          items: [], 
          totalItems: 0,
          isListView: true
    },
   someOtherKey: {}
}

and I have this config:

localStorageSync({
    keys: [{itemResults: ['isListView']}],
    rehydrate: true
})

and I have this state being revived from localStorage:

{ 
   itemResults: {
       isListView: false
   }
}

Which means during @ngrx/store/init, since we only do a shallow merge of keys using Object.assign, I end up with the following state:

{ 
   itemResults: {
       isListView: false
   }
}

So basically if you use a nested key or keys, then that slice of state overrides the whole slice of initialState for the top level key. I was thinking it'd be nice if I could just use lodash's merge to merge (or basically Object.assign nested keys also), but I don't want to bloat this library either. It would be nice if I could supply a function to the library to use to do the merge, then I could just use lodash.merge there instead of bundling it with the library.

Could we possibly add a config option to allow the user to provide a custom merge function? Basically something like this?

export const localStorageSync = (config: LocalStorageConfig) => (reducer: any) => {
    // ... code omitted

    const mergeFunction = config.customMergeFunction || ((state, rehydratedState) =>  Object.assign({}, state, rehydratedState));

    return function (state = rehydratedState, action: any) {
        
        if (action.type === INIT_ACTION && rehydratedState) {
            state = mergeFunction(state, rehydratedState);
        }
     
        // ... code omitted
    };
};

Any thoughts/objections, anyone?


EDIT:
Renaming the thread since it appears that there's a general consensus that a deep merge should be the default behavior here.

Add a flag to control date rehydration

We store dates in ISO date format (string). When using this library, the reloading of the state breaks my data, as the ISO dates get parsed to date objects.

It would be very helpful, if there is a flag to disable date rehydration in my case.

-- thanks for this handy library, it's helping a lot at my development! ๐Ÿ‘

Rehydrate with default state when no localStorage key?

Thank you for this really nice utility.
Is there any possibility that I can't figure out to provide default state for rehydration?

My problem:
I've got a state with "user" section, being and object with default values, i.e.:

{
  user: { token: null, loggedIn: whatever, ...other properties...},
  ...(other root keys)...
}

I'm syncing the whole user node. However, when the new user visits page and there's no localStorage "user" key, the user state after rehydration is null, instead of the object with user properties { token: null, loggedIn: 'whatever', ...other properties...}. I'd like to have the object, not null.

Do you think this default, initial rehydration state when no localStorage key exists would be useful or I'm totally missing some other possibility (not hackish) to achieve what I want?

error reading store data

I created the ngrx store something on the lines of this article and it is working fine.

I'm using the localstorage option now StoreModule.provideStore( compose(localStorageSync(['reducer']), combineReducers)({ reducer })

my 'reducer' ( meta reducer )code looks like this

`
const reducers = {
profileData: fromProfile.reducer,
collectionData: fromCollection.reducer
};

const combinedReducer: ActionReducer = combineReducers(reducers);

export function reducer(state: any, action: any) {
return combinedReducer(state, action);
}
`

when I dispatch post login actions to get the profile and update the store, it happens fine, and I also see the data in console.log and also store in local storage. By the time I navigate to the profile page and call Load profile action, I see errors. when I debug It looks the the state object is empty.

my profile reducer looks like this

`export interface State {
profile: Profile;
};

const initialState: State = { profile: {} };

export function reducer(state = initialState, action: profile.Actions): State {
console.log('in profile.reducer reducer() action is ', action);
switch (action.type) {
case profile.ActionTypes.FETCH_PROFILE_SUCCESS: {
console.log('FETCH_PROFILE_SUCCESS ', action.payload);
console.log('current state is ', state);

        if (state) {

            const newProfile: Profile = action.payload;
            newProfile.access_token = state.profile.access_token;

            let newState: State = {
                profile: newProfile
            };
            console.log('new state is ', newState);
            return newState;
        }
        return state;
    }
    case profile.ActionTypes.LOAD_PROFILE_DATA: {
        console.log('LOAD_PROFILE_DATA ', action.payload);
        console.log('current state is ', state);
        if (state) {
            let newState: State = {
                profile: state.profile
            };
            console.log('new state is ', newState);
            return newState;
        }
        return state;
    }`

I tried both options

StoreModule.provideStore( compose(localStorageSync(['reducer']), combineReducers)({ reducer })

and

StoreModule.provideStore( compose(localStorageSync([{ reducer: ['profileData'] }]), combineReducers)({ reducer }) )

I am totally new to javascript and angular2. Any help would be appreciated.

rehydrate State is not working with FeatureModule state

I have a FeatureModule for Authentication that is using StoreModule.forFeature('auth' and therefore creates an auth object on the store.
This auth object i want to sync with the localStorage.
The rehydrate function only listens on the @ngrx/store/init event.
However at this time the auth if not yet set on the store
You have to also listen for the @ngrx/store/update-reducers event.
This is called afterwards (for each FeatureModule)
and overwrites the auth with it's initialState value.

So the whole ngrx-store-localstorage module rehydrate is not working for FeatureModules (StoreModule.forFeature)
But actually thats the whole point of this so it is not useable...

Update:
I just diged in it and it seems, that rehydrate actually gets called for every added FeatureModule on the @ngrx/store/update-reducers event.
However in the meanwhile, the store got synced again to the localstorage and has overwritten the auth.
So the thing is, that when the event is @ngrx/store/update-reducers then please don't sync it to the localStorage as it overwrites the state that should get rehydrated

Update 2:
I made a fixed version of localStorageSync, it may not be very sophisticated and it actually has to cover more configuration, but for me it does the trick for now:
`export const localStorageSyncFixed = (config: LocalStorageConfig) => (reducer: any) => {

if (config.storage === undefined) {
config.storage = localStorage || window.localStorage;
}

if (config.storageKeySerializer === undefined) {
config.storageKeySerializer = (key) => key;
}

const stateKeys = validateStateKeys(config.keys);
const rehydratedState = config.rehydrate ? rehydrateApplicationState(stateKeys, config.storage, config.storageKeySerializer) : 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 && rehydratedState)
state = Object.assign({}, state, rehydratedState);

// Also on Update Reducers rehydrate the state
if (action.type === UPDATE && rehydratedState)
  state = Object.assign({}, state, rehydratedState);

const nextState = reducer(state, action);

if (action.type !== UPDATE && action.type !== INIT)
  syncStateUpdate(nextState, stateKeys, config.storage, config.storageKeySerializer, config.removeOnUndefined);

return nextState;

};
};`

Change to use Date.parse instead of date regex breaks rehydration

The change to use Date.parse instead of the date checking regex seems to match too many possible values.

For example, a number stored as a string, such as a version number, will pass the Date.parse check and be interpreted as a date instead of a string value

e.g.

{
    "version":"3.5.0"
    ...
}

Results in a Date instance of Sun Mar 05 2000 00:00:00 GMT-0500 (EST) being set for version in the store/state.

Using:

Reverting to version 0.2.0 fixes the issue

Docs and status for use in ngrx 4.0

I'm trying (and failing) to understand #43 and #41. The readme mentions StoreModule.provideStore which no longer exist. How should this project be used in 4.0? Is it currently not actually compatible and some "very simplistic version as a temporary solution" workaround exists?

Format of date strings change after rehydration

We store dates like "2017-05-03T16:17:05" as strings in our store.
We use your localStorageSync with rehydrate=true.
After reloading our angular application the rehydrated initial state shows that the date string has been changed to "2017-05-03T14:17:05.000Z"

If I read the following issue I know why this is the case
#3

But do you know how I could prevent that my date strings are getting converted?

We use "ngrx-store-localstorage": "0.1.5"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.