In this guide, we'll build an Ionic 2 app using a LokiJS database with LocalForage for persistent storage. My app won't be your ordinary database-related app, though. Here are my app requirements:
LokiJS offers some distinct advantages.
Ionic 2 is growing and maturing quickly. Here's the environment I used to create this tutorial.
Cordova CLI: 6.1.1
Ionic Framework Version: 2.0.0-beta.9
Ionic CLI Version: 2.0.0-beta.25
Ionic App Lib Version: 2.0.0-beta.15
ios-deploy version: 1.8.6
ios-sim version: 5.0.8
OS: Mac OS X El Capitan
Node Version: v5.7.1
Xcode version: Xcode 7.3 Build version 7D175
In your terminal, type the commands
1ionic start LokiDB blank --v2
2cd LokiDB
3ionic platform add ios
4ionic platform add android
5npm install lokijs
6npm install localforage
If your Ionic Framework version is older than beta 9, you'll need add "--ts" to the first command:
1ionic start LokiDB blank --v2 --ts
These commands will build our skeleton app. All of our hacking will take place in app > pages > home > home.html and home.ts.
In home.ts, just under the import
statements, add
1declare var require: any;
2var loki = require('lokijs');
Inside the HomePage class, we need to declare 2 objects: one for our database and one for its collection of documents
1db: any; // LokiJS database
2robots: any; // our DB's document collection object
Let's set up these objects inside the constructor
1this.db = new loki('robotsOnTV');
2this.robots = this.db.addCollection('robots');
Next, we'll insert a few documents (for those who aren't used to no-SQL databases, a document is just an object held by the database). We're using JSON-style insertion because LokiJS receives the data as JSON. Don't worry about creating TypeScript interfaces because they will only increase the amount of code we need to write.
1this.robots.insert({ name: 'Bender', tvShow: 'Futurama' });
2this.robots.insert({ name: 'Rosie', tvShow: 'The Jetsons' });
3this.robots.insert({ name: 'K1', tvShow: 'Dr. Who' });
The final thing to do in the TS file is to add a helper function. We want the HTML file to display these results, but *ngFor
will not iterate over custom data types. As a result, we're going to write a simple, generic object-to-Array function:
1convert2Array(val) {
2 return Array.from(val);
3}
This is how your home.ts should look:
1import {Component} from "@angular/core";
2import {NavController} from 'ionic-angular';
3
4declare var require: any;
5var loki = require('lokijs');
6
7@Component({
8 templateUrl: 'build/pages/home/home.html'
9})
10
11export class HomePage {
12 db: any; // LokiJS database
13 robots: any; // our DB's document collection object
14
15 constructor(private navController: NavController) {
16 this.db = new loki('robotsOnTV');
17 this.robots = this.db.addCollection('robots');
18
19 this.robots.insert({ name: 'Bender', tvShow: 'Futurama' });
20 this.robots.insert({ name: 'Rosie', tvShow: 'The Jetsons' });
21 this.robots.insert({ name: 'K1', tvShow: 'Dr. Who' });
22 }
23
24 convert2Array(val) {
25 return Array.from(val);
26 }
27}
Lastly, let's get the HTML ready. Delete everything inside the <ion-content>
tag of home.html, and replace it with this:
1<!-- list all database elements -->
2<ion-card *ngFor="let robot of convert2Array(robots.data)">
3 <ion-card-header>
4 {{robot.name}}
5 </ion-card-header>
6 <ion-card-content>
7 {{robot.tvShow}}
8 </ion-card-content>
9</ion-card>
Inside home.ts, add 2 variables for user input. Let's call them robotName
and robotTVShow
.
1robotName: string;
2robotTVShow: string;
We'll add in support to insert and delete from the database as well:
1addDocument() {
2 if (!this.robotName || !this.robotTVShow) {
3 console.log("field is blank...");
4 return;
5 }
6
7 this.robots.insert({ name: this.robotName, tvShow: this.robotTVShow });
8
9 // LokiJS is not zero-indexed, so the final element is at <length>, not <length - 1>
10 console.log("inserted document: " + this.robots.get(length));
11 console.log("robots.data.length: " + this.robots.data.length);
12}
13
14deleteDocument($event, robot) {
15 console.log("robot to delete: name = " + robot.name + ", TV show = ", robot.tvShow);
16
17 // $loki is the document's index in the collection
18 console.log("targeting document at collection index: " + robot.$loki);
19 this.robots.remove(robot.$loki);
20}
Let's add one more card to home.html.
1<!-- add items to LokiJS database -->
2<ion-card>
3 <ion-card-content>
4 <ion-list>
5 <ion-item>
6 <ion-label floating>Robot Name</ion-label>
7 <ion-input clearInput [(ngModel)]="robotName"></ion-input>
8 </ion-item>
9 <ion-item>
10 <ion-label floating>Which TV Show?</ion-label>
11 <ion-input type="text" [(ngModel)]="robotTVShow"></ion-input>
12 </ion-item>
13 </ion-list>
14 </ion-card-content>
15 <ion-card-content>
16 <button (click)="addDocument()">Add</button>
17 </ion-card-content>
18</ion-card>
Finally, we need to allow for document deletion. Let's change the original card so that we have a Delete button:
1<!-- list all database elements -->
2<ion-card *ngFor="let robot of convert2Array(robots.data)">
3 <ion-card-header>
4 {{robot.name}}
5 </ion-card-header>
6 <ion-card-content>
7 {{robot.tvShow}}
8 <button (click)="deleteDocument($event, robot)">Delete</button>
9 </ion-card-content>
10</ion-card>
We're going to allow for saving to file and importing from that file. For more info on how LocalForage prioritizes storage, see http://mozilla.github.io/localForage/
home.ts needs a localForage object. Add this just below your var loki = ...
code:
1var localforage = require('localforage');
Add in functions for saving the database and retrieving it. LocalForage uses key-value maps, and since we're only interested in saving a single value (the entire database), we'll hard-code our key as storeKey
.
1saveAll() {
2 localforage.setItem('storeKey', JSON.stringify(this.db)).then(function (value) {
3 console.log('database successfully saved');
4 }).catch(function(err) {
5 console.log('error while saving: ' + err);
6 });
7}
8
9importAll() {
10 var self = this;
11 localforage.getItem('storeKey').then(function(value) {
12 console.log('the full database has been retrieved');
13 self.db.loadJSON(value);
14 self.robots = self.db.getCollection('robots'); // slight hack! we're manually reconnecting the collection variable
15 }).catch(function(err) {
16 console.log('error importing database: ' + err);
17 });
18}
1<button (click)="saveAll()">Save All</button>
2<button (click)="importAll()">Import All</button>
That's all it takes to build a persistent, no-SQL database in Ionic 2!
I hope you found this tutorial informative and enjoyable. Thank you for reading!