NGXS Ioni c хранилище SQLite - PullRequest
1 голос
/ 17 апреля 2020

Я пытаюсь реализовать 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.');
    }
}
...