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:
- Install package
- Wrap
localStorageSync
in an exported function - 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“.