How To Use LokiJS For Local Storage In Your Ionic App

26 September 2015Ionic, Cordova, PouchDB, SQLite, LokiJS, Local Storage

A few months ago I wrote a tutorial on how to use PouchDB for local storage in Ionic apps.

I've recently come across another library called LokiJS that promises fast performance because it uses an in-memory database.

In this tutorial we'll take the same app I used in the PouchDB tutorial and build it with LokiJS instead.

###What is the difference between LokiJS and PouchDB? LokiJS creates an in-memory database and allows you to query it efficiently using an API that is similar to MongoDB. You can persist the database with IndexedDB, as a JSON file or write your own adapter to store it.

One of the reasons LokiJS was created, was to offer an alternative to SQLite for Cordova apps.

LokiJS doesn't have advanced synchronization capabilities, but there is a Changes API, which keeps track of all the changes made to the local database. You can use this API to synchronize your database.

PouchDB also allows you to create a database locally and has several adapters for persisting the data, using WebSQL, IndexedDB and SQLite, but the most interesting feature is that it supports seamless synchronization with a CouchDB database.

###Which one should I use? We will create an app that only stores data locally, so we don't need the synchronization capabilities of PouchDB.

So the choice here is simple: LokiJS, because it offers faster performance since all the queries on the database are executed in-memory.

Read this article for a more detailed comparison of PouchDB and LokiJS.

###Installation We can use Bower to install LokiJS into our Ionic project.

$ bower install lokijs --save

Next, let's reference the LokiJS libraries in index.html. LokiJS has support for Angular, so we will include the JavaScript file for that as well.

<script src="lib/lokijs/src/lokijs.js"></script>
<script src="lib/lokijs/src/loki-angular.js"></script>

LokiJS automatically persists the data to localStorage, but I wouldn't trust that, because localStorage can be cleared when memory is low.

If we have a look at the adapters that come with LokiJS, the other option would be IndexedDB, but unfortunately that doesn't work on iOS devices. That's because Cordova uses UIWebView which doesn't support IndexedDB.

The other option would be to save the data as a JSON file, but at the moment LokiJS doesn't have an adapter that can save files in Cordova apps.

We can download a unofficial adapter for it though: loki-cordova-fs-adapter. It's written by Corentin Smith in ES6, but you can download the transpiled verion here, so let's reference that in index.html.

<script src="js/loki-cordova-fs-adapter.js"></script>

This adapter depends on the Cordova File plugin, so let's install that as well.

$ cordova plugin add cordova-plugin-file

Note: You can also create your own adapter to persist the data whichever way you want to.

Now that we have installed all the neccessary libraries, let's inject the lokijs module into our app module, in app.js.

angular.module('starter', ['ionic', 'lokijs'])

###What are we going to build? Our app is going to be a birthday registration app that will have add, update, delete and read functionality.

Screenshot Birthday App

###Create database service Let's start by creating a service to encapsulate our LokiJS calls.

angular.module('starter').factory('BirthdayService', ['$q', 'Loki', BirthdayService]);

function BirthdayService($q, Loki) {
    var _db;
    var _birthdays;

    function initDB() {
        var adapter = new LokiCordovaFSAdapter({"prefix": "loki"});
        _db = new Loki('birthdaysDB',
                {
                    autosave: true,
                    autosaveInterval: 1000, // 1 second
                    adapter: adapter
                });
    };

    return {
        initDB: initDB,
        getAllBirthdays: getAllBirthdays,
        addBirthday: addBirthday,
        updateBirthday: updateBirthday,
        deleteBirthday: deleteBirthday
    };
}

There are different ways to initialize a LokiJS database, in this example we are going to configure it to autosave every second to a JSON file.

The autosave function is smart enough to know that it only needs to save if the data in the in-memory database has changed.

###Get all birthdays Next, let's implement the getAllBirthdays function.

function getAllBirthdays() {

    return $q(function (resolve, reject) {
        var options = {};

        _db.loadDatabase(options, function () {
            _birthdays = _db.getCollection('birthdays');

            if (!_birthdays) {
                _birthdays = _db.addCollection('birthdays');
            }

            resolve(_birthdays.data);
        });
    });
};

We have to load the database first with the loadDatabase function. This function takes a callback so that we can access the collections when it's done loading.

As you can see there is an options object passed in as the first parameter of loadDatabase. You can leave this object empty if you're happy with the way the data is deserialized from JSON.

However, in our case the dates in JSON will not be automatically converted back to Date objects, so we'll have to do that ourselves by defining an inflate function.

var options = {
    birthdays: {
        proto: Object,
        inflate: function (src, dst) {
            var prop;
            for (prop in src) {
                if (prop === 'Date') {
                    dst.Date = new Date(src.Date);
                } else {
                    dst[prop] = src[prop];
                }
            }
        }
    }
};

We're basically copying all properties from the source to the destination object, except the value of the Date property will be converted to a Date object.

Note: You can also set autoload when initializing the database, but then you lose the option to specify an inflate method.

####Add/Update/Delete a birthday All we have left to do now are the add/update/delete functions. As you can see, these are very simple.

function addBirthday(birthday) {
    _birthdays.insert(birthday);
};

function updateBirthday(birthday) {
    _birthdays.update(birthday);
};

function deleteBirthday(birthday) {
    _birthdays.remove(birthday);
};

###Let's build the UI OK, so we have the service set up which does most of the heavy work, let's have a look at the UI.

We'll add an OverviewController, this calls the birthdayService.initDB function, but we have to wait for the $ionicPlatform.ready event to make sure the device is ready.

angular.module('starter').controller('OverviewController', ['$scope', '$ionicModal', '$ionicPlatform', 'BirthdayService', OverviewController]);

function OverviewController($scope, $ionicModal, $ionicPlatform, birthdayService) {
	var vm = this;

    $ionicPlatform.ready(function() {

        // Initialize the database.
        birthdayService.initDB();

        // Get all birthday records from the database.
        birthdayService.getAllBirthdays()
                        .then(function (birthdays) {
                            vm.birthdays = birthdays;
                        });
    });

	// Initialize the modal view.
	$ionicModal.fromTemplateUrl('add-or-edit-birthday.html', {
		scope: $scope,
		animation: 'slide-in-up'
	}).then(function(modal) {
		$scope.modal = modal;
	});

	vm.showAddBirthdayModal = function() {
		$scope.birthday = {};
		$scope.action = 'Add';
		$scope.isAdd = true;
		$scope.modal.show();
	};

	vm.showEditBirthdayModal = function(birthday) {
		$scope.birthday = birthday;
		$scope.action = 'Edit';
		$scope.isAdd = false;
		$scope.modal.show();
	};

	$scope.saveBirthday = function() {
		if ($scope.isAdd) {
			birthdayService.addBirthday($scope.birthday);
		} else {
			birthdayService.updateBirthday($scope.birthday);
		}
		$scope.modal.hide();
	};

	$scope.deleteBirthday = function() {
		birthdayService.deleteBirthday($scope.birthday);
		$scope.modal.hide();
	};

	$scope.$on('$destroy', function() {
		$scope.modal.remove();
	});

	return vm;
}

And this is the code in index.html, we're using a modal dialog to display the Add Birthday and Edit Birthday views.

<body ng-app="starter">
  <ion-pane ng-controller="OverviewController as vm">
    <ion-header-bar class="bar-stable">
      <h1 class="title">🎂  Birthdays  🎉</h1>
      <div class="buttons">
        <button ng-click="vm.showAddBirthdayModal()" class="button button-icon icon ion-plus"></button>
      </div>
    </ion-header-bar>
    <ion-content>
      <ion-list>
        <ion-item ng-repeat="b in vm.birthdays" ng-click="vm.showEditBirthdayModal(b)">
          <div style="float: left">{{ b.Name }}</div>
          <div style="float: right">{{ b.Date | date:"dd MMMM yyyy" }}</div>
        </ion-item>
      </ion-list>
    </ion-content>
  </ion-pane>

  <script id="add-or-edit-birthday.html" type="text/ng-template">
    <ion-modal-view>
      <ion-header-bar>
        <h1 class="title">{{ action }} Birthday</h1>
        <div class="buttons">
        <button ng-hide="isAdd" ng-click="deleteBirthday()" class="button button-icon icon ion-trash-a"></button>
        </div>
      </ion-header-bar>
      <ion-content>
        <div class="list list-inset">
          <label class="item item-input">
          <input type="text" placeholder="Name" ng-model="birthday.Name">
          </label>
          <label class="item item-input">
          <input type="date" placeholder="Birthday" ng-model="birthday.Date">
          </label>
        </div>
        <div class="padding">
          <button ng-click="saveBirthday()" class="button button-block button-positive activated">Save</button>
        </div>
      </ion-content>
    </ion-modal-view>
  </script>
</body>

###Final Thoughts As you can see it's very easy to use LokiJS for local storage. The only part I struggled with was figuring out which adapter to use for Ionic apps. Saving the data as a JSON file seems to work fine, I've tested it on both iOS and Android devices.

In this example we haven't explored the advanced querying capabilities of LokiJS, so I encourage you to have a look at the documentation and the links below.

###More Info LokiJS, the idiomatic way
How to query a CSV file with Javascript and LokiJS

WRITTEN BY
profile
Ashteya Biharisingh

Full stack developer who likes to build mobile apps and read books.