How to use Ionic-Storage in Vue 3.0?

Welcome to Ionic! I started my Ionic journey a little over a year ago using Vue.

Here is some code. Hopefully it helps and definitely let me know if you have any questions. A couple notes.

  • I am using TypeScript
  • I am using Mutation types which is an enum of my mutation names
  • I created a store wrapper class around Ionic Storage
  • I am also using Capacitor Storage but removed it from the code below. I store a few items from Vuex there like the user’s API key. For two reasons, it is more secure since it is OS native storage and two there is more of a guarantee that it will remain. It is slower and only meant to store key/value pairs.
  • I am stringifying the data before storing in Ionic Storage. I can’t remember why, but I think there was an issue getting it back out if it was stored as JSON.
  • I use a store version (a prop in my state) so if I’ve changed data structures, when the app boots up it will not reload from Ionic Storage. All of the data in my app except for the API key can be re-pulled from the API.
  • Last but not least, I did all of this almost a year ago so don’t judge me if anything is not best practice :joy:

utils/simple-promise-queue.ts

// Adapted from https://github.com/championswimmer/vuex-persist/blob/a056a7b4e9420b9254a4fb0f829ea7c1d6522261/src/SimplePromiseQueue.ts

export default class SimplePromiseQueue {
    private readonly _queue: Array<Promise<void>> = []
    private _flushing = false

    public enqueue(promise: Promise<void>): Promise<void> {
        this._queue.push(promise)

        if (!this._flushing) {
            return this.flushQueue()
        }

        return Promise.resolve()
    }

    private flushQueue(): Promise<void> {
        this._flushing = true

        const chain = (): Promise<void> | void => {
            const nextTask = this._queue.shift()

            if (nextTask) {
                return nextTask.then(chain)
            } else {
                this._flushing = false
            }
        }

        return Promise.resolve(chain())
    }
}

store/index.ts

// Note this code only includes snippets of my file

import { StoreStorage } from '@/store/store-storage'

export const persistentStorage = new StoreStorage()

store/state.ts

// Note this code only includes snippets of my file

export interface State {
    storeVersion: string
    stats: StatsModel
    communities: CommunityCollection
}

export const state: State = {
    storeVersion: '5',
    stats: new StatsModel(),
    communities: new CommunityCollection(),
}

store/mutations.ts

// Note this code only includes snippets of my file

import { persistentStorage } from '.' // Imports from store/index.ts

export interface MutationRestorer {
    propName: keyof State
    data: any
}

export const mutations: MutationTree<State> & Mutations = {
     // This mutation is used to restore the state from Ionic Storage.
    // It is called from store-storage.ts
    [MutationTypes.RESTORE_STATE](state: State, payload: MutationRestorer) {
        if (payload.propName === 'stats')
            state[payload.propName] = StatsModel.fromJson(payload.data)
        else if (payload.propName === 'communities')
            state[payload.propName] = CommunityCollection.fromJson(payload.data)
    },

    // Two examples of normal mutations.
    // First sets the state in Vuex and then sets persistent storage using the promise queue
    [MutationTypes.SET_STATS](state, payload: StatsModel) {
        state.stats = payload
        
        persistentStorage.set('stats', state.stats)
    },

    [MutationTypes.SET_COMMUNITIES](state, payload: CommunityCollection) {
        state.communities = payload

        persistentStorage.set('communities', state.communities)
    },
}

store/store-storage.ts
This is my storage wrapper around Ionic Storage to save and re-populate my Vuex store.

import SimplePromiseQueue from '@/utils/simple-promise-queue'
import { Storage } from '@ionic/storage'
import { store } from '.'
import { MutationTypes } from './mutation-types'
import { MutationRestorer } from './mutations'
import { State, state } from './state'

export class StoreStorage {
    private storageAdapter: Storage
    private promiseQueue: SimplePromiseQueue
    private storeIsRestored = false

    public constructor() {
        this.storageAdapter = new Storage({
            storeName: 'vuex',
        })

        this.promiseQueue = new SimplePromiseQueue()
    }

    public async initialize(): Promise<void> {
        if (!this.storeIsRestored) {
            await this.storageAdapter.create()

            // Check store versions. If not the same version, don't reload and clear Ionic Storage.
            if (!(await this.isSameVersion())) {
                await this.storageAdapter.clear()
                await this.set('storeVersion', state.storeVersion)
            }

            if ((await this.storageAdapter.length()) !== 0) {
                const statePropsArray: Array<keyof State> = Object.keys(state) as Array<keyof State>

                for (const propName of statePropsArray) {
                    let data = await this.get<typeof propName>(propName)

                    if (data != null) {
                        const payload: MutationRestorer = {
                            propName: propName,
                            data: data,
                        }

                        store.commit(MutationTypes.RESTORE_STATE, payload)
                    }
                }
            }

            this.storeIsRestored = true
        }
    }

    public async get<T>(key: keyof State): Promise<T> {
        return JSON.parse(await this.storageAdapter.get(key))
    }

    public set<T>(key: keyof State, data: T): Promise<void> {
        return this.promiseQueue.enqueue(this.storageAdapter.set(key, JSON.stringify(data)))
    }

    public remove(key: keyof State): Promise<void> {
        return this.storageAdapter.remove(key)
    }

    public wipe(): Promise<void> {
        return this.storageAdapter.clear()
    }

    private async isSameVersion(): Promise<boolean> {
        const storeVersion = await this.get<string>('storeVersion')

        if (storeVersion === state.storeVersion) {
            return true
        }

        return false
    }
}

main.ts
This shows how I re-populate on app mount.

import router from '@/router'
import { persistentStorage } from '@/store'

// Before `createApp` is called
const waitForStoreStorageToBeReady = async () => {
    await persistentStorage.initialize()
}

router.beforeEach(waitForStoreStorageToBeReady)
1 Like