PouchDB & CouchDB

Offline Syncing in Ionic 2 with PouchDB & CouchDB



·

Since I started travelling around Australia I’ve tried to keep things pretty minimal. I got rid of all the clothes I didn’t need, shoes, kitchen ware, furniture, cleaning products and so on. Basically, I tried to get rid of everything I didn’t need… yet I still have 2 iPhones, an iPad, 3 Android phones, a Mac and a PC.

To be fair, I’m a mobile developer, so although I probably don’t need these they certainly make my life easier. Other people might not have that many devices, but I think it would be pretty rare to find someone who just has a single “smart” device. So if you’re building an app that only stores data locally on one device, you might be causing issues for a user who would like to use your app on their iPhone and their iPad.

The solution is to store the data remotely on a server somewhere, which will allow the user to access the data from any device. However, this introduces a new problem: not everybody is connected to the Internet all the time (I should know, I’m writing this from the middle of the Australian outback). So we’re going to tackle two issues:

  1. How to store data remotely, and;
  2. How to provide offline functionality with online syncing

In this tutorial, we will be creating a todo list application called ‘ClouDO’. Unlike a previous tutorial which only stored the todo data locally, this todo application will store the data in a remote database and locally. The local data will be synced to the remote database when an Internet connection is available, and any new data from the remote database will also be synced to the local database if there is new data available.

The result will be a todo application where the user can access their todos from any device, and they will even be able to view and edit the todos on their device even when no Internet connection is available. In the end, it’s going to look like this:

ClouDO Screenshot

Syncing offline and online data sounds like quite the task (and it is), but two bits of technology are going to make this process pretty straightforward for us: CouchDB and PouchDB.

CouchDB is a document style NoSQL database that is built for the web. It is very similar to MongoDB which we used in Building a Review App with Ionic 2, MongoDB & Node. Perhaps the biggest advantage CouchDB has over MongoDB is its ability to easily replicate databases across devices (CouchDB can run just about anywhere), which is great for facilitating offline functionality with online sync. If this is not a requirement for your application, then MongoDB may be the better choice (which has better support for ad hoc queries).

PouchDB was inspired by CouchDB (hence the name), but it is designed for storing local data and then syncing to a CouchDB database when a connection is available. In this case we will be using CouchDB as the remote database, but you don’t have to use CouchDB specifically, you can use any database that supports the CouchDB protocol (of which there are many).

Before we Get Started

Before you go through this tutorial, you should have at least a basic understanding of Ionic 2 concepts. You must also already have Ionic 2 set up on your machine.

If you’re not familiar with Ionic 2 already, I’d recommend reading my Ionic 2 Beginners Guide first to get up and running and understand the basic concepts. If you want a much more detailed guide for learning Ionic 2, then take a look at Building Mobile Apps with Ionic 2.

Introduction to CouchDB

We’re not going to go into too much detail here because we will cover most of what we need to know while building out the app. I do want to cover some key concepts, though.

CouchDB uses a REST API for interfacing with the database, which means we use HTTP methods like PUT, POST and DELETE to interact with the database.

All documents stored in a CouchDB database must have an _id field, which you can specify manually or CouchDB can generate it automatically for you. After creating a document, CouchDB will also assign it a _rev (revision) field, which changes each time the document is changed. In order to update a document you must supply both the _id and the _rev, if either of these are incorrect then the update will fail. This is the key concept to ensure data is not corrupted in a CouchDB database. If you were to supply the _rev you retrieved, but the document had been updated by someone else after you retrieved that _rev, the update would fail since there has already been an update that we didn’t know about.

Another key concept is the replication. A CouchDB database can easily be replicated to another database (which we will be making use of in this tutorial), and this replication can be:

  • One way (PouchDB database is replicated to the CouchDB database)
  • Bi-directional (PouchDB database is replicate to the CouchDB database, and vice versa)
  • Ad hoc (replication is triggered manually)
  • Continuous (database is continually replicated as necessary, changes are replicated instantly)

In this tutorial, we will be using PouchDB to interface with CouchDB, not CouchDB itself, but the concepts are the same. If you wanted to, you could also just interact with the CouchDB database directly using the REST API rather than using PouchDB (but then you would lose the awesome offline sync functionality).

We will be setting up a bi-directional and continuous replication, so as soon as we make a change to our local data it will be reflected in the remote database, and as soon as we make a change to the remote data it will be replicated in the local data (it’s quite cool to play around with).

Setting up CouchDB

The first thing we need to do is get our CouchDB database set up. We will be using a locally installed version of CouchDB for ease of development, so you won’t be able to access the data outside of the machine you are developing on. However, if you are following this tutorial for a real application you are building, you will just need to set up CouchDB on your server, rather than on your local machine.

To set up CouchDB you need to do is head to the CouchDB website, download it, and then it should be as simple as extracting the files and opening the CouchDB application.

Once you have it installed you should be able to able to navigate to:

http://127.0.0.1:5984/_utils/

or

http://localhost:5984/_utils/

to open up Futon, which is CouchDB’s built in administration interface. Which will look like this:

Futon Screenshot

NOTE: If you are running this on a server and not a local development machine, make sure to fix the “Welcome to the admin party!” message.

What we need to do now is create a new database for our application. Click the Create Database option in the top left to create a new database called cloudo. Once you create it, you will automatically be taken inside of the database where you can create a new document. If you click ‘Add Document’ you will see something like this:

Futon Screenshot

As I mentioned earlier, you can either manually create your own _id field or accept the default from CouchDB. We will just use the default, so click the tick icon to the right to accept the _id.

Once you have done that you can click the Add Field button to add some fields to this document. Create a new field called title and give it a value of hello. Once you are done click Save Document. Now you will see something like this:

Futon Screenshot

Notice that the _rev field has been automatically added now, and it is prefixed with 1- to indicate that this is the first revision of the document. If you now change the hello value to hello world and then click Save Document again, that _rev field will change to 2-xxxx.

If you would like, you can create some more documents in this database but that’s all we need to do for our application. We will be creating the ability to add documents through the application so there’s no need to manually modify anything in here.

Generating a new Ionic 2 Application

Now that we have our backend ready to use, let’s start building the front end. We will start by generating a new Ionic 2 application.

Generate a new Ionic 2 application with the following command:

ionic start cloudo blank --v2

Once it has finished generating, we will need to switch into it.

Run the following command to make the new project your working directory:

cd cloudo

We are going to be creating a provider to handle interfacing with the database, so let’s create that now.

Create a Todos provider with the following command:

ionic g provider Todos

and we are also going to need to install PouchDB.

Install PouchDB with the following command:

npm install pouchdb --save

The TypeScript compiler doesn’t know what PouchDB is, so it’s going to throw some errors at us if we try to use it. To get around this we need to install the types for PouchDB.

Run the following command to install the types for PouchDB:

npm install @types/pouchdb --save --save-exact

In order to be able to make use of the provider we created, we will need to add it to the app.module.ts file.

Modify src/app/app.module.ts to reflect the following:

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { Todos } from '../providers/todos';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}, Todos]
})
export class AppModule {}

There’s one more thing we need to do to set up our application. By default, we are going to run into CORS (Cross Origin Resource Sharing) issues when trying to interact with CouchDB. You may get an error like this:

XMLHttpRequest cannot load http://localhost:5984/clouddo/?_nonce=1466856096255. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8100' is therefore not allowed access.

To fix this, you can simply install the add-cors-to-couchdb package.

Run the following command:

npm install -g add-cors-to-couchdb

Then run the following command:

add-cors-to-couchdb

If it has worked, you should get a message saying “Success”. This will configure CouchDB correctly, and you only ever need to run this once (if you create another project using CouchDB you won’t need to do this again).

Creating the Front End

Now we’ve got all the theory and configuration out of the way, it’s time to jump into the fun stuff. We’ll start off with the most interesting thing which is the Todos provider.

Modify todos.ts to reflect the following:

import { Injectable } from '@angular/core';
import PouchDB from 'pouchdb';

@Injectable()
export class Todos {

  data: any;
  db: any;
  remote: any;

  constructor() {

    this.db = new PouchDB('cloudo');

    this.remote = 'http://localhost:5984/cloudo';

    let options = {
      live: true,
      retry: true,
      continuous: true
    };

    this.db.sync(this.remote, options);

  }

  getTodos() {

  }

  createTodo(todo){

  }

  updateTodo(todo){

  }

  deleteTodo(todo){

  }

  handleChange(change){

  }

}

We’ve set up the basic structure for our provider above. The first thing to note is that we have imported PouchDB, which lets us create a new PouchDB database in the constructor, which we set up as db. We will be able to use that database reference throughout this provider to interact with its various methods.

In the constructor we also define our remote database, which is just http://localhost:5984/ followed by the name of the database, which in this case is cloudo. Then we call PouchDB’s sync method, which will set up two way replication between our local PouchDB database, and the remote CouchDB database. If you only wanted one way replication you could instead use this.db.replicate.to('http://localhost:5984/cloudo').

Then we have defined a bunch of functions which will perform various tasks for our application, we are going to go through those one by one now.

Modify getTodos() to reflect the following:

  getTodos() {

    if (this.data) {
      return Promise.resolve(this.data);
    }

    return new Promise(resolve => {

      this.db.allDocs({

        include_docs: true

      }).then((result) => {

        this.data = [];

        let docs = result.rows.map((row) => {
          this.data.push(row.doc);
        });

        resolve(this.data);

        this.db.changes({live: true, since: 'now', include_docs: true}).on('change', (change) => {
          this.handleChange(change);
        });

      }).catch((error) => {

        console.log(error);

      }); 

    });

  }

This function will return a promise containing the data from our database. If the data has already been fetched then it just returns it right away, otherwise it fetches the data from our database. We use the this.db.allDocs method to return all of the documents in our database, and then we process the result by pushing all of the data into our this.data array.

We also set up a db.changes listener here, which will trigger every time there is a change to the data (i.e. if we manually edited the data in Futon). It will sent the change through to the handleChange function, which we will define shortly.

Modify the handleChange function to reflect the following:

  handleChange(change){

    let changedDoc = null;
    let changedIndex = null;

    this.data.forEach((doc, index) => {

      if(doc._id === change.id){
        changedDoc = doc;
        changedIndex = index;
      }

    });

    //A document was deleted
    if(change.deleted){
      this.data.splice(changedIndex, 1);
    } 
    else {

      //A document was updated
      if(changedDoc){
        this.data[changedIndex] = change.doc;
      } 

      //A document was added
      else {
        this.data.push(change.doc); 
      }

    }

  }

This function is provided information about the change that occurred. What we want to do with that change is reflect it in our this.data array, but it get’s a little bit tricky. The change object that is sent back could either be a document that has been updated, a new document, or a deleted document.

Detecting a deleted document is easy enough because it will contain the deleted property. But to see if it is an update, we need to check if we already have a document with the same id, if we don’t then we know it is a new document.

Now if there is a change in the remote data, we are going to see it reflected immediately in our local this.data array. Let’s finish off the rest of the functions now.

Modify the createTodo, updateTodo, and deleteTodo functions to reflect the following:

  createTodo(todo){
    this.db.post(todo);
  }

  updateTodo(todo){
    this.db.put(todo).catch((err) => {
      console.log(err);
    });
  }

  deleteTodo(todo){
    this.db.remove(todo).catch((err) => {
      console.log(err);
    });
  }

These functions are quite straight forward, we simply call PouchDB’s methods to create, delete or update a document. Remember how I said that you need to provide both the _id and _rev when updating a document? You can just supply these manually, or you can do what we have done here and just provide the whole document (which will contain both the _id and the _rev).

PouchDB does all the heavy lifting behind the scenes for us, but you could also interact with CouchDB directly by using the Http service and the POST, PUT, and DELETE methods.

Now all we need to do is create our interface, which is going to be a simple one page list. We will handle adding new todos and updating todos with Alerts.

Modify home.ts to reflect the following:

import { Component } from "@angular/core";
import { NavController, AlertController } from 'ionic-angular';
import { Todos } from '../../providers/todos';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  todos: any;

  constructor(public navCtrl: NavController, public todoService: Todos, public alertCtrl: AlertController) {

  }

  ionViewDidLoad(){

    this.todoService.getTodos().then((data) => {
      this.todos = data;
    });

  }

  createTodo(){

    let prompt = this.alertCtrl.create({
      title: 'Add',
      message: 'What do you need to do?',
      inputs: [
        {
          name: 'title'
        }
      ],
      buttons: [
        {
          text: 'Cancel'
        },
        {
          text: 'Save',
          handler: data => {
            this.todoService.createTodo({title: data.title});
          }
        }
      ]
    });

    prompt.present();

  }

  updateTodo(todo){

    let prompt = this.alertCtrl.create({
      title: 'Edit',
      message: 'Change your mind?',
      inputs: [
        {
          name: 'title'
        }
      ],
      buttons: [
        {
          text: 'Cancel'
        },
        {
          text: 'Save',
          handler: data => {
            this.todoService.updateTodo({
              _id: todo._id,
              _rev: todo._rev,
              title: data.title
            });
          }
        }
      ]
    });

    prompt.present();
  }

  deleteTodo(todo){
    this.todoService.deleteTodo(todo);
  }

}

In this class we are importing our Todos service and loading in the data from it. We create two methods for creating and updating todos through an Alert, which both call our Todos service, as well as a method for deleting todos. Notice that when we create the todo create a JSON object only containing the title, but when we update it we supply the _id, _rev, and title. When deleting we just pass through the entire document from our template.

Now let’s get the template sorted

Modify home.html to reflect the following:

<ion-header no-border>
  <ion-navbar color="secondary">
    <ion-title>
      ClouDO
    </ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="createTodo()"><ion-icon name="cloud-upload"></ion-icon></button>
    </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content>

  <ion-list no-lines>

    <ion-item-sliding *ngFor="let todo of todos">

      <ion-item>

        {{todo.title}}

      </ion-item>

      <ion-item-options>
        <button ion-button icon-only color="light" (click)="updateTodo(todo)">
          <ion-icon name="create"></ion-icon>
        </button>
        <button ion-button icon-only color="primary" (click)="deleteTodo(todo)">
          <ion-icon name="checkmark"></ion-icon>
        </button>
      </ion-item-options>
    </ion-item-sliding>

  </ion-list> 

</ion-content>

Pretty straightforward here, we just create a simple list to display the data, with sliding items to reveal both the Edit and Delete functions.

We’re pretty much done now but let’s add a bit of styling to pretty things up a bit.

Modify home.scss to reflect the following:

.ios, .md {

  page-home {

    .scroll-content {
      background-color: #ecf0f1;
      display: flex !important;
      justify-content: center;
    }

    ion-list {
      width: 90%;
    }

    ion-item-sliding {
      margin-top: 20px;
      border-radius: 20px;
    }

    ion-item {
      border: none !important;
      font-weight: bold !important;
    }

  }

}

Modify the $colors in src/theme/variables.scss to reflect the following:

$colors: (
  primary:    #95a5a6,
  secondary:  #3498db,
  danger:     #f53d3d,
  light:      #f4f4f4,
  dark:       #222,
  favorite:   #69BB7B
);

You should now have something that looks like this:

ClouDO Screenshot

Go ahead and add some items to your todo list, and what’s even cooler is that you can open up Futon again, change some data in the database, and watch the data update live in your app!

Summary

I think it’s pretty clear to see the benefit that replicating databases and offline syncing provides, and the PouchDB + CouchDB combo makes this really easy to pull off. In a later tutorial we will cover how to do some more advanced things with CouchDB (like using MapReduce).

What to watch next...

  • Is couch db realtime? is there a limit like localstorage? And can i run query on this?

  • Rolf Erikson

    Can the same thing be done with Backand?

  • Bruno Renostro

    How could I have multiple users?

  • #Maïky

    Thank you for this post! I started with pouchdb/couchdb a few months ago and you confirm that I was not wrong with this choice.

  • Maulik

    hi,
    how can we upload image to dbs from phone or from camera?

    • You can do it using ionic2 camera native componants , then converting it to Base64, then finally saving it as a attachment in pouchdb.. there is a lot of documentation for this 🙂

  • Florian Hübner

    Great tutorial Josh! Would be interested in hearing how this approach compares to Firebase in your oppinion.

  • Bob

    First of all, great tutorial! I was hoping you could help provide details on adding authentication to the above example. I found pouched-authentication but I have not been able to figure out how to get it working in the project. If you could help with that it would be great — could also make a fantastic part 2 of your tutorial! Thanks so much for the time you spend writing these tutorials. It is very much appreciated!

  • Pingback: Creating a Multiple User App with PouchDB & CouchDB | HTML5 Mobile Tutorials | Ionic, Phaser, Sencha Touch & PhoneGap()

  • DC

    Josh, excellent overall tutorial. It seems my todos are not being added to the cloudo database. I’m running android.
    I’ve reviewed the source as well. Appreciate any ideas. Thanks

  • Sourabh

    Hi Josh. thankyou very much for the tutorial. following this article i managed to setup couchdb + pouch db in my quiz app i m building but i m facing a major issue. My couchdb server have hundreds of documents for topics and thousands of documents for Quiz. How do i prevent cleint app to download all the documents form the server . i only wants the app to download the documents that are of topics but not of quiz. otherwise the app database become too huge. is there a way i can do this. i have read a bit about filter but i m not sure how to implement this.

  • Sourabh

    Hello again josh thanks for the previous reply. however i m stuck at this problem since yesterday and not able to figure it out. can you please have a look and guide me in the right direction . my question is long and includes codes so i have posted it on stackoverflow here http://stackoverflow.com/questions/38567089/ionic-2-couchdb-pouchdb-how-to-get-data-which-is-not-synced-yet . please advise me i would be very grateful to you.

  • John

    Please could you give me advice how solve my database question.
    I’m creating app on mobile and web so pouchDb+couchDb is ideal for live sync.
    Every account has own couchDb, every account has more users which can read and insert documents (without any permission check).
    Document has:name, inserted date, category, user which created it.

    Typical operation is: get documents for last month from user aaa and category bbb

    When I will store 600 documents per month after one year I will have 220 000 documents.

    Should I worry that operation “get documents from last month or any period from user” on so many documents could be too slow?

    Thanks

  • Emmanuel FRANCOIS

    Hi Josh,
    Thanks for this interesting article!
    However, I’d like to mention that installing PouchDB in my project was a true nightmare!
    It tooks me about 10 hours, only to make it install properly on my Windows 7 x64 PC, dealing with so many npm / node_gyp issues…
    For those who encounter same problems, the solution is here: https://github.com/nodejs/node-gyp/issues/629 (at the end).
    You will have to install Visual Studio 2015 and change npm config…
    Yes, that’s not a joke: you need Visual Studio C++ compilers to compile node PouchDB libraries: I can’t believe it!
    Installing Visual Studio tooks me 2 hours and about 10Gb on my hard drive!…
    If you know a simpler and less costly solution, it could be interesting to mention it in your article.

    • Evandro Junior

      In Linux it took me 20 seconds to install pouchdb. You are not using the best OS for the job

  • Jon

    Hi Josh,

    Thanks for the tutorial! I’m having a bit of a problem with installing pouchdb. When I try, I get the below error:

    $sudo typings install dt~pouchdb/pouch –global –save
    >typings ERR! message Unable to find “pouchdb/pouch” (“dt”) in the registry.

    Any idea how I might solve this? Your thoughts would be greatly appreciated!

    • Hi, check the post again for an updated command.

  • Pingback: Part 2: Creating a Multiple User App with Ionic 2, PouchDB & CouchDB | HTML5 Mobile Tutorials | Ionic, Phaser, Sencha Touch & PhoneGap()

  • Jan Landen

    Hi Josh,
    I downloaded the source code ot the cloudo tutorial and managed to get it up and running with ionic2. It works perfectly, but when I create couple of items and remove the first one from the list, it will remove the doc from the remote db as well. The problem is that the UI will always hang when running it with ios emulator. It works fine with “ionic serve”, but the ios simulator won’t work when removing the items… Any ideas?

    • Jan Landen

      Hi,

      When the IOS app hangs, there is following error in the simulator console:

      error JSON.stringify()ing argument: TypeError: JSON.stringify cannot serialize cyclic structures.

      • Ghali Ahmed

        hi there, are you in an ios10 envirement ? i have the same ussue

      • Jan Landen

        Hi Ghali, i changed my architecture totally. Now i am very confident about the scalability, reliability and the fluent user experience. I changed to React Native and Couchbase. Works like a charm!

      • Ghali Ahmed

        Thanks

  • nghiath

    fix syntax:

    home.ts should be like this:
    ————————————————————————————-
    import {Component} from “@angular/core”;
    import {NavController, AlertController} from ‘ionic-angular’;
    import {Todos} from ‘../../providers/todos/todos’;

    @Component({
    templateUrl: ‘build/pages/home/home.html’
    })
    export class HomePage {

    todos: any;

    constructor(private nav: NavController, private todoService: Todos, private alertController: AlertController) {

    }

    ionViewLoaded(){
    this.todoService.getTodos().then((data) => {
    this.todos = data;
    });
    }

    createTodo(){

    let prompt = this.alertController.create({
    title: ‘Add’,
    message: ‘What do you need to do?’,
    inputs: [
    {
    name: ‘title’
    }
    ],
    buttons: [
    {
    text: ‘Cancel’
    },
    {
    text: ‘Save’,
    handler: data => {
    this.todoService.createTodo({title: data.title});
    }
    }
    ]
    });

    //this.nav.present(prompt);
    prompt.present();

    }

    updateTodo(todo){

    let prompt = this.alertController.create({
    title: ‘Edit’,
    message: ‘Change your mind?’,
    inputs: [
    {
    name: ‘title’
    }
    ],
    buttons: [
    {
    text: ‘Cancel’
    },
    {
    text: ‘Save’,
    handler: data => {
    this.todoService.updateTodo({
    _id: todo._id,
    _rev: todo._rev,
    title: data.title
    });
    }
    }
    ]
    });

    //this.nav.present(prompt);
    prompt.present();
    }

    deleteTodo(todo){
    this.todoService.deleteTodo(todo);
    }

    }

    • Akmal

      thanks 🙂
      no wonder i have error

    • alsbury

      Thanks, you just saved me the trouble of sorting that all out myself

    • Adel

      We have to call ionViewLoaded() method in the constructor .

  • Evandro Junior

    It is time to update the tutorial for ionic rc 0

    The issue with pouch db has been fixed!
    https://github.com/driftyco/ionic/issues/8274

  • Hello,

    Thank you for this tutorial!

    Please, is it possible to have an update witch Ionic 2 RC?
    For the moment, following this tutorial, I get this exception: “Error in ./HomePage class HomePage_Host – inline template:0:0 caused by: PouchDB$2 is not a constructor”
    Do you have any idea to fix this issue?

    Thank you for your help.

    • Here the solution:
      Use
      import PouchDB from ‘pouchdb’;
      Instead of
      import * as PouchDB from ‘pouchdb’;

  • Chuyen Nguyen Anh

    Thanks for your tut. But I can not run that code.
    I have latest version of ionic-angular rc2, but I got this error with ionicbootstrap module.
    ==> has no exported member ‘ionicBootstrap’.
    I googled but no results to make your code works…

    Please help!

  • nine

    Thanks for this great tuto.
    I’m facing error typescrypt :
    typescript: src/app/app.ts, line: 2
    Module ‘”/Users/username/www/myapp/node_modules/ionic-angular/index”‘ has no exported member
    ‘ionicBootstrap’.
    here is my package.json
    {
    “name”: “ionic-hello-world”,
    “author”: “Ionic Framework”,
    “homepage”: “http://ionicframework.com/”,
    “private”: true,
    “scripts”: {
    “ionic:build”: “ionic-app-scripts build”,
    “ionic:serve”: “ionic-app-scripts serve”
    },
    “dependencies”: {
    “@angular/common”: “2.1.1”,
    “@angular/compiler”: “2.1.1”,
    “@angular/compiler-cli”: “2.1.1”,
    “@angular/core”: “2.1.1”,
    “@angular/forms”: “2.1.1”,
    “@angular/http”: “2.1.1”,
    “@angular/platform-browser”: “2.1.1”,
    “@angular/platform-browser-dynamic”: “2.1.1”,
    “@angular/platform-server”: “2.1.1”,
    “@ionic/storage”: “1.1.6”,
    “ionic-angular”: “2.0.0-rc.3”,
    “ionic-native”: “2.2.3”,
    “ionicons”: “3.0.0”,
    “pouchdb”: “^6.1.0”,
    “rxjs”: “5.0.0-beta.12”,
    “zone.js”: “0.6.26”
    },
    “devDependencies”: {
    “@ionic/app-scripts”: “0.0.45”,
    “typescript”: “2.0.6”
    },
    “cordovaPlugins”: [
    “cordova-plugin-device”,
    “cordova-plugin-console”,
    “cordova-plugin-whitelist”,
    “cordova-plugin-splashscreen”,
    “cordova-plugin-statusbar”,
    “ionic-plugin-keyboard”
    ],
    “cordovaPlatforms”: [
    “ios”,
    {
    “platform”: “ios”,
    “version”: “”,
    “locator”: “ios”
    }
    ],
    “description”: “campagnole: An Ionic project”
    }

    how get it working ? Thank you for looking !
    nine

  • Hello all…

    When I run the command

    $ add-cors-to-couchdb

    I get this error:

    Error: status 401 {“error”:”unauthorized”,”reason”:”You are not a server admin.”}

    at /usr/local/lib/node_modules/add-cors-to-couchdb/index.js:45:13

    at process._tickCallback (internal/process/next_tick.js:103:7)

    Any thoughts on what I should do?

    • mash kaponde

      I am also facing the same problem, have you managed to find a work around this?

    • Danilo S. Coelho

      Hi Samuel,
      This occurred with me.
      Probably your CouchDB is not in “Admin Party”, so use your user and password.

      add-cors-to-couchdb -u username -p password

      Good luck.

  • Daniel Almeida Luz

    Does the “sync” method at “this.db.sync” really needs the “continuous” option? Didn’t found anything about this one in the PouchDB Reference(https://pouchdb.com/guides/replication.html), just the “live” and “retry” options should do it, right?

  • Kurl Rip

    Hello
    Once month ago i use this tutorial and all work corectly.
    I would use this in other app and that dont work

    So yesterday, i do again this tuto, in other app name cloudo2, and that don t work…

    My problem is that the data don t refresh in live.

    If i open two navigator on the main page. the first time i add a data on a page, this data appear on the second. But if i add a other data, the data don’t appear on the second. On the second page the data appears only if i click on add button or if i do an interaction with the button del or modify.

    So why the data refresh in live only for the once.
    And why once month ago it s work and no it don t.
    I have the two project but i don t see the difference between the two project…..

    ps: sorry for bad english…

    • I have a similar problem. It appears that the handleChange function gets called and the this.data array get updated on time, but that these changes aren’t always immediately reflected back in the UI. Seems to be an issue with Angular change detection and I don’t (yet) know enough about Angular to work out how to overcome it.

      • pintu gupta

        I saw in debugger tool , pouch db change events gets fired and changed data gets added to the list , but UI does’t gets changed data?. It is the case when i open page inside browser

      • Huy Tran

        One of the commenters above mentioned to call the zone function to update the UI. I made some modifications to todos.ts at three areas of the file and it works for me

        import { Injectable, NgZone } from ‘@angular/core’;
        .
        .
        constructor(public zone: NgZone) {
        .
        .
        handleChange(change){
        let changedDoc = null;
        let changedIndex = null;

        this.data.forEach((doc, index) => {

        if(doc._id === change.id){
        changedDoc = doc;
        changedIndex = index;
        }

        });

        this.zone.run(() => {
        //A document was deleted
        if(change.deleted){
        this.data.splice(changedIndex, 1);
        }
        else {

        //A document was updated
        if(changedDoc){
        this.data[changedIndex] = change.doc;
        }

        //A document was added
        else {
        this.data.push(change.doc);
        }

        }

        });
        }

  • Clément de Bellefroid

    Hi,

    In order to access CouchDb with my android device, i’m guessing i’d have to change the “remote” address, or is it possible to access localhost on my device ?

    Thanks for the tutorial.

    • Dhameer Govind

      Hi, any luck with this?

      • Clément de Bellefroid

        No…

      • Dhameer Govind

        OK thanks. I’ll keep trying for the rest of tomorrow. Hopefully I’ll get somewhere. I found a 30 day free couchdb hosting site (https://www.smileupps.com) where I deployed couchdb. Now just having big issues connecting to it. If I get anywhere I’ll post here.

    • Dhameer Govind

      Ok, apologies for the late response. I got it to work on the 30 day free trial of IBM cloudant

      //local code
      this.db = new PouchDB(‘dbname’);
      this.remote = ‘https://username:[email protected]/dbname’

      //server
      ps. make sure you allow CORS under account–>CORS settings and also check your permissions are correct(on cloudant)

      might not be the most secure manner but it’s where I got it to at this point

  • Hey Josh!!! That is really wonderful article. Can you please assist me in creating a demo with rest api and mysql backend?

  • Kevin Gomez

    Hi Josh, this code works perfectly, but when, update, create or delete some things in one device, in the others the UI just change the first time, and not all the time, any idea why?

  • YD Hettiarachchi

    “but you don’t have to use CouchDB specifically, you can use any database that supports the CouchDB protocol (of which there are many).”
    Is mongoDB spports the CouchDB protocol?

  • Solved the angular update issue by wrapping the update code in todos.ts in a zone.run call as follows:
    this.zone.run(() =>
    {
    //A document was deleted
    if (change.deleted)
    {
    console.log(“deletion detected, removing”);
    this.data.splice(changedIndex, 1);
    }
    else
    {

    //A document was updated
    if (changedDoc)
    {
    console.log(“update detected, updating”);
    this.data[changedIndex] = change.doc;
    }

    //A document was added
    else
    {
    console.log(“adition detected, adding”);
    this.data.push(change.doc);
    }

    }
    });

    • pintu gupta

      thanks dave .. working perfectly.. i was implementing the same but inside home component where we fetch all document

  • pintu gupta

    hi Josh , please help , I have implemented similar code , but UI does’t gets reflected when i change data in server,It changes only first time , I am working in development environment and using chrome/edge. I have gone through the below comments it is there with other folks also..

  • Gaurav

    Hi Josh, Thanks for tutorial. I need to save posts data (images + text) locally retrieved from web api for offline browsing & performing other task using saved data. Please advise best way to do that, if possible any example for that will be great.
    Thanks Again!

  • Hernán Pereyra

    hi everyone, this tutorial apply to the ionic 3 latest version? very thanks, regards

  • osaru agbonaye

    Hi, thanks for the tutorial
    how do you modify this in order to log in with cluchdb admin username and password

    this.remote = ‘http://localhost:5984/cloudo’;

    let options = {
    live: true,
    retry: true,
    continuous: true
    };

  • Johan

    Hi, I am new on Ionic. I need help please.

    How do I submit one input using a form with formbuilder.

  • VITHIKA GUPTA

    I am getting the following error on running the project using ionicserve on browser-

    vendor.js:90642 Uncaught Error: Encountered undefined provider! Usually this means you have a circular dependencies (might be caused by using ‘barrel’ index.ts files).

    • VITHIKA GUPTA

      The error has been resolved,addingimport { Http } from ‘@angular/http’;constructor( public http: Http) in todos.ts file and importing it in the app.module.ts file,adding it in imports too.

  • Hello All,
    How can I change the remote URL from localhost to hosted webserver?
    this.remote = ‘http://localhost:5984/cloudo’

    please help.

  • Huy Tran

    This may be a dump question. While reading through this tutorial (very detail and great info), if we are using pouchdb for manipulating data, why do we have to maintain the “data” array? I’m i missing something?