Storage
Back your stores with
localStorage
, sessionStorage
or any other mechanism you wish.npm install @ngxs/storage-plugin --save
# or if you are using yarn
yarn add @ngxs/storage-plugin
Import the
NgxsStoragePluginModule
into your app module like:import { NgxsModule } from '@ngxs/store';
import { NgxsStoragePluginModule } from '@ngxs/storage-plugin';
@NgModule({
imports: [NgxsModule.forRoot([]), NgxsStoragePluginModule.forRoot()]
})
export class AppModule {}
It is recommended to register the storage plugin before other plugins so initial state can be picked up by those plugins.
The plugin has the following optional values:
key
: State name(s) to be persisted. You can pass a string or array of strings that can be deeply nested via dot notation. If not provided, it defaults to all states using the@@STATE
key.namespace
: The namespace is used to prefix the key for the state slice. This is necessary when running micro frontend applications which use storage plugin. The namespace will eliminate the conflict between keys that might overlap.storage
: Storage strategy to use. This defaults to LocalStorage but you can pass SessionStorage or anything that implements the StorageEngine API.deserialize
: Custom deserializer. Defaults toJSON.parse
serialize
: Custom serializer. Defaults toJSON.stringify
migrations
: Migration strategiesbeforeSerialize
: Interceptor executed before serializationafterDeserialize
: Interceptor executed after deserialization
The
key
option is used to determine what states should be persisted in the storage. key
shouldn't be a random string, it has to coincide with your state names. Let's look at the below example:// novels.state.ts
@State<Novel[]>({
name: 'novels',
defaults: []
})
@Injectable()
export class NovelsState {}
// detectives.state.ts
@State<Detective[]>({
name: 'detectives',
defaults: []
})
@Injectable()
export class DetectivesState {}
In order to persist all states there is no need to provide the
key
option, so it's enough just to write:@NgModule({
imports: [NgxsStoragePluginModule.forRoot()]
})
export class AppModule {}
But what if we wanted to persist only
NovelsState
? Then we would have needed to pass its name to the key
option:@NgModule({
imports: [
NgxsStoragePluginModule.forRoot({
key: 'novels'
})
]
})
export class AppModule {}
It's also possible to provide a state class as opposed to its name:
@NgModule({
imports: [
NgxsStoragePluginModule.forRoot({
key: NovelsState
})
]
})
export class AppModule {}
And if we wanted to persist
NovelsState
and DetectivesState
:@NgModule({
imports: [
NgxsStoragePluginModule.forRoot({
key: ['novels', 'detectives']
})
]
})
export class AppModule {}
Or using state classes:
@NgModule({
imports: [
NgxsStoragePluginModule.forRoot({
key: [NovelsState, DetectivesState]
})
]
})
export class AppModule {}
You can even combine state classes and strings:
@NgModule({
imports: [
NgxsStoragePluginModule.forRoot({
key: ['novels', DetectivesState]
})
]
})
export class AppModule {}
This is very handy to avoid persisting runtime-only states that shouldn't be saved to any storage.
This is also possible to provide storage engines per individual key. Suppose we want to persist
NovelsState
into the local storage and DetectivesState
into the session storage. The key
signature will look as follows:import { LOCAL_STORAGE_ENGINE, SESSION_STORAGE_ENGINE } from '@ngxs/storage-plugin';
@NgModule({
imports: [
NgxsStoragePluginModule.forRoot({
key: [
{
key: 'novels', // or `NovelsState`
engine: LOCAL_STORAGE_ENGINE
},
{
key: DetectivesState, // or `detectives`
engine: SESSION_STORAGE_ENGINE
}
]
})
]
})
export class AppModule {}
LOCAL_STORAGE_ENGINE
and SESSION_STORAGE_ENGINE
are injection tokens that resolve to localStorage
and sessionStorage
. They shouldn't be used in apps with server-side rendering because it will throw an exception that those symbols are not defined on the global scope. Instead, we should provide a custom storage engine. The engine
property may also refer to classes that implement the StorageEngine
interface:import { StorageEngine } from '@ngxs/storage-plugin';
@Injectable({ providedIn: 'root' })
export class MyCustomStorageEngine implements StorageEngine {
// ...
}
@NgModule({
imports: [
NgxsStoragePluginModule.forRoot({
key: [
{
key: 'novels',
engine: MyCustomStorageEngine
}
]
})
]
})
export class AppModule {}
The namespace option should be provided when the storage plugin is used in micro frontend applications. The namespace may equal the app name and will prefix keys for state slices:
@NgModule({
imports: [
NgxsStoragePluginModule.forRoot({
namespace: 'auth'
})
]
})
export class AppModule {}
You can add your own storage engine by implementing the
StorageEngine
interface.import { NgxsStoragePluginModule, StorageEngine, STORAGE_ENGINE } from '@ngxs/storage-plugin';
export class MyStorageEngine implements StorageEngine {
get length(): number {
// Your logic here
}
getItem(key: string): any {
// Your logic here
}
setItem(key: string, val: any): void {
// Your logic here
}
removeItem(key: string): void {
// Your logic here
}
clear(): void {
// Your logic here
}
}
@NgModule({
imports: [NgxsModule.forRoot([]), NgxsStoragePluginModule.forRoot()],
providers: [
{
provide: STORAGE_ENGINE,
useClass: MyStorageEngine
}
]
})
export class MyModule {}
You can define your own logic before or after the state get serialized or deserialized.
- beforeSerialize: Use this option to alter the state before it gets serialized.
- afterSerialize: Use this option to alter the state after it gets deserialized. For instance, you can use it to instantiate a concrete class.
@NgModule({
imports: [
NgxsStoragePluginModule.forRoot({
key: 'counter',
beforeSerialize: (obj, key) => {
if (key === 'counter') {
return {
count: obj.count < 10 ? obj.count : 10
};
}
return obj;
},
afterDeserialize: (obj, key) => {
if (key === 'counter') {
return new CounterInfoStateModel(obj.count);
}
return obj;
}
})
]
})
export class AppModule {}
You can migrate data from one version to another during the startup of the store. Below is a strategy to migrate my state from
animals
to newAnimals
.@NgModule({
imports: [
NgxsModule.forRoot([]),
NgxsStoragePluginModule.forRoot({
migrations: [
{
version: 1,
key: 'zoo',
versionKey: 'myVersion',
migrate: state => {
return {
newAnimals: state.animals,
version: 2 // Important to set this to the next version!
};
}
}
]
})
]
})
export class MyModule {}
In the migration strategy, we define:
version
: The version we are migratingversionKey
: The identifier for the version key (Defaults to 'version')migrate
: A function that accepts a state and expects the new state in return.key
: The key for the item to migrate. If not specified, it takes the entire storage state.
Note: Its important to specify the strategies in the order of which they should progress.
Last modified 2mo ago