Skip to content

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Ionic 2 + LokiJS + LocalForage (Progressive Web App, no-SQL db, and long-term storage)

Learn how to build an Ionic 2 app in this tutorial using a LokiJS database with LocalForage for persistent storage.

Jan 10, 2019 • 9 Minute Read

Summary

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:

  1. a no-SQL database
  2. long-term data persistence
  3. simple, legible code and as few adapters as possible
  4. platform agnostic

The Main Reasons I Opted for LokiJS

LokiJS offers some distinct advantages.

  • simple, familiar JavaScript objects
  • good documentation on lokijs.org
  • in-memory architecture
  • ability to store full DB as a JSON token (awesome for small DBs!)
  • microscopic footprint

My Environment in Ionic 2

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

Generating the Initial Code Base

In your terminal, type the commands

      ionic start LokiDB blank --v2
cd LokiDB
ionic platform add ios
ionic platform add android
npm install lokijs
npm install localforage
    

If your Ionic Framework version is older than beta 9, you'll need add "--ts" to the first command:

      ionic 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.

Adding LokiJS without Persistence

  1. In home.ts, just under the import statements, add

    declare var require: any;
    var loki = require('lokijs');
    
    • Inside the HomePage class, we need to declare 2 objects: one for our database and one for its collection of documents

      	db: any;            // LokiJS database
      robots: any;        // our DB's document collection object
      
      • Let's set up these objects inside the constructor

        	this.db = new loki('robotsOnTV');
        this.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.

          	this.robots.insert({ name: 'Bender', tvShow: 'Futurama' });
          this.robots.insert({ name: 'Rosie', tvShow: 'The Jetsons' });
          this.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:

            	convert2Array(val) {
                return Array.from(val);
            }
            

            This is how your home.ts should look:

            import {Component} from "@angular/core";
            import {NavController} from 'ionic-angular';
            
            declare var require: any;
            var loki = require('lokijs');
            
            @Component({
                templateUrl: 'build/pages/home/home.html'
            })
            
            export class HomePage {
               db: any;                        // LokiJS database
               robots: any;                    // our DB's document collection object
            
                constructor(private navController: NavController) {
                    this.db = new loki('robotsOnTV');
                    this.robots = this.db.addCollection('robots');
            
                    this.robots.insert({ name: 'Bender', tvShow: 'Futurama' });
                    this.robots.insert({ name: 'Rosie', tvShow: 'The Jetsons' });
                    this.robots.insert({ name: 'K1', tvShow: 'Dr. Who' });
                }
            
                convert2Array(val) {
                    return Array.from(val);
                }
            }
            
            • Lastly, let's get the HTML ready. Delete everything inside the <ion-content> tag of home.html, and replace it with this:

              	<!-- list all database elements -->
              <ion-card *ngFor="let robot of convert2Array(robots.data)">
                  <ion-card-header>
                      {{robot.name}}
                  </ion-card-header>
                  <ion-card-content>
                      {{robot.tvShow}}
                  </ion-card-content>
              </ion-card>
              

Adding interactive elements to our LokiJS database

  1. Inside home.ts, add 2 variables for user input. Let's call them robotName and robotTVShow.

    robotName: string;
    robotTVShow: string;
    
    • We'll add in support to insert and delete from the database as well:

      addDocument() {
          if (!this.robotName || !this.robotTVShow) {
              console.log("field is blank...");
              return;
          }
      
          this.robots.insert({ name: this.robotName, tvShow: this.robotTVShow });
      
          // LokiJS is not zero-indexed, so the final element is at <length>, not <length - 1>
          console.log("inserted document: " + this.robots.get(length));
          console.log("robots.data.length: " + this.robots.data.length);
      }
      
      deleteDocument($event, robot) {
          console.log("robot to delete: name = " + robot.name + ", TV show = ", robot.tvShow);
      
          // $loki is the document's index in the collection
          console.log("targeting document at collection index: " + robot.$loki);
          this.robots.remove(robot.$loki);
      }
      
      • Let's add one more card to home.html.

        <!-- add items to LokiJS database -->
        <ion-card>
            <ion-card-content>
                <ion-list>
                    <ion-item>
                        <ion-label floating>Robot Name</ion-label>
                        <ion-input clearInput [(ngModel)]="robotName"></ion-input>
                    </ion-item>
                    <ion-item>
                        <ion-label floating>Which TV Show?</ion-label>
                        <ion-input type="text" [(ngModel)]="robotTVShow"></ion-input>
                    </ion-item>
                </ion-list>
            </ion-card-content>
            <ion-card-content>
                <button (click)="addDocument()">Add</button>
            </ion-card-content>
        </ion-card>
        
        • Finally, we need to allow for document deletion. Let's change the original card so that we have a Delete button:

          <!-- list all database elements -->
          <ion-card *ngFor="let robot of convert2Array(robots.data)">
              <ion-card-header>
                  {{robot.name}}
              </ion-card-header>
              <ion-card-content>
                  {{robot.tvShow}}
                  <button (click)="deleteDocument($event, robot)">Delete</button>
              </ion-card-content>
          </ion-card>
          

Adding LocalForage for Long-term Storage

We're going to allow for saving to file and importing from that file. For more info on how LocalForage prioritizes storage, see https://mozilla.github.io/localForage/

  1. home.ts needs a localForage object. Add this just below your var loki = ... code:

    var 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.

      saveAll() {
          localforage.setItem('storeKey', JSON.stringify(this.db)).then(function (value) {
              console.log('database successfully saved');
          }).catch(function(err) {
              console.log('error while saving: ' + err);
          });
      }
      
      importAll() {
          var self = this;
          localforage.getItem('storeKey').then(function(value) {
              console.log('the full database has been retrieved');
              self.db.loadJSON(value);
              self.robots = self.db.getCollection('robots');        // slight hack! we're manually reconnecting the collection variable
          }).catch(function(err) {
              console.log('error importing database: ' + err);
          });
      }
      
      • In home.html, we're going to hook up the new storage functions to 2 new buttons. Next to our "Add" button, include these:

        <button (click)="saveAll()">Save All</button>
        <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!