Я пытаюсь реализовать SQLite в NGXS. Я использую NGXS Storage, как определено здесь: https://www.ngxs.io/plugins/storage.
Я определил свой пользовательский механизм хранения следующим образом:
@Injectable({
providedIn: 'root'
})
export class IonicStorageProvider implements AsyncStorageEngine {
constructor(private storage: Storage) {}
length(): Observable<number> {
return from(this.storage.keys()).lift(keyArray => keyArray.length);
}
getItem(key: any): Observable<any> {
return from(this.storage.get(key));
}
setItem(key: any, val: any): Observable<any> {
return from(this.storage.set( key, val ));
}
removeItem(key: any): Observable<any> {
return from(this.storage.remove(key));
}
clear(): Observable<void> {
return from(this.storage.clear());
}
key(val: number): Observable<string> {
throw new Error('Method not implemented.');
}
keys(): Observable<any> {
return from(this.storage.keys());
}
}
И определил его в app.module:
IonicStorageProvider,
StoragePlugin,
{
provide: STORAGE_ENGINE,
useClass: IonicStorageProvider
},
{
provide: NGXS_STORAGE_PLUGIN_OPTIONS,
useValue: {
key: environment.stateStorageKeys,
serialize: JSON.stringify,
deserialize: JSON.parse
}
},
{
provide: NGXS_PLUGINS,
useClass: StoragePlugin,
multi: true
}
И определил этот StoragePlugin, который реализует функция обработки:
import { Injectable, Inject } from '@angular/core';
import { NgxsPlugin, NgxsNextPluginFn, actionMatcher, InitState, UpdateState, getValue, setValue } from '@ngxs/store';
import { tap, concatMap, reduce, map, flatMap } from 'rxjs/operators';
import { Observable, of, from, concat } from 'rxjs';
import {
NGXS_STORAGE_PLUGIN_OPTIONS,
STORAGE_ENGINE,
NgxsStoragePluginOptions,
StorageEngine,
AsyncStorageEngine,
AsyncStorageEngineProxy
} from '@ngxs-labs/async-storage-plugin';
@Injectable()
export class StoragePlugin implements NgxsPlugin {
private asyncStorageEngine: AsyncStorageEngine;
constructor(
@Inject(NGXS_STORAGE_PLUGIN_OPTIONS) private pluginOptions: NgxsStoragePluginOptions,
@Inject(STORAGE_ENGINE) private storageEngine: StorageEngine | AsyncStorageEngine
) {
if (typeof this.storageEngine.length === 'function') {
this.asyncStorageEngine = this.storageEngine as AsyncStorageEngine;
} else {
this.asyncStorageEngine = new AsyncStorageEngineProxy(this.storageEngine as StorageEngine);
}
}
handle(state: any, event: any, next: NgxsNextPluginFn) {
console.log('StoragePlugin handle');
const options = this.pluginOptions || {} as any;
const matches = actionMatcher(event);
const isInitAction = matches(InitState) || matches(UpdateState);
const keys: any[] = Array.isArray(options.key) ? options.key : [options.key];
let hasMigration = false;
let initAction: Observable<any> = of(state);
if (isInitAction) {
console.log('StoragePlugin getItem');
initAction = from(keys).pipe(
concatMap(key => this.asyncStorageEngine.getItem(key.key || key).pipe(map(val => [key, val]))),
reduce((previousState, [key, val]) => {
console.log('StoragePlugin: getItem key:', key);
console.log('StoragePlugin: getItem val:', val);
const isMaster = key === '@@STATE';
let nextState = previousState;
if (val !== 'undefined' && typeof val !== 'undefined' && val !== null) {
try {
val = options.deserialize(val);
} catch (e) {
console.error('Error ocurred while deserializing the store value, falling back to empty object.', e);
val = {};
}
if (options.migrations) {
options.migrations.forEach(strategy => {
const versionMatch = strategy.version === getValue(val, strategy.versionKey || 'version');
const keyMatch = (!strategy.key && isMaster) || strategy.key === key;
if (versionMatch && keyMatch) {
val = strategy.migrate(val);
hasMigration = true;
}
});
}
if (!isMaster) {
nextState = setValue(previousState, key.key || key, val);
} else {
nextState = { ...previousState, ...val };
}
}
return nextState;
}, state),
);
}
return initAction.pipe(
concatMap(stateAfterInit => next(stateAfterInit, event)),
tap(async nextState => {
if (!isInitAction || (isInitAction && hasMigration)) {
for (let key of keys) {
let val = nextState as any;
if (key !== '@@STATE') {
if (!key || typeof (key) === 'string') {
val = await getValue(nextState, key);
} else {
const subKeys = key.subKeys;
key = key.key;
val = await getValue(nextState, key);
const filtered = [];
for (const element of val) {
const obj = {} as any;
for (const subKey of subKeys) {
obj[subKey] = element[subKey];
}
filtered.push(obj);
}
val = filtered;
}
}
try {
console.log('StoragePlugin: setItem key:', key);
console.log('StoragePlugin: setItem val:', val);
this.asyncStorageEngine.setItem(key, options.serialize(val));
} catch (e) {
console.error('Error ocurred while serializing the store value, value not updated.', e);
}
}
}
})
);
}
Я отдельно проверил получение и установку хранилища, и он работает нормально. Однако при запуске приложения оно сначала вызывает setItem несколько раз, сбрасывая состояние, а затем вызывает getItem (который возвращает сброшенное состояние).
До этого я использовал плагин localstorage, и этот плагин сначала вызывал getItem, а затем setItem, который правильно устанавливает состояние.
Старый поставщик хранилища:
import { Observable, from } from 'rxjs';
import { AsyncStorageEngine } from '@ngxs-labs/async-storage-plugin';
import { Injectable } from '@angular/core';
import { Storage } from '@capacitor/core';
import { pluck } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class CapacitorStorageProvider implements AsyncStorageEngine {
length(): Observable<number> {
return from(Storage.keys()).lift(keyArray => keyArray.length);
}
getItem(key: any): Observable<any> {
return from(Storage.get({ key: key })).pipe(pluck('value'));
}
setItem(key: any, val: any): Observable<any> {
return from(Storage.set({ key: key, value: val }));
}
removeItem(key: any): Observable<any> {
return from(Storage.remove({ key: key }));
}
clear(): Observable<void> {
return from(Storage.clear());
}
key(val: number): Observable<string> {
throw new Error('Method not implemented.');
}
}