Synchronizing Offline App Data with PouchDB
Applications which can work online and offline are an advantage to mobile users. For example, someone traveling by air generally has no internet connection. With a well designed app they can still work in offline mode and synchronize the offline data with an application server.
How Does This Work?
When the application is offline, the data is stored using HTML 5 local storage and session storage objects to store data in the browser or WebView. When the application is online, its programming logic will synchronize changes to a central database.
PouchDb
PouchDb is an open source JavaScript database inspired by CouchDb which helps developers build applications that work in offline and online modes.
What We’;Ll Build
We’ll be using the IONIC framework to create an expense logger mobile application making use of PouchDB for the back end.
The source code for this tutorial is available on GitHub.
Getting Started
Start by installing IONIC using the node package manager (npm).
npm install -g cordova ionic
Once IONIC has installed, create a blank project called iPouch
.
ionic start iPouch blank
Navigate to the project directory and install the required dependencies.
cd iPouch
npm install
Run the following command in the terminal to start the app in the web browser
ionic serve
Download and add the PouchDB JavaScript to the iPouch/www/js folder.
Add a reference to the pouchdb-4.0.x.min.js
script in the iPouchDb/www/index.html page.
<script src="js/pouchdb-4.0.0.min.js"></script>
(replacing x
with the current version number)
Setting up CouchDb
Use of your platforms package manager of choice to install CouchDb.
sudo apt-get install couchdb // For Ubuntu
brew install couchdb // For MAC
And start couchdb with the couchdb
command.
To allow cross domain requests from PouchDb to CouchDb we need to install add-cors-to-couchdb
.
npm install -g add-cors-to-couchdb
Once installed enable the CORS by running the following command:
add-cors-to-couchdb
Couch Db should be running at port 5984. Try browsing http://localhost:5984/ .
Adding the PouchDb Service
Let’s start by creating a controller for our application. Add a file called controller.js to the iPouch/js folder. Add a reference to the controller in index.html:
<script type="text/javascript" src="js/controller.js"></script>
Inside controller.js define a module called starter.controllers
.
angular.module('starter.controllers', ['ionic'])
Define a controller called HomeCtrl
.
.controller('HomeCtrl', ['$scope', function($scope) {
}])
Create an AngularJS factory service which will return the PouchDb object.
.factory('pouchdb', function() {
return new PouchDB('myApp');
});
Inject the pouchdb
service into HomeCtrl
and create a new pouch database for both the local pouch db and the remote couch db.
.controller('HomeCtrl', ['$scope','pouchdb', function($scope,pouchdb) {
var dbLocal = new PouchDB('dbname');
var dbRemote = new PouchDB('http://localhost:5984/dbname');
}])
Here is the complete controller.js file so far.
angular.module('starter.controllers', ['ionic'])
.controller('HomeCtrl', ['$scope','pouchdb', function($scope,pouchdb) {
var dbLocal = new PouchDB('dbname');
var dbRemote = new PouchDB('http://localhost:5984/dbname');
}])
.factory('pouchdb', function() {
return new PouchDB('myApp');
});
Add the starter.controllers module to application by injecting the module in app.js.
angular.module('starter', ['ionic','starter.controllers'])
Add Expense Tracking Functionality
Let’s start creating a user interface for adding expense items and amounts. Open www/index.html and change the existing body content as shown below :
<body ng-app="starter" ng-controller="HomeCtrl">
<ion-pane>
<ion-header-bar class="bar-stable">
<h1 class="title">Expense Checker</h1>
<button class="button" ng-click="openModal()">Add</button>
</ion-header-bar>
</ion-pane>
</body>
Here we created a home page with the header button on the right side of the screen. We added a click event on the button which will open a modal window. Save the changes and you should be able to view the home screen.
We’ll use IONIC modals to show the modal pop up. Add the following template code for a modal pop up to the index.html page after the ion-pane
ending tag.
<script id="my-modal.html" type="text/ng-template">
<ion-modal-view>
<ion-header-bar>
<h1 class="title">Add Expense</h1>
</ion-header-bar>
<ion-content>
<div class="list">
<label class="item item-input">
<input type="text" ng-model="item.expense" placeholder="Expense">
</label>
<label class="item item-input">
<input type="number" ng-model="item.amount" placeholder="Amount">
</label>
<button ng-click="add()" class="button button-full button-positive">
Add Expense
</button>
</div>
</ion-content>
</ion-modal-view>
</script>
To show the modal, inject $ionicModal
into the controller.
.controller('HomeCtrl',['$scope','pouchdb','$ionicModal',function($scope,pouchdb,$ionicModal)
Initialize the modal pop inside the HomeCtrl
controller.
$ionicModal.fromTemplateUrl('my-modal.html', {
scope: $scope,
animation: 'slide-in-up'
}).then(function(modal) {
$scope.modal = modal;
});
Add two functions inside the HomeCtrl
to show and close the modal pop up.
$scope.openModal = function() {
$scope.modal.show();
};
$scope.closeModal = function() {
$scope.modal.hide();
};
$scope.$on('$destroy', function() {
$scope.modal.remove();
});
In the above code I have an extra destroy
event call to clean up the modal pop up. Here is how the pop up looks:
Define the add
function to add the data to pouchdb.
$scope.add = function(){
// Code would be here !!
};
We’ll make use of the db.put() API to create a new document in pouchdb. Start by creating an item object to save.
var timeStamp = String(new Date().getTime());
var item = {
"_id": timeStamp,
"expense": $scope.item.expense,
"amount": $scope.item.amount
};
Once we have the item object, we’ll post it to pouchdb. On success we’ll add the item to the items
array which will later populate the display.
dbLocal.put(
item
).then(function (response) {
$scope.items.push(item); // Add to items array
$scope.closeModal(); // Close the modal
}).catch(function (err) {
console.log(err);
});
Once the data is added we’ll use the db.allDocs API to fetch the documents added to pouchdb. Inside the Home controller add the following code to get all documents from pouchdb.
dbLocal.allDocs({
include_docs: true
}).then(function(result) {
console.log(result)
}).catch(function(err) {
console.log(err);
});
We’ll iterate through the results and push item objects into an items array. Define an items array in the Home controller.
$scope.item = {};
$scope.items = [];
Iterate through the results received, creating and inserting an item object into the array list. Update dbLocal.allDocs
to the following:
dbLocal.allDocs({
include_docs: true
}).then(function (result) {
console.log('re var dbLocal = new s is',result.rows);
for(var i=0;i<result.rows.length;i++){
var obj = {
"_id": result.rows[i].doc.id,
"expense": result.rows[i].doc.expense,
"amount": result.rows[i].doc.amount
}
$scope.items.push(obj);
$scope.$apply();
}
console.log($scope.items);
}).catch(function (err) {
console.log(err);
});
$scope.$apply()
is to update the bindings. To display the content of items
we’ll use the AngularJS ngRepeat
directive. In index.html
add the following code to display ul
element.
<ion-view title="iPouch">
<ion-content>
<ul class="list">
<li ng-repeat="i in items" class="item">
Amount spent on {{i.expense}} is {{i.amount}}
</li>
</ul>
</ion-content>
</ion-view>
Save the above changes and try adding a new expense using the application. Once added you should be able to view the data on the screen as shown:
Sync Data When Offline
As you would have noticed we have defined two database in the Home controller, dbLocal
and dbRemote
. We save the data in the dbLocal
database which is the pouchdb database. Once we have the data in the local database we can replicate the data to a remote server. This way when the application is offline the data is saved in the local database and when online, replicated to the remote server.
We’ll make use of the replicate API to replicate data to the remote when online. Add the following line of code to replicate data to remote.
dbLocal.replicate.to(dbRemote,{live:true},function(err){
console.log(err);
});
We have specified the live : true
option to allow live replication. Save the above changes and add a new item. Items replicated to the remote couch database are visible using the remote url in browser.
http://localhost:5984/dbname
Conclusion
In this tutorial, we learned how to create an IONIC application with a pouchdb back end. We saw how to store data locally when offline and replicate it to the server when online.
Read the pouchdb documentation to get a detailed insight about more of the features available to your application.
Let me know your thoughts, suggestions or corrections in the comments below.