Learning Vue for Ionic/Angular Developers – Part 2: Navigation
Learning Vue for Ionic/Angular Developers – Part 3: Services/Providers and HTTP Requests
Learning Vue for Ionic/Angular Developers – Part 4: Storing Data with Local Storage, IndexedDB, WebSQL, and SQLite (this tutorial)
Learning Vue for Ionic/Angular Developers – Part 5: Building with Cordova and the Vue CLI
Most applications will at some point require saving some kind of data that needs to be persisted across application reloads. Often, we want to make use of the local storage on the user’s device to do this. When using Ionic/Angular we can use Ionic’s built-in Storage API to easily interact with storage without needing to know what is happening behind the scenes – Ionic will automatically use the best storage mechanism available.
Once again, with Vue, we don’t have these luxuries out of the box. It’s up to us to decide what we want to use, and either install a library to do that or build our own solution.
In this tutorial, we are going to extend the Reddit application we built in the last tutorial to allow switching between multiple different subreddits. We will then save the user’s selection to storage so that upon revisiting the application their choice will be saved.
Getting Started
This tutorial will follow on from the previous tutorial. If you want to follow along step-by-step, you should complete that tutorial first. However, you could easily apply the concepts in this tutorial to any application, so it is not necessary to complete the previous tutorial if you do not want to.
localForage and the Ionic Storage Module
Before we get into the code, we are going to talk a little theory. We are going to base our code on the Ionic Storage Module that is available in the ionic-angular
library. Specifically, we are going to base our code off of the Storage class.
Behind the scenes, Ionic uses the localForage library to interact with storage. By using localForage we are given a consistent API to interact with, no matter what technology is actually being used for storage: Native SQLite storage, IndexedDB, WebSQL, or even just simple browser local storage.
The Ionic Storage module wraps localForage and handles automatically setting the appropriate driver based on what is available. That means that if you build your application with Cordova and have the SQLite plugin installed, then that is what will be used to store data. If the SQLite plugin is not available it will fallback to using IndexedDB or WebSQL, and if neither of those is available the local storage API will be used as a last resort.
We want to emulate this behaviour in our Ionic/Vue application, so let’s take a look at how Ionic does it. We will just focus on the most relevant parts of the storage.ts file.
import LocalForage from 'localforage';
import CordovaSQLiteDriver from 'localforage-cordovasqlitedriver';
The localForage library needs to be installed and imported, but we also need to separately install the CordovaSQLiteDriver if we want to also support being able to use SQLite. The benefit of using SQLite is that we don’t need to worry about the browser storage being cleared by the operating system.
constructor(config: StorageConfig) {
this._dbPromise = new Promise((resolve, reject) => {
let db: LocalForage;
const defaultConfig = getDefaultConfig();
const actualConfig = Object.assign(defaultConfig, config || {});
LocalForage.defineDriver(CordovaSQLiteDriver).then(() => {
db = LocalForage.createInstance(actualConfig);
})
.then(() => db.setDriver(this._getDriverOrder(actualConfig.driverOrder)))
.then(() => {
this._driver = db.driver();
resolve(db);
})
.catch(reason => reject(reason));
});
}
In the constructor
a promise is created that wraps the instantiation of localForage. We need to first define the CordovaSQLiteDriver
, and once that is set up we can create a new instance of Local Forage using a configuration object.
The default configuration for the Ionic Storage API looks like this:
{
name: '_ionicstorage',
storeName: '_ionickv',
driverOrder: ['sqlite', 'indexeddb', 'websql', 'localstorage']
};
The most important part of this config is the order of the drivers, this is what determines the preference of storage mechanisms. In this case, sqlite
is will be used first, and localstorage
will be used last. In order to make sure that these drivers are named appropriately, the following function is being used:
_getDriverOrder(driverOrder) {
return driverOrder.map((driver) => {
switch (driver) {
case 'sqlite':
return CordovaSQLiteDriver._driver;
case 'indexeddb':
return LocalForage.INDEXEDDB;
case 'websql':
return LocalForage.WEBSQL;
case 'localstorage':
return LocalForage.LOCALSTORAGE;
}
});
}
This will map the driver order array to use the appropriate values provided by localForage. If you are not familiar with the map
method of arrays, you may be interested in watching this video. The basic idea is that a map applies some transformation to each element in the array, in this case, we are swapping out the value we have supplied in the array, with the actual name of the driver as defined by localForage.
Most of the rest of the API is just simple wrappers around the standard localForage functions:
get(key: string): Promise<any> {
return this._dbPromise.then(db => db.getItem(key));
}
set(key: string, value: any): Promise<any> {
return this._dbPromise.then(db => db.setItem(key, value));
}
All the Ionic Storage module is doing is making sure that the storage has finished being instantiated properly by checking the dbPromise
class member. Once that promise has resolved, the standard getItem
and setItem
functions are used to set the values appropriately in storage (or retrieve them).
Creating a Storage Service in Vue
We could just interface with localForage directly in our components, but if we want to set up something smart like Ionic’s Storage API, then it makes more sense to separate it out into its own service. We are going to take the concepts we’ve just learned, and use them to create a new storage service in our Ionic/Vue application.
First, we will need to install the dependencies in our project.
Run the following commands:
npm install localforage --save
npm install localforage-cordovasqlitedriver --save
Create a file at src/services/storage.js and add the following:
import LocalForage from 'localforage';
import CordovaSQLiteDriver from 'localforage-cordovasqlitedriver';
export default class Storage {
dbPromise;
constructor() {
this.dbPromise = new Promise((resolve, reject) => {
let db;
let config = {
name: '_vuestorage',
storeName: '_vuekv',
driverOrder: ['sqlite', 'indexeddb', 'websql', 'localstorage'],
};
LocalForage.defineDriver(CordovaSQLiteDriver)
.then(() => {
db = LocalForage.createInstance(config);
})
.then(() => db.setDriver(this.getDriverOrder(config.driverOrder)))
.then(() => {
resolve(db);
})
.catch((reason) => reject(reason));
});
}
ready() {
return this.dbPromise;
}
getDriverOrder(driverOrder) {
return driverOrder.map((driver) => {
switch (driver) {
case 'sqlite':
return CordovaSQLiteDriver._driver;
case 'indexeddb':
return LocalForage.INDEXEDDB;
case 'websql':
return LocalForage.WEBSQL;
case 'localstorage':
return LocalForage.LOCALSTORAGE;
}
});
}
get(key) {
return this.dbPromise.then((db) => db.getItem(key));
}
set(key, value) {
return this.dbPromise.then((db) => db.setItem(key, value));
}
remove(key) {
return this.dbPromise.then((db) => db.removeItem(key));
}
clear() {
return this.dbPromise.then((db) => db.clear());
}
}
This is just a simplified version of the same code that the Ionic Storage API uses – it more or less functions in the same way. I’ve left some features out so that it looks a little friendlier, but there is no reason why you couldn’t also implement the rest of the features.
Saving and Retrieve Data Using the Storage Service
Now, all we need to do is make use of our service. It will be as simple as doing this for saving to storage:
storage.set('something', 'somevalue');
and this for retrieving from storage:
storage.get('something').then((value) => {
console.log(value);
});
Modify src/components/HelloWorld.vue to reflect the following:
<template>
<ion-app>
<ion-header>
<ion-navbar>
<ion-title>REDDIT!</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-button @click="switchSubreddit('funny')">Funny</ion-button>
<ion-button @click="switchSubreddit('gifs')">Gifs</ion-button>
<ion-button @click="switchSubreddit('worldnews')">Worldnews</ion-button>
<ion-list>
<ion-item v-for="post in posts" v-bind:key="post.data.id">
{{post.data.title}}
</ion-item>
</ion-list>
</ion-content>
</ion-app>
</template>
<script>
import RedditService from '../services/reddit';
import Storage from '../services/storage';
const storage = new Storage();
export default {
name: 'HelloWorld',
data() {
return {
posts: [],
};
},
created() {
storage.get('subreddit').then((value) => {
if (value === null) {
value = 'gifs';
}
RedditService.getPosts(value).then((response) => {
this.posts = response.body.data.children;
});
});
},
methods: {
switchSubreddit(subreddit) {
storage.set('subreddit', subreddit);
RedditService.getPosts(subreddit).then((response) => {
this.posts = response.body.data.children;
});
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
We have imported our new storage service, and we are first calling the get
method inside of the created
method. The created
method will run as soon as this component is created, so it will immediately check in storage for the existence of subreddit
. If this value exists, that subreddit will be used for the call to the API, if it does not, then the default gifs
value will be used instead.
We have also set up three buttons that trigger the switchSubreddit
function with different values. That function will save the new subreddit into storage, and then call the getPosts
method to change the subreddit. Next time that the application launches, the value that was saved to storage will be used instead of the default gifs
subreddit.
NOTE: If you have not followed the previous tutorials, you won’t be able to use the Ionic components in the template here.
Summary
With not too much extra work, we have managed to create our own storage service that is as convenient and useful as the default storage mechanism supplied to Ionic/Angular applications.