When developing an Ionic 2 application there are often scenarios where it probably isn’t a good idea to include repetitive source code in multiple application pages. A common example of this would be when it comes to database interaction in an Ionic 2 application. Sure you could establish a connection to the database on every page and query it, but it would probably make more sense to use it like a shared provider.
We’re going to see how to create a SQLite shared provider, often referred to as a shared service or sometimes a singleton class, in an Ionic 2 Android and iOS application using Angular.
To make what we’re about to cover as easy to understand as possible, we’re going to start with a fresh Ionic 2 project. From your Terminal (Mac and Linux) or Command Prompt (Windows), execute the following:
ionic start provider-project blank --v2
cd provider-project
ionic platform add ios
ionic platform add android
There are a few things to note in the above commands. The first thing to note is the --v2
tag found in the project creation command. This tags mean that we’ll be creating an Ionic 2 project that uses TypeScript. You must be using the Ionic 2 CLI to make this possible, not the Ionic Framework 1 CLI. The second thing to note is that if you’re not using a Mac, you cannot build for the iOS platform.
Since SQLite will be the basis around our example, we need to add the Apache Cordova SQLite plugin. Using your Command Prompt or Terminal, execute the following:
ionic plugin add cordova-sqlite-storage
If you want to know the basics behind the SQLite plugin and Ionic 2, check out the previous post I wrote regarding using SQLite in an Ionic 2 application.
So the goal here is to set up the SQLite foundation once and use it with a minimal amount of effort in each of the pages. Let’s start by creating a provider for our database. From your Terminal or Command Prompt, execute the following:
ionic g provider database
The above command will generate a provider at app/providers/database/database.ts. It will be pre-populated with a lot of code that we don’t need.
Open the app/providers/database/database.ts file and replace the code with the following:
import { Injectable } from '@angular/core';
import {SQLite} from 'ionic-native';
@Injectable()
export class Database {
private storage: SQLite;
private isOpen: boolean;
public constructor() {
if(!this.isOpen) {
this.storage = new SQLite();
this.storage.openDatabase({name: "data.db", location: "default"}).then(() => {
this.storage.executeSql("CREATE TABLE IF NOT EXISTS people (id INTEGER PRIMARY KEY AUTOINCREMENT, firstname TEXT, lastname TEXT)", []);
this.isOpen = true;
});
}
}
public getPeople() {
return new Promise((resolve, reject) => {
this.storage.executeSql("SELECT * FROM people", []).then((data) => {
let people = [];
if(data.rows.length > 0) {
for(let i = 0; i < data.rows.length; i++) {
people.push({
id: data.rows.item(i).id,
firstname: data.rows.item(i).firstname,
lastname: data.rows.item(i).lastname
});
}
}
resolve(people);
}, (error) => {
reject(error);
});
});
}
public createPerson(firstname: string, lastname: string) {
return new Promise((resolve, reject) => {
this.storage.executeSql("INSERT INTO people (firstname, lastname) VALUES (?, ?)", [firstname, lastname]).then((data) => {
resolve(data);
}, (error) => {
reject(error);
});
});
}
}
There is a quite a bit happening above so let’s break it down.
Since we’re using SQLite we can use of Ionic Native to make it a bit more Angular friendly. We import the Ionic Native components along with the Injectable
component so we can inject it in our pages and providers list.
In the Database
class we create a private variable for keeping track of our storage solution. In the constructor
method we initialize the storage solution and create a new database table only if it doesn’t already exist. This constructor
method is called every time we inject this provider into a page, but our conditional logic prevents trying to open more than one instance of the database.
Finally we have two functions, a getPeople
function and a createPerson
function. The function for retrieving data will return a promise. The success response of the promise will be an array because we are transforming the SQLite results to an array of objects. The function for creating data will take two strings and return a row id.
So how do we make use of this database provider?
To keep this example simple we’re only going to make use of the HomePage
which was created when we started a new project. This page will have a TypeScript logic file and HTML UI file.
Starting with the TypeScript file, open the project’s app/pages/home/home.ts file and include the following code:
import {Component} from "@angular/core";
import {NavController} from 'ionic-angular';
import {Database} from "../../providers/database/database";
@Component({
templateUrl: 'build/pages/home/home.html'
})
export class HomePage {
public itemList: Array<Object>;
public constructor(private navController: NavController, private database: Database) {
this.itemList = [];
}
public onPageDidEnter() {
this.load();
}
public load() {
this.database.getPeople().then((result) => {
this.itemList = <Array<Object>> result;
}, (error) => {
console.log("ERROR: ", error);
});
}
public create(firstname: string, lastname: string) {
this.database.createPerson(firstname, lastname).then((result) => {
this.load();
}, (error) => {
console.log("ERROR: ", error);
});
}
}
We start by importing the provider that we had just created. In the HomePage
class we define a public variable which will be bound to the page UI. It will contain data from the database.
The constructor
method will do two things. It is where we inject the database provider for use in other functions and it is where we initialize the public variable.
It is not a good idea to load data in the constructor
method. Instead we’re going to load the data from the onPageDidEnter
method. This method will call the load
method which will call the getPeople
method from the database provider. Notice how we don’t use any SQL here or any data parsing. Welcome to the magic of adding functionality to providers.
Finally there is the create
method that will insert new data and then load it once it has been saved.
Now let’s look at the HTML file that is paired with our TypeScript logic. Open the project’s app/pages/home/home.html file and include the following markup:
<ion-header>
<ion-navbar>
<ion-title>
Provider Project
</ion-title>
<ion-buttons end>
<button (click)="create('Nic', 'Raboy')">
<ion-icon name="add"></ion-icon>
</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-list>
<ion-item *ngFor="let person of itemList">
{{person.firstname}} {{person.lastname}}
</ion-item>
</ion-list>
</ion-content>
In the navigation bar we have a button that will call the create
method. We also have a list that will loop through our public array of objects. When adding new items to the database, the list will automatically be refreshed.
To be able to use the providers on every page of the application, they must be added to the applications root TypeScript file. This is not too difficult to do.
Open the project’s app/app.ts file and include the following code:
import {Component} from '@angular/core';
import {Platform, ionicBootstrap} from 'ionic-angular';
import {StatusBar} from 'ionic-native';
import {HomePage} from './pages/home/home';
import {Database} from "./providers/database/database";
@Component({
template: '<ion-nav [root]="rootPage"></ion-nav>',
providers: [Database]
})
export class MyApp {
rootPage: any = HomePage;
constructor(platform: Platform) {
platform.ready().then(() => {
StatusBar.styleDefault();
});
}
}
ionicBootstrap(MyApp);
Let’s break down the above code.
First we import the Database
provider for use in this particular file. Where the magic really happens is in the providers
property of the @Component
block. Any providers added here will be available throughout the rest of the application pages.
You just saw how to create a provider that handles all interaction with the database in your Ionic 2 Android and iOS application. This is convenient for reducing duplicate code and sharing a consistent service throughout our application pages. While you don’t need to use providers, it makes your code more maintainable in the long run.