How to use Ionic-Storage in Vue 3.0?

Hi,
I am trying to use ionic-storage in my Vuejs project created using ionic-cli, but I don’t know how to use it! How do I have to create a store / how can I read data from it - do I have to set it as data in a sec or what?

I don’t find any docs about it - does anybody have made experiences with it?

Would be very happy as I love the ionic design and way of usage!

Thanks,
Jannis

@jannisgo, Actually I am not really using ionic storage from community plugins so much. The reason causes some plugins will have issues or are unable to use with capacitor.

So the solution is using storage of Capacitor and there are their docs. Hope my comment able help u out :slight_smile:

https://capacitorjs.com/docs/apis/storage

I am using both Ionic Storage and Capacitor Storage in my Vue 3 and Capacitor 3 app. @whitersun, Ionic Storage was built and is maintained by Ionic. They released v3 in March to support non-Angular projects.

The README of the GitHub repo for Ionic Storage does a pretty good job explaining how to set it up and use it. I am using it to persist my Vuex store.

Here is some basic code:

const storage = new Storage({
    storeName: 'vuex'
})

await storage.set('my_awesome_key', 'some_awesome_value')

const myAwesomeValue = await storage.get('my_awesome_key')

Let me know if you have any specific questions. Using it with Vuex required me to create a wrapper around it and to use a promise queue since mutations are synchronous.

Oh, thank you so much. I will try it for my project, try to change from capacitor storage to ionic storage, and comparison which one is suitable for me.

Hello,

I have a working VueX store functionality. And now I’m trying to persist my data with ionic/storage. But I have some tourbles setting it up. Could you share some code from the VueX file perhaps? This is my current code:

import { createStore } from 'vuex';
import { Storage } from '@ionic/storage';

const ionicStorage = new Storage();
ionicStorage.create();

const store = createStore({

    state() {
        return {
            key:""
        }

    },
    getters: {
        key(state){
            const myAwesomeValue = ionicStorage.get('my_awesome_key')
            if(myAwesomeValue != ''){
                return myAwesomeValue;
            }
            return state.key;
        }
    },
    mutations: {
        changeKey (state, payload) {
            ionicStorage.set('my_awesome_key', payload)
            state.key = payload
        }
    },
    actions: {}
}    
);

export default store;

But that delivers a promise so I need to build in a async function with await… But im not sure how to do that.

I’d be more than happy to share some code but I think I need a little more info of what you are trying to do.

Mutations are synchronous but Ionic Storage is asynchronous so if you mutate the same item multiple times in quick succession, it isn’t guaranteed that the last value will be what ends up getting set. This is why I built a promise queue so they are processed in the correct order.

Also, pulling from Ionic Storage every time in a get, isn’t really a good idea.

Here is a summary of what I am doing. If it sounds like what you are trying to do, I can share some code.

  • Throughout the app, Vuex is used like normal.
  • No getters touch Ionic Storage. Why would they need to if the data hasn’t changed?
  • Upon a mutation, the Vuex state is modified and then the change gets put onto the promise queue to go and update Ionic Storage.
  • Ionic Storage is only being used to persist the data so Vuex can be reloaded when the App is freshly loaded/booted/mounted.
  • When the app is mounted, I load Vuex with the data stored Ionic Storage.

Also, as a side note, if you are just getting started with Vuex, I would recommend switching to Pinia (https://pinia.vuejs.org). That is the next generation of Vuex and what Vue will be recommending as of next week when Vue 3 becomes default for the Vue ecosystem. Pinia is much simpler (gets rid of mutations) and fully supports Typescript.

1 Like

Thanks for your reply. I’m just getting started with Ionic and Vue so I’m for now building a very simple SPA. Consisting of 2 pages. Startpage and Settingspage. On the SettingsPage there is one input where a value is defined. That value is displayed in a text box on the Startpage. Very simple. Very basic. I got it working with VueX, now I need to get persistent storage. So I tried using this new Ionic/Storage.

If I see your summary I think that falls pretty well in my use case.

I see now I do some weird stuff indeed. I probably should only check on startup if there is a value in the ionic/storage.

I am indeed intrigued by that promise queue and how I can implmenet that exactly.

Thanks I will definitely check that out. Unfortunately I dont find a lot of tutorials of Ionic and Vue. Thanks for your help though. Very informative.

There is quite a bit of content on ionic and vuejs, just try googling it or asking. this myth that there is not a lot of content on ionic and react and ionic and vue is simply not true so I suggest you let go of that fallacy and ask for help and as I said, Google is your friend

Also since you are asking about Vuex and Ionic Storage, you would probably get better help if you asked that specific question and not added on to a question just about Ionic Storage

2 Likes

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)

Thanks for the detailed answer. With your code I have been able to make it work. This way. Probably not the most elegant, but it works, so I’m happy for now.

import { createStore } from 'vuex';
import { Storage } from '@ionic/storage';

const ionicStorage = new Storage();
ionicStorage.create();

const store = createStore({

    state() {
        return {
            key:""
        }

    },
    getters: {
        key(state){
            return state.key;
        },

        async getStorageKey(state){
            const myAwesomeValue = await ionicStorage.get('key')
            if(myAwesomeValue != ''){
                state.key = myAwesomeValue;
            }
            return state.key;
        }
    },
    mutations: {
        changeKey (state, payload) {
            ionicStorage.set('key', payload)
            state.key = payload
        }
    },
    actions: {}
}    
);

export default store;
1 Like