Ionic Framework is still one of the leaders in hybrid mobile application development. It allows you to create Android and iOS applications

using only HTML, JavaScript, and CSS.

Previously I wrote about how to use Couchbase in

an Ionic Framework mobile Android and iOS application, but it made use of Couchbase

Lite as its embedded NoSQL database. This time around we’re going to look at replacing Couchbase Lite with PouchDB. Should you use one

method over the other? No, it comes down to preference in the end.

If you haven’t already seen my post regarding PouchDB

and AngularJS with Couchbase, I encourage you to have a look as this tutorial will be using many of the same concepts and code.

What We’ll Need

There are a few requirements to the application we’re going to build. We’ll see how to obtain them along the way, but here is a

taste so you know what you’re getting yourself into.

Couchbase Sync Gateway

PouchDB 4

Ionic Framework 1

Getting the Couchbase Sync Gateway

This project will require the Couchbase Sync Gateway in order to succeed. If you’re unfamiliar, the Couchbase Sync Gateway is a

middleman service that handles processing data between the local application (your Ionic Framework application) and the Couchbase Server. We

won’t be using Couchbase Server in this example so the Sync Gateway will act as our in-memory storage solution in the cloud.

The Couchbase Sync Gateway can be found via the Couchbase downloads section.

Creating Our Ionic Framework Project

Before going any further it is good to note that if you’re not using a Mac, you cannot add and build for the iOS platform. Windows, Mac,

and Linux computers can build for Android, but only Mac can build for iOS.

From the Command Prompt (Windows) or Terminal (Mac and Linux), execute the following command to create a new Ionic Framework project:

ionic start PouchProject blank cd PouchProject ionic platform add android ionic platform add ios 1 2 3 4 5 6 ionic start PouchProject blank cd PouchProject ionic platform add android ionic platform add ios

Our blank template project is now ready for working with.

Including The Dependencies

If you haven’t already, download PouchDB 4 and make note of the min.js file as

we’ll be using it through the project. Copy the

PouchDB min.js file into your Ionic project’s www/js directory.

With the file in place, open your project’s www/index.html file and include the following:

<script src="js/pouchdb-4.0.3.min.js"></script> 1 2 3 <script src = "js/pouchdb-4.0.3.min.js" > </script>

This script line should appear above the app.js include line and the version information should match that of your actual

file rather than the version I included here.

Modifying The Index File

Before we jump into the AngularJS code we need to make a final revision to the project’s www/index.html file. Open it and

replace the tags with the following:

1 2 3 4 5 6 7 8

Because we’re using the AngularJS UI-Router that ships with Ionic Framework, we only need a basic www/index.html file.

Creating Our PouchDB AngularJS Service

Before we start using PouchDB, we need to make a wrapper for it so it fits nicely with AngularJS and Ionic Framework. Out of the box PouchDB

is a vanilla JavaScript library, so it isn’t necessarily the easiest to use when it comes to AngularJS.

Inside your project’s www/js/app.js file, include the following service code:

.service("$pouchDB", ["$rootScope", "$q", function($rootScope, $q) { var database; var changeListener; this.setDatabase = function(databaseName) { database = new PouchDB(databaseName); } this.startListening = function() { changeListener = database.changes({ live: true, include_docs: true }).on("change", function(change) { if(!change.deleted) { $rootScope.$broadcast("$pouchDB:change", change); } else { $rootScope.$broadcast("$pouchDB:delete", change); } }); } this.stopListening = function() { changeListener.cancel(); } this.sync = function(remoteDatabase) { database.sync(remoteDatabase, {live: true, retry: true}); } this.save = function(jsonDocument) { var deferred = $q.defer(); if(!jsonDocument._id) { database.post(jsonDocument).then(function(response) { deferred.resolve(response); }).catch(function(error) { deferred.reject(error); }); } else { database.put(jsonDocument).then(function(response) { deferred.resolve(response); }).catch(function(error) { deferred.reject(error); }); } return deferred.promise; } this.delete = function(documentId, documentRevision) { return database.remove(documentId, documentRevision); } this.get = function(documentId) { return database.get(documentId); } this.destroy = function() { database.destroy(); } }]) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 . service ( "$pouchDB" , [ "$rootScope" , "$q" , function ( $ rootScope , $ q ) { var database ; var changeListener ; this . setDatabase = function ( databaseName ) { database = new PouchDB ( databaseName ) ; } this . startListening = function ( ) { changeListener = database . changes ( { live : true , include_docs : true } ) . on ( "change" , function ( change ) { if ( ! change . deleted ) { $ rootScope . $ broadcast ( "$pouchDB:change" , change ) ; } else { $ rootScope . $ broadcast ( "$pouchDB:delete" , change ) ; } } ) ; } this . stopListening = function ( ) { changeListener . cancel ( ) ; } this . sync = function ( remoteDatabase ) { database . sync ( remoteDatabase , { live : true , retry : true } ) ; } this . save = function ( jsonDocument ) { var deferred = $ q . defer ( ) ; if ( ! jsonDocument . _id ) { database . post ( jsonDocument ) . then ( function ( response ) { deferred . resolve ( response ) ; } ) . catch ( function ( error ) { deferred . reject ( error ) ; } ) ; } else { database . put ( jsonDocument ) . then ( function ( response ) { deferred . resolve ( response ) ; } ) . catch ( function ( error ) { deferred . reject ( error ) ; } ) ; } return deferred . promise ; } this . delete = function ( documentId , documentRevision ) { return database . remove ( documentId , documentRevision ) ; } this . get = function ( documentId ) { return database . get ( documentId ) ; } this . destroy = function ( ) { database . destroy ( ) ; } } ] )

You might be thinking that code looks familiar. Well, it is the exact code I used in

the previous PouchDB example for AngularJS.

Now we can easily use PouchDB in our project.

Creating A Local Database And Start Syncing

The goal here is to create a local database when our application starts (if it doesn’t already exist) and then start syncing with the

Couchbase Sync Gateway. This can be accomplished in the AngularJS run() function of our www/js/app.js

file:

.run(function($ionicPlatform, $pouchDB) { $ionicPlatform.ready(function() { if(window.cordova && window.cordova.plugins.Keyboard) { cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); } if(window.StatusBar) { StatusBar.styleDefault(); } }); $pouchDB.setDatabase("nraboy-test"); if(ionic.Platform.isAndroid()) { $pouchDB.sync("http://192.168.57.1:4984/test-database"); } else { $pouchDB.sync("http://localhost:4984/test-database"); } }) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 . run ( function ( $ ionicPlatform , $ pouchDB ) { $ ionicPlatform . ready ( function ( ) { if ( window . cordova & & window . cordova . plugins . Keyboard ) { cordova . plugins . Keyboard . hideKeyboardAccessoryBar ( true ) ; } if ( window . StatusBar ) { StatusBar . styleDefault ( ) ; } } ) ; $ pouchDB . setDatabase ( "nraboy-test" ) ; if ( ionic . Platform . isAndroid ( ) ) { $ pouchDB . sync ( "http://192.168.57.1:4984/test-database" ) ; } else { $ pouchDB . sync ( "http://localhost:4984/test-database" ) ; } } )

The IP addresses I used might vary for you in terms of simulators, but for production they will likely match for both iOS and

Android.

Designing A Controller For Your Views

We haven’t created our views yet, but let’s go ahead and create the controller logic for them. Open your project’s

www/js/app.js file and include the following controller:

.controller("MainController", function($scope, $rootScope, $state, $stateParams, $ionicHistory, $pouchDB) { $scope.items = {}; $scope.save = function(firstname, lastname, email) { } $scope.delete = function(id, rev) { } $scope.back = function() { } }) 1 2 3 4 5 6 7 8 9 10 11 12 13 . controller ( "MainController" , function ( $ scope , $ rootScope , $ state , $ stateParams , $ ionicHistory , $ pouchDB ) { $ scope . items = { } ; $ scope . save = function ( firstname , lastname , email ) { } $ scope . delete = function ( id , rev ) { } $ scope . back = function ( ) { } } )

As of right now we have a basic controller. We know we’ll be saving and deleting items which is why we’ve defined a function for such

tasks. We also have a function called back() that will pop an item (go back) in the history stack.

Let’s go bottom up and start with the back() function. It should contain the following code:

$scope.back = function() { $ionicHistory.goBack(); } 1 2 3 4 5 $ scope . back = function ( ) { $ ionicHistory . goBack ( ) ; }

When it comes to deleting items from the database we’ll need to provide a particular document id to delete as well as the particular revision

we wish to delete. This will all be passed from the views, but the logic will be as follows:

$scope.delete = function(id, rev) { $pouchDB.delete(id, rev); } 1 2 3 4 5 $ scope . delete = function ( id , rev ) { $ pouchDB . delete ( id , rev ) ; }

The delete(id, rev) function makes a call to the PouchDB service that we made.

This leaves us with the save() function. Based on the simplicity of our application we’ll only be saving three data

properties, but it can easily be changed should you need to. Inside your controller, make the save() function like so:

$scope.save = function(firstname, lastname, email) { var jsonDocument = { "firstname": firstname, "lastname": lastname, "email": email }; if($stateParams.documentId) { jsonDocument["_id"] = $stateParams.documentId; jsonDocument["_rev"] = $stateParams.documentRevision; } $pouchDB.save(jsonDocument).then(function(response) { $state.go("list"); }, function(error) { console.log("ERROR -> " + error); }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ scope . save = function ( firstname , lastname , email ) { var jsonDocument = { "firstname" : firstname , "lastname" : lastname , "email" : email } ; if ( $ stateParams . documentId ) { jsonDocument [ "_id" ] = $ stateParams . documentId ; jsonDocument [ "_rev" ] = $ stateParams . documentRevision ; } $ pouchDB . save ( jsonDocument ) . then ( function ( response ) { $ state . go ( "list" ) ; } , function ( error ) { console . log ( "ERROR -> " + error ) ; } ) ; }

This function does two things. It will prepare an insert or it will prepare an update should a document id and document revision

be available.

We’re not quite done yet though. Although we finished all our functions, we still need to handle listening for

changes. When we call $pouchDB.startListening(); in our controller, our PouchDB service will start making use of the

AngularJS $broadcast. While it is broadcasting we can listen for those broadcasts using something like:

$rootScope.$on("$pouchDB:change", function(event, data) { $scope.items[data.doc._id] = data.doc; $scope.$apply(); }); $rootScope.$on("$pouchDB:delete", function(event, data) { delete $scope.items[data.doc._id]; $scope.$apply(); }); 1 2 3 4 5 6 7 8 9 10 11 $ rootScope . $ on ( "$pouchDB:change" , function ( event , data ) { $ scope . items [ data . doc . _id ] = data . doc ; $ scope . $ apply ( ) ; } ) ; $ rootScope . $ on ( "$pouchDB:delete" , function ( event , data ) { delete $ scope . items [ data . doc . _id ] ; $ scope . $ apply ( ) ; } ) ;

Our view controller logic is now good to go!

Defining Your Ionic Framework Views

The last part of our www/js/app.js file will be for defining our views. This is done in the AngularJS

config() function like so:

.config(function($stateProvider, $urlRouterProvider) { $stateProvider .state("list", { "url": "/list", "templateUrl": "templates/list.html", "controller": "MainController" }) .state("item", { "url": "/item/:documentId/:documentRevision", "templateUrl": "templates/item.html", "controller": "MainController" }); $urlRouterProvider.otherwise("list"); }) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 . config ( function ( $ stateProvider , $ urlRouterProvider ) { $ stateProvider . state ( "list" , { "url" : "/list" , "templateUrl" : "templates/list.html" , "controller" : "MainController" } ) . state ( "item" , { "url" : "/item/:documentId/:documentRevision" , "templateUrl" : "templates/item.html" , "controller" : "MainController" } ) ; $ urlRouterProvider . otherwise ( "list" ) ; } )

We defined two views, one for all our list items and one for creating and updating new list items. The item state takes an optional

document id and document revision parameter. When they are present, it means we are going to be updating a particular document.

All our AngularJS logic is complete now. We have initialized our database, started syncing, defined our views, and planned for interaction

from our views.

Creating A List View

Here we will define how data is presented in the list. In your project’s www/templates/list.html file, add the following code:

<button class="right button button-icon icon ion-plus"></button> {{value.firstname}} {{value.lastname}} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 < button class = "right button button-icon icon ion-plus" > < / button > { { value . firstname } } { { value . lastname } }

When swiping a list item we are presented with a delete button that will call the delete() function of our controller.

Creating A Form View

Here we will define out documents will be inserted or updated in our database. Essentially, this view is only a form. In your project’s

www/templates/item.html file, add the following code:

Save

The Sync Gateway Configuration

PouchDB and Ionic Framework are only half the story here. Sure they will create a nice locally running application, but we want things to sync. The Couchbase Sync Gateway is our endpoint for this and of course PouchDB works great with it.

Inside your project’s sync-gateway-config.json file, add the following:

{ "log":["CRUD+", "REST+", "Changes+", "Attach+"], "databases": { "test-database": { "server":"walrus:data", "sync":` function (doc) { channel (doc.channels); } `, "users": { "GUEST": { "disabled": false, "admin_channels": ["*"] } } } }, "CORS": { "Origin": ["http://localhost:9000"], "LoginOrigin": ["http://localhost:9000"], "Headers": ["Content-Type"], "MaxAge": 17280000 } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 { "log" : [ "CRUD+" , "REST+" , "Changes+" , "Attach+" ] , "databases" : { "test-database" : { "server" : "walrus:data" , "sync" : ` function ( doc ) { channel ( doc . channels ) ; } ` , "users" : { "GUEST" : { "disabled" : false , "admin_channels" : [ "*" ] } } } } , "CORS" : { "Origin" : [ "http://localhost:9000" ] , "LoginOrigin" : [ "http://localhost:9000" ] , "Headers" : [ "Content-Type" ] , "MaxAge" : 17280000 } }

This is one of the most basic configurations around. A few things to note about it:

It uses walrus:data for storage which is in memory and does not persist. Not to be used for production.

All data is synced via the GUEST user, so there is no authentication happening here, but there could be.

We are fixing CORS issues by allowing requests on localhost:9000 in case you wanted to serve with Python for browser testing.

Testing The Application

At this point all our code is in place and our Sync Gateway is ready to be run. Start up the Sync Gateway by running the following in a

Command Prompt or Terminal:

/path/to/sync/gateway/bin/sync-gateway /path/to/project/sync-gateway-config.json 1 2 3 / path / to / sync / gateway / bin / sync - gateway / path / to / project / sync - gateway - config . json

The Sync Gateway should now be running and you can validate this by visiting http://localhost:4984 in your web browser.

Testing For Android

With a device connected or a simulator running, from the Command Prompt or Terminal, run the following two commands to build and

install the APK file:

ionic build android adb install -r platforms/android/build/outputs/apk/android-debug.apk 1 2 3 4 ionic build android adb install - r platforms / android / build / outputs / apk / android - debug . apk

Testing For iOS

There are two good ways to do this. You can either build the project and open it with Xcode, or you can build and

emulate the application without launching Xcode. The first can be done like so:

ionic build ios 1 2 3 ionic build ios

Then open the project’s platform/ios/ directory and launch the Xcode project file.

If you’ve installed the Node Package Manager (NPM) package ios-sim, you can do the following:

ionic build ios ionic emulate ios 1 2 3 4 ionic build ios ionic emulate ios

Conclusion

You saw now that there are two ways you can use Couchbase in your Ionic Framework mobile Android and iOS application. You can use the

Apache Cordova Couchbase plugin as demonstrated in

the previous blog series, or you can

use PouchDB. Both of these are very suitable options when it comes to cross platform data storage and sync in your application.

You can obtain the full working source code to this blog post via

our Couchbase Labs GitHub repository.