About NgRx

NgRx was created to provide an opinionated approach to build reactive applications in Angular. The NgRx Store (which was inspired by Redux) provides reactive state management by unifying application events to derive state using RxJS. NgRx has become the top choice for state management for enterprise Angular applications today.

The Use Case for LocalStorage

NgRx is an amazing tool for building applications in Angular. However, from time to time, we find ourselves with a requirement that isn’t handled easily with NgRx. Primarily, in cases where we want state to be persisted beyond a browser session but not stored in a database. This is where syncing select slices (or partial slices) from your store into a user’s localStorage can come to the rescue.

What is localStorage?

The read-only localStorage property allows you to access a Storage object for the Document’s origin; the stored data is saved across browser sessions. localStorage is similar to sessionStorage. Except that data stored in localStorage has no expiration time but data stored in sessionStorage gets cleared when the page session ends; that is, when the page is closed: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage.

Implementing StorageSync

For the sake of brevity, let’s assume we have a very trivial NgRx store that implements 2 features: featureOne and featureTwo. Now let’s add ngrx-store-localstorage to our app. We just need to implement the following steps:

  1. Install package
  2. Wrap localStorageSync in an exported function
  3. Include in the meta-reducers array of StoreModule.forRoot

1. Install the StorageSync package

npm install ngrx-store-localstorage

2. Wrap localStorageSync in an exported function

function localStorageSyncReducer(reducer: ActionReducer,[object Object]

3. Include in meta-reducers

import { State } from './my-store';
import reducer as featureOneReducer from './my-store/featureOne';
import reducer as featureTwoReducer from './my-store/featureTwo';

const reducers: ActionReducerMap,[object Object]

Keys

Let’s take a moment to talk about the keys property of the LocalStorageConfig interface that gets added to localStorageSync. The keys property serves as State keys to be synced with local storage. The keys can be defined in two different formats:

  • string[]: Array of strings representing the state (reducer) keys. Example: localStorageSync({keys: ['featureOne', 'featureTwo']}).
  • object[]: Array of objects where for each object the key represents the state key and the value represents custom serialize/deserialize options. Example: localStorageSync({keys: [{ featureOne: ['sliceOne', 'sliceTwo']}, { featureTwo: ['sliceOne']}]})

For more key configuration options, see the docs

Access localStorage

To Access your localStorage, simply go to the developer tools of your Browser (F12). Then click on the Application tab where you should see a list of keys and corresponding values. You can now verify that the key properties you put inside localStorageSync are stored successfully inside localStorage.

Rehydrating

One of my favorite benefits to using storageSync is the ability to automatically rehydrate your NgRx store with state that isn’t persisted in a database somewhere. We’ve all worked on projects where there was a lot of governance around data storage and storing client preferences wasn’t an option (or worth the hassle). This is where storageSync comes to the rescue. Adding rehydration is as simple as setting the rehydrate property to true on the LocalStorageConfig.

function localStorageSyncReducer(reducer: ActionReducer,[object Object]

By setting rehydrate to true, it also can speed up the development process by easily hydrating the store on reloads (which happen often during development). Hint If you find that you are repeating yourself as a dev on reloads, it might mean adding that state to localStorage (temporarily or permanently) could be a great solution.

mergeReducer

If you are lazy loading your NgRx store slices using forFeature you may encounter an error that looks something like this:

In order to resolve this error you just need to implement a mergeReducer that describes when and how you want to rehydrate your state. Let’s use lodash to create a basic implementation:

npm install lodash.merge
import merge from "lodash.merge";
const INIT_ACTION = "@ngrx/store/init";
const UPDATE_ACTION = "@ngrx/store/update-reducers";

const mergeReducer = (state: State, rehydratedState: State, action: Action): State => {
  if ((action.type === INIT_ACTION || action.type === UPDATE_ACTION) && rehydratedState) {
    state = merge(state, rehydratedState);
  }

  return state;
}

function localStorageSyncReducer(reducer: ActionReducer,[object Object]

Here we tell our metaReducer to merge the localStorage state (defined via keys in our config) with our store’s initialState when the INIT_ACTION or UPDATE_ACTION are dispatched and when a rehydratedState is available.

Summary

In this guide, you learned how simple and easy it is to add slices of your NgRx store to localStorage by adding a simple implementation of localStorageSync to your store’s metaReducers. To view a working example of this in action you can check out this Stackblitz example.

Learn more about LocalStorage in our article, “Managing Local Storage in Angular“.