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:
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.
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';
persistState({
include: ['auth.token', 'todos'],
preStorageUpdateOperator: () => debounceTime(2000),
});