Persist State

The persistState() function gives you the ability to persist some of the app’s state, by saving it to localStorage/sessionStorage or anything that implements the StorageEngine API, and restore it after a refresh.

To use it you should call the persistState() function. Here's an example of Angular's main file:

main.ts
import { persistState } from '@datorama/akita';
const storage = persistState();
const providers = [{ provide: 'persistStorage', useValue: storage }];
platformBrowserDynamic(providers)
.bootstrapModule(AppModule)
.catch((err) => console.log(err));

API

clearStore

Clear the provided store from storage:

export class TodosService {
constructor(@Inject('persistStorage') private persistStorage) {
persistStorage.clearStore('todos');
// Clear all
persistStorage.clearStore();
}
}

destroy

Stop sync the state:

import { PersistState } from '@datorama/akita';
export class TodosService {
constructor(@Inject('persistStorage') private persistStorage: PersistState) {}
stopSync() {
this.persistStorage.destroy();
}
}

Options

storage

storage strategy to use. This defaults to localStorage but you can pass sessionStorage or anything that implements the StorageEngine API.

key

The key by which the state is saved.

include

By default the whole state is saved to storage, use this param to include only the stores you need. It can be a store name or a predicate callback.

info

To store an entityUIStore you need to add UI/{storeName}.

select

By default the whole state is saved to storage, use this param to include only the data you need.

import { persistState, PersistStateSelectFn } from '@datorama/akita';
const selectToken: PersistStateSelectFn<AuthState> = (state) => ({ token: state.token });
selectToken.storeName = 'auth';
persistState({ select: [selectToken] });

deserialize

Custom deserializer. Defaults to JSON.parse

serialize

Custom serializer, defaults to JSON.stringify

preStorageUpdateOperator

Custom operators that will run before storage update

enableInNonBrowser

Whether to enable persistState in a non-browser environment

persistOnDestroy

Whether to persist the cache value of dynamic store upon destroy

You can also track a specific store's key. For example:

persistState({ include: ['auth.token'] });

Separate branch in storage

By default when you provide persistStorage, it means that all stores would be persisted under one key in localStorage or other storage.

@Injectable({ providedIn: 'root' })
@StoreConfig({ name: 'settings' })
export class SettingsStore extends Store<SettingsState> {
constructor() {
super();
}
}
@Injectable({ providedIn: 'root' })
@StoreConfig({ name: 'users' })
export class UsersStore extends Store<UsersState> {
constructor() {
super(createInitialState());
}
}
export const persistStorage = persistState({
include: ['settings', 'users'],
key: 'myStore',
});
const providers = [{ provide: 'persistStorage', useValue: storage }];

In the example, all stores data would be saved in one localStorage item with key myStore;

But, you also can provide a storage which would be saved in the separate localStorage item:

@Injectable({ providedIn: 'root' })
@StoreConfig({ name: 'settings' })
export class SettingsStore extends Store<SettingsState> {
constructor() {
super();
}
}
export const settingsPersistStorage = persistState({
include: ['settings'],
key: 'settingsStore',
});
@Injectable({ providedIn: 'root' })
@StoreConfig({ name: 'users' })
export class UsersStore extends Store<UsersState> {
constructor() {
super(createInitialState());
}
}
export const usersPersistStorage = persistState({
include: ['users'],
key: 'usersStore',
});
const providers = [
{ provide: 'persistStorage', useValue: settingsPersistStorage, multi: true },
{ provide: 'persistStorage', useValue: usersPersistStorage, multi: true },
];

Now, SettingsStore would be saved in localStorage under key settingsStore and UsersStore under usersStore.

Custom Hooks

You can use the preStorageUpdate, and preStoreUpdate hooks to get more control on the state. For example, here is how we can save only specific keys from the auth state:

persistState({
include: ['auth', 'todos'],
preStorageUpdate(storeName, state) {
if (storeName === 'auth') {
return {
token: state.token,
expired: state.expired,
};
}
return state;
},
preStoreUpdate(storeName, state) {
return state;
},
});

We can also control how the state is being saved and updated:

persistState({
include: ['auth', 'products'],
preStorageUpdate(storeName, state) {
if (storeName === 'products') {
return {
sort: state.ui.filters.sort,
};
}
return state;
},
preStoreUpdate(storeName, state, initialState) {
if (storeName === 'products') {
return {
ui: {
filters: {
...initialState.ui.filters,
sort: state.sort,
},
},
};
}
return state;
},
});

Async Support

This gives you the option to save a store’s value to a persistent storage, such as indexDB, websql, or any other asynchronous API. Here’s an example that leverages localForage:

import * as localForage from 'localforage';
localForage.config({
driver: localForage.INDEXEDDB,
name: 'Akita',
version: 1.0,
storeName: 'akita-storage',
});
persistState({ include: ['auth.token', 'todos'], storage: localForage });

selectPersistStateInit

Akita also exposes the selectPersistStateInit observable. This observable emits after Akita initialized the stores based on the storage's value. For example:

import { selectPersistStateInit } from '@datorama/akita';
export class AuthGuard {
constructor(private router: Router, private authQuery: AuthQuery) {}
canActivate() {
return combineLatest([this.authQuery.isLoggedIn$, selectPersistStateInit()]).pipe(
map(([isAuth]) => {
if (isAuth) return true;
this.router.navigateByUrl('login');
return false;
}),
take(1)
);
}
}

Performance Optimization

By default, the plugin will update the storage upon each store's change. 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 preStorageUpdateOperator option and add a debounce. For example:

import { debounceTime } from 'rxjs/operators';
persistState({
include: ['auth.token', 'todos'],
preStorageUpdateOperator: () => debounceTime(2000),
});