Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@ngrx-addons/persist-state

Michsior145.5kMIT8.0.3TypeScript support: included

Persist state in storage for ngrx, based on @ngneat/elf-persist-state

ngrx, persist state, persist store, storage, local storage, session storage, indexed db

readme

@ngrx-addons/persist-state npm bundle size npm NPM npm

The library for persisting state in ngrx. Highly inspired by @ngneat/elf-persist-state. Supports local storage, session storage and async storages with localForage.

Supported versions

  • angular 19+
  • @ngrx/store 19+

Installation

npm i @ngrx-addons/persist-state

or

yarn add @ngrx-addons/persist-state

Usage

The module gives ability to persist some of the app’s states, by saving it to localStorage/sessionStorage or anything that implements the StorageEngine API, and restore it after a refresh. It supports both root and feature states. The only thing you need to do is to add PersistStateModule.forRoot/providePersistStore to your AppModule and PersistStateModule.forFeature/providePersistState to your feature module.

For root states

import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { BeforeAppInit } from '@ngrx-addons/common';
import { PersistStateModule, localStorageStrategy } from '@ngrx-addons/persist-store';

const counterReducer = ...;
const reducers = {
  counter: counterReducer,
} as const;

@NgModule({
  imports: [
    StoreModule.forRoot(reducers),
    // Define after EffectsModule.forRoot() if you want to listen on `rehydrate` action
    // type provided for hints on states
    PersistStateModule.forRoot<typeof reducers>({
      states: [
        {
          key: 'counter',
          // the package exposes localStorageStrategy and
          // sessionStorageStrategy, optionally you can
          // provide your own implementation or even
          // use localForage for indexed db.
          storage: localStorageStrategy
          // optional options (default values)
          runGuard: () => typeof window !== 'undefined',
          source: (state) => state,
          storageKey: `${storageKeyPrefix}-${key}@store`,
          migrations: [],
          skip: 1
        },
        // next states to persist, same reducer key can be
        // specified multiple times to save parts of the state
        // to different storages
      ],
      // optional root options (for all, also feature states)
      storageKeyPrefix: 'some-prefix',
      // optional rehydration strategy
      strategy: BeforeAppInit, // or AfterAppInit
    }),
  ],
})
export class AppModule {}

or in case of using standalone API:

import { NgModule } from '@angular/core';
import { provideStore } from '@ngrx/store';
import { BeforeAppInit } from '@ngrx-addons/common';
import { providePersistStore, localStorageStrategy } from '@ngrx-addons/persist-store';

const counterReducer = ...;
const reducers = {
  counter: counterReducer,
} as const;

@NgModule({
  providers: [
    provideStore(reducers),
    // Define after EffectsModule.forRoot() if you want to listen on `rehydrate` action
    // type provided for hints on states
    providePersistStore<typeof reducers>({
      states: [
        {
          key: 'counter',
          // the package exposes localStorageStrategy and
          // sessionStorageStrategy, optionally you can
          // provide your own implementation or even
          // use localForage for indexed db.
          storage: localStorageStrategy
          // optional options (default values)
          runGuard: () => typeof window !== 'undefined',
          source: (state) => state,
          storageKey: `${storageKeyPrefix}-${key}@store`,
          migrations: [],
          skip: 1
        },
        // next states to persist, same reducer key can be
        // specified multiple times to save parts of the state
        // to different storages
      ],
      // optional root options (for all, also feature states)
      storageKeyPrefix: 'some-prefix',
      // optional rehydration strategy
      strategy: BeforeAppInit, // or AfterAppInit
    }),
  ],
})
export class AppModule {}

The forRoot/providePersistStore method accepts an object with the following properties:

  • states - array of states configs (defined below, required)
  • storageKeyPrefix - prefix for all storage keys (optional)
  • strategy - defines if rehydrate actions should be fired before or after app initialization (optional, default: BeforeAppInit)

Each state can be described by multiple state configs with the following properties:

  • key - the reducer key in app state (required)
  • storage: an object or function resolving to an object with async setItem, getItem and removeItem methods for storing the state. The package exposes localStorageStrategy or sessionStorageStrategy, excepts also a localForage instance (required).
  • source: a method that receives the observable of a state and return what to save from it (by default - the entire state).
  • storageKey: the name under which the store state is saved (by default - the prefix plus store name plus a @store suffix).
  • runGuard - returns whether the actual implementation should be run. The default is () => typeof window !== 'undefined'
  • skip - the number of state changes skipped before the state is persisted. Used to skip the initial state change. The default is 1.
  • migrations - the array of migrations to run on the state before rehydrated event is fired. The default is [].
    • version - the version of the state to migrate from.
    • versionKey - the key in the state that contains the version. The default is version.
    • migrate - the function that receives the state and returns the migrated state.

For feature states

Remember to add features only once, in any case only the last registration will be used.

import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { PersistStateModule, localStorageStrategy } from '@ngrx-addons/persist-store';

interface CounterState {
  count: number;
}
const counterReducer = ...;

@NgModule({
  imports: [
    StoreModule.forRoot(),
    // forRoot should be always called, similar to ngrx StoreModule and it's forFeature implementation.
    PersistStateModule.forRoot(),
  ],
})
export class AppModule {}

@NgModule({
  imports: [
    StoreModule.forFeature('counter', reducer),
    // type provided for hints on states
    PersistStateModule.forFeature<CounterState>({
      key: 'counter',
      states: [
        {
          // The same options as for root states, except the key
          storage: localStorageStrategy
        },
      ],
    }),
  ]
})
export class CounterModule {}

or in case of using standalone API:

import { NgModule } from '@angular/core';
import { provideStore, provideState } from '@ngrx/store';
import { providePersistStore, providePersistState, localStorageStrategy } from '@ngrx-addons/persist-store';

interface CounterState {
  count: number;
}
const counterReducer = ...;

@NgModule({
  providers: [
    provideStore(),
    // forRoot should be always called, similar to ngrx StoreModule and it's forFeature implementation.
    providePersistStore(),
  ],
})
export class AppModule {}

@NgModule({
  providers: [
    provideState('counter', reducer),
    // type provided for hints on states
    providePersistState<CounterState>({
      key: 'counter',
      states: [
        {
          // The same options as for root states, except the key
          storage: localStorageStrategy
        },
      ],
    }),
  ]
})
export class CounterModule {}

The forFeature/providePersistState method accepts an object with the following properties:

  • key - the feature key (required)
  • states - array of states configs as in forRoot, except key property (required)

Once the state is rehydrated, the action (rehydrated, type: @ngrx-addons/persist-state/rehydrate) with the proper features is dispatched (multiple times). You can use it to react in effects or meta-reducers.

Excluding/Including keys from the state​

The excludeKeys()/includeKeys() operator can be used to exclude keys from the state:

import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { excludeKeys, includeKeys } from '@ngrx-addons/common';
import { PersistStateModule, localStorageStrategy } from '@ngrx-addons/persist-store';

const counterReducer = ...;
const reducers = {
  counter: counterReducer,
} as const;

@NgModule({
  imports: [
    StoreModule.forRoot(reducers),
    PersistStateModule.forRoot<typeof reducers>({
      states: [
        {
          key: 'counter',
          storage: localStorageStrategy,
          source: (state) => state.pipe(excludeKeys(['a', 'b'])),
          // source: (state) => state.pipe(includeKeys(['a', 'b'])),
        },
      ],
    }),
  ],
})
export class AppModule {}

Performance Optimization​

By default, the module will update the storage upon each state changes (distinctUntilChanged with object equality check is applied). Some applications perform multiple updates in a second, and update the storage on each change can be costly.

For such cases, it's recommended to use the debounceTime operator. For example:

import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { debounceTime } from 'rxjs/operators';
import { PersistStateModule, localStorageStrategy } from '@ngrx-addons/persist-store';

const counterReducer = ...;
const reducers = {
  counter: counterReducer,
} as const;

@NgModule({
  imports: [
    StoreModule.forRoot(reducers),
    PersistStateModule.forRoot<typeof reducers>({
      states: [
        {
          key: 'counter',
          storage: localStorageStrategy,
          source: (state) => state.pipe(debounceTime(1000)),
        },
      ],
    }),
  ],
})
export class AppModule {}

Examples

Check apps

changelog

8.0.3 (2025-06-13)

🩹 Fixes

  • persist-state: expose state migration type (88b2b5a)

❤️ Thank You

  • Michał Mrozek

8.0.2 (2025-03-22)

🩹 Fixes

  • sync-state: sync both ways after strategy signals readiness (#824)

❤️ Thank You

  • Michał Mrozek @Michsior14

8.0.1 (2025-03-01)

🩹 Fixes

  • sync-state: properly sync on skipped states (#804)

❤️ Thank You

  • Michał Mrozek @Michsior14

8.0.0 (2024-12-17)

🚀 Features

  • ⚠️ update to angular 19 (c27dcb1)
  • ⚠️ support ngrx 19 (8658ea3)
  • ⚠️ update ngrx to v19.0.0 (#733)

⚠️ Breaking Changes

  • ⚠️ update ngrx to v19.0.0 (#733)
  • ⚠️ support ngrx 19 (8658ea3)
  • ⚠️ update to angular 19 (c27dcb1)

❤️ Thank You

  • Michał Mrozek

7.0.0 (2024-06-14)

🚀 Features

  • ⚠️ update to ngrx 18 (#542)
  • add standalone api (#543)

⚠️ Breaking Changes

  • ⚠️ update to ngrx 18 (#542)

❤️ Thank You

  • Michał Mrozek @Michsior14

6.0.0 (2024-05-29)

🚀 Features

  • ⚠️ update to angular 18 (#520)

⚠️ Breaking Changes

  • ⚠️ update to angular 18 (#520)

❤️ Thank You

  • Michał Mrozek @Michsior14

Change Log

All notable changes to this project will be documented in this file. See Conventional Commits for commit guidelines.

5.0.1 (2024-01-26)

Note: Version bump only for package @ngrx-addons/root

5.0.0 (2023-11-21)

⚠ BREAKING CHANGES

  • update to angular 17 and ngrx 17 (#355)

Features

4.0.1 (2023-10-31)

Bug Fixes

  • make sure streams are always completed (180a99b)

4.0.0 (2023-08-10)

⚠ BREAKING CHANGES

  • update to angular 16
  • upgrade to angular 15 and ngrx 15

Features

  • add initialization strategies (#183) (3585f44), closes #182
  • add persist-state package (c0ed7dc)
  • add sync state lib (#9) (9e27911)
  • allow skipping configs in forRoot calls (#185) (dbc606c)
  • persist-state: add possibility to adjust number of state skip (f4aea6e)
  • persist-state: support migrations (34731d9)
  • update to angular 16 (dd051b1)
  • upgrade to angular 15 and ngrx 15 (1227351)

Bug Fixes

  • forFeatures registered only once (#192) (21bcd0c), closes #191
  • forRoot source typings (#189) (46fc84b)
  • lock common lib to specific version (95d8c61)
  • persist-state: add missing rxjs peer dependency (3318475)
  • persist-state: expose createStore (6160f81)
  • provide default type in feature modules (#179) (3c35ff7), closes #178
  • remove migrations file (26fda30)
  • wait to access storage until ready for strategies (#184) (cdbee08)