Sync Data Using PouchDB In Your Ionic Framework App

December 30, 2014By 10 Comments

Syncing data with the cloud is all the hype lately.  Dropbox, Firebase, Parse, Couchbase, everyone is working towards having the easiest solution.  There are many others, but those are just a few off the top of my head.

One of my subscribers recently asked if I knew how to get PouchDB working with an Apache Cordova mobile application using Ionic Framework in particular.  I didn’t immediately know what PouchDB was, but after some reading I found it was inspired by Apache CouchDB.

In this guide, we’re going to see how to install CouchDB and use PouchDB in an Ionic Framework mobile application for cross platform synchronization.

Since PouchDB is built upon Apache CouchDB, this guide will have two parts:

  1. Setting up a CouchDB server using Debian Linux
  2. Using PouchDB in a mobile application

When everything has been completed, you’ll have a simple application that can sync between devices and platforms.

 

Setting up a CouchDB server using Debian Linux

Per the PouchDB and CouchDB documentation, most of what we need is already available on Debian and Ubuntu based Linux distributions.  For this example, I am using a fresh Digital Ocean VPS with Debian 7 installed.  While using SSH with root access to your server, run the following to install CouchDB:

apt-get install couchdb

You’re going to have some Cross Origin Resource Sharing (CORS) errors out of the box so we need to install a special package.  However, this package requires NPM to have already been installed.  If it is not already installed you can run the following:

curl -sL https://deb.nodesource.com/setup | bash -
apt-get install -y nodejs

With NodeJS installed, run the following to install and run our CORS package:

npm install -g add-cors-to-couchdb
add-cors-to-couchdb

We’re not completely finished yet.  Currently only connections via localhost can be made to CouchDB.  For this example we’re going to open CouchDB to anyone.  You’re probably going to want to tweak this in the future.  As a root user, edit /etc/couchdb/local.ini and uncomment the bind_address line.  Also change 127.0.0.1 to 0.0.0.0.  Now everyone can access the database.

Again, it is important to use a setup that meets your needs in a production environment.

You can confirm everything was set up correctly by entering http://serverip:5984/_utils into your web browser, exchanging serverip with the correct remote IP address.

 

Using PouchDB in a mobile application

The first thing we want to do is create a fresh Ionic Android and iOS project to work with:

ionic start PouchApp blank
cd PouchApp
ionic platform add android
ionic platform add ios

Note, if you’re not using a Mac, you cannot add and build for the iOS platform.

The next thing we want to do is download the PouchDB JavaScript library and place it in the www/js directory of our Ionic project.  Once the file is in place, we can continue to adding it into our www/index.html file:

<script src="js/pouchdb-3.2.0.min.js"></script>

Now I did some Google searches and found that there is an AngularJS library for PouchDB, but I found the documentation to be very poor.  Because of this I’ve decided to use the official JavaScript library and make my own wrappers.

For simplicity, this application is going to be a to-do-list type application.  It will have one screen with a list, and users can choose to add to the list.  Open your www/index.html file and add the following between the <body> tags.

<ion-pane ng-controller="ExampleController">
    <ion-header-bar class="bar-stable">
        <h1 class="title">Ionic Blank Starter</h1>
        <button class="right button button-icon icon ion-plus" ng-click="create()"></button>
    </ion-header-bar>
    <ion-content>
        <ion-list>
            <ion-item ng-repeat="todo in todos">
                {{todo.title}}
            </ion-item>
        </ion-list>
    </ion-content>
</ion-pane>

Based on this UI, you can conclude that we will have one controller called ExampleController, an add button that calls create() when clicked, and a list of todo items.

Open www/js/app.js and make sure your angular.module is set to a variable name like the following:

var example = angular.module('starter', ['ionic']);

Right below this line, we need to declare our local and remote databases.  Later in our code we will be synchronizing them, but it doesn’t remove the need to declare them.  This can be done like so:

var localDB = new PouchDB("todos");
var remoteDB = new PouchDB("http://serverip:5984/todos");

Remember to swap out serverip with the IP of your server.

Inside the $ionicPlatform.ready of your run() method we need to start synchronizing.  This can be done by adding the following:

localDB.sync(remoteDB, {live: true});

The above line means that we’re constantly going to be listening for changes between databases and synchronizing them as they occur.

So how are we going to watch for data changes in our databases and refresh our UI list?  If you’re familiar with AngularJS, you can’t just watch a global JavaScript variable.  Some extra magic has to happen.  We will be making use of $rootScope.$broadcast to signal every time we’ve received some kind of data change.  Create the following factory in your code:

example.factory('PouchDBListener', ['$rootScope', function($rootScope) {

    localDB.changes({
        continuous: true,
        onChange: function(change) {
            if (!change.deleted) {
                $rootScope.$apply(function() {
                    localDB.get(change.id, function(err, doc) {
                        $rootScope.$apply(function() {
                            if (err) console.log(err);
                            $rootScope.$broadcast('add', doc);
                        })
                    });
                })
            } else {
                $rootScope.$apply(function() {
                    $rootScope.$broadcast('delete', change.id);
                });
            }
        }
    });

    return true;
    
}]);

The above code says the local database will look for changes.  If the change is not a delete, it is an add, so fire the add broadcast with the change data.  If the change is a delete, then fire the delete broadcast with the ID of the record.

Time to create our ExampleController which will look for changes and add new changes via an $ionicPopup prompt.  Add the following code to your www/js/app.js file:

example.controller("ExampleController", function($scope, $ionicPopup, PouchDBListener) {

    $scope.todos = [];

    $scope.create = function() {
        $ionicPopup.prompt({
            title: 'Enter a new TODO item',
            inputType: 'text'
        })
        .then(function(result) {
            if(result !== "") {
                if($scope.hasOwnProperty("todos") !== true) {
                    $scope.todos = [];
                }
                localDB.post({title: result});
            } else {
                console.log("Action not completed");
            }
        });
    }

    $scope.$on('add', function(event, todo) {
        $scope.todos.push(todo);
    });

    $scope.$on('delete', function(event, id) {
        for(var i = 0; i < $scope.todos.length; i++) {
            if($scope.todos[i]._id === id) {
                $scope.todos.splice(i, 1);
            }
        }
    });

});

If a delete is found, remove it from the list.  If an addition was found, add it to the list.  Creating a new item via the application will trigger the addition.

The full www/js/app.js code can be seen below:

var example = angular.module('starter', ['ionic']);
var localDB = new PouchDB("todos");
var remoteDB = new PouchDB("http://serverip:5984/todos");

example.run(function($ionicPlatform) {
    $ionicPlatform.ready(function() {
        if(window.cordova &amp;&amp; window.cordova.plugins.Keyboard) {
            cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
        }
        if(window.StatusBar) {
            StatusBar.styleDefault();
        }
        localDB.sync(remoteDB, {live: true});
    });
});

example.controller("ExampleController", function($scope, $ionicPopup, PouchDBListener) {

    $scope.todos = [];

    $scope.create = function() {
        $ionicPopup.prompt({
            title: 'Enter a new TODO item',
            inputType: 'text'
        })
        .then(function(result) {
            if(result !== "") {
                if($scope.hasOwnProperty("todos") !== true) {
                    $scope.todos = [];
                }
                localDB.post({title: result});
            } else {
                console.log("Action not completed");
            }
        });
    }

    $scope.$on('add', function(event, todo) {
        $scope.todos.push(todo);
    });

    $scope.$on('delete', function(event, id) {
        for(var i = 0; i < $scope.todos.length; i++) {
            if($scope.todos[i]._id === id) {
                $scope.todos.splice(i, 1);
            }
        }
    });

});

example.factory('PouchDBListener', ['$rootScope', function($rootScope) {

    localDB.changes({
        continuous: true,
        onChange: function(change) {
            if (!change.deleted) {
                $rootScope.$apply(function() {
                    localDB.get(change.id, function(err, doc) {
                        $rootScope.$apply(function() {
                            if (err) console.log(err);
                            $rootScope.$broadcast('add', doc);
                        })
                    });
                })
            } else {
                $rootScope.$apply(function() {
                    $rootScope.$broadcast('delete', change.id);
                });
            }
        }
    });

    return true;

}]);

A video version of this article can be seen below.

Filed in: AndroidAngularJSCordovaioniciOSMobile Development Tags:

About the Author ()

Skilled application developer with experience in Android, SQL, NodeJS, AngularJS, Ionic Framework, Java, and Unity3D. See other useful posts on https://www.thepolyglotdeveloper.com

Comments (10)

Trackback URL | Comments RSS Feed

  1. jimibi says:

    Hi,

    Very interesting Post, I test the App in my phone and in Android emulator and it’ ok (The file are synchro) but in Browser not.
    It’s normal?
    Happy new Year

  2. Really great article; I’ve actually been wanting to play around with PouchDBCouchDB synchronization in a PhoneGap app for awhile now, so it’s good to see that it’s relatively straight forward.

    I wanted to mention real quick, though, that you’re referencing Couchbase in a few places where I think you probably meant CouchDB. I.e., the “Setting up a Couchbase server […]” section is really covering CouchDB installation. The projects of course have the same roots, but are far from being interchangeable at this point.

  3. pablo says:

    exactly what i needed to know thank you so much!

  4. Chris says:

    How are the remote databases constrained to this particular user? Does that happen automatically when using Pouch DB?

  5. Duncan says:

    Nice article!

    I thought the whole point of PouchDB is that you don’t need to carefully track and broadcast changes as they happen. Instead, you can regularly check to see if the user is online and run a syncdb between local and remote. Couch and Pouch does all the rest.

    Yes?

  6. Steven Schmidt says:

    Thanks for the great post. I have one problem I cannot resolve, my project works fine in the ADB emulator and syncs with my DigitalOcean spun CouchDB. But, I cannot successfully sync when using Chrome via ‘ionic serve’ or my Genymotion Emulator…with the console below…

    ‘XMLHttpRequest cannot load http://xx.xx.xx.xx:5984/todos/?_nonce=1424287424095. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8100′ is therefore not allowed access. The response had HTTP status code 405.’

    is there a simple step I’m missing. MY Adb Emulator on windows machine is soooo slow, hoping to resolve and use Genymotion or ionic serve for debugging.

  7. Hey thats awesome. I was looking for exactly the same :)

Leave a Reply