Not too long ago I wrote about using Couchbase in an Ionic 2 application. In the previous example we saw how to use Couchbase Lite and the RESTful API for saving data and working with Couchbase Sync Gateway. While that is the preferred approach, it isn’t the only approach.
There is a great JavaScript library called PouchDB that can actually connect to Couchbase Sync Gateway, pulling Couchbase Lite out of the equation. This means that PouchDB can be used with Ionic 2 to synchronize data between Couchbase Server and any application that was created.
So how do we make this happen?
Requisitos
If you saw the previous tutorial, the requirements here will be no different. They are as follows:
- Node.js 4.0+
- Marco iónico 2.0
- Pasarela de sincronización Couchbase
- Android SDK para Android y Xcode para iOS
The Ionic Framework 2.0 CLI is used for creating and building applications. It uses the Node Package Manager (NPM) found with Node.js to download all the dependencies. We won’t be syncing with Couchbase Server in this example, but we’ll still be using Couchbase Sync Gateway. The plan here is to make use of the in-memory option that Sync Gateway has for prototyping. Including Couchbase Server isn’t difficult after this.
Preparing Couchbase Sync Gateway
Couchbase Sync Gateway handles all the orchestration of data between devices, platforms, and Couchbase Server. This includes any read and write permissions. Because of this it needs to have its own configuration based on the needs of your application.
Take the following configuration for example:
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 |
{ "log":["CRUD+", "REST+", "Cambios+", "Adjuntar+"], "bases de datos": { "ejemplo": { "servidor":"morsa:", "sync":` función (doc) { canal (doc.canales); } `, "usuarios": { "INVITADO": { "desactivado": falso, "admin_canales": ["*"] } } } }, "CORS": { "Origen": ["http://localhost:8100"], "LoginOrigin": ["http://localhost:8100"], "Cabeceras": ["Tipo de contenido"], "MaxAge": 17280000 } } |
The above configuration creates a database called ejemplo with no rules in terms of data channeling. This means that all devices will be able to read and write. Your needs may be different.
At the bottom of the configuration is information regarding cross origin resource sharing (CORS). The bottom bit is not necessary unless you plan to use features like ionic serve
. Trying to access Sync Gateway from the same host, but different port will throw JavaScript errors. The CORS addition will prevent this. Again, this is only if you wish to serve the application in your web browser. Running the application from your device will have no issues.
Creating an Ionic Framework with PouchDB Application
The application we create will match that of what we created previously with Couchbase Lite and Ionic 2. We will be building a simple todo list application that synchronizes the items between devices.

This is a single page application, but it covers all the necessary Ionic 2 and Angular 2 best practices. To create this project, execute the following from your Command Prompt (Windows) or Terminal (Mac and Linux):
1 2 3 4 |
iónico iniciar Proyecto PouchDB en blanco --v2 cd Proyecto PouchDB iónico plataforma añada ios iónico plataforma añada android |
The above commands will create a fresh Ionic 2 project that uses Angular 2 and TypeScript. While I’ve gone ahead and added both the Android and iOS build platforms, you won’t be able to build for iOS unless you’re using a Mac with Xcode installed.
Since this project will be using PouchDB, it needs to be installed into our project. This can be done by executing the following command:
1 |
npm instale pouchdb --guardar |
PouchDB is a JavaScript project and it doesn’t have proper support for TypeScript. This is not an issue as we can get around the hurdle of missing type definitions. However, we’re going to need to obtain type definitions for another library that will load the plugin.
Desde la línea de comandos, ejecute lo siguiente:
1 |
npm instale @tipo/nodo --guardar |
Installing the Node.js type definitions will allow us to use the requiere
keyword which is essential when importing JavaScript dependencies.
There is one more JavaScript dependency for our project, but this time we don’t have to worry about the type definitions not existing. We need to install a library for generating unique id values which will represent our document keys. This library can be installed through the following command:
1 |
npm instale uuid @tipo/uuid --guardar |
The above command will install the library and its type definitions for TypeScript.
At this point we can focus on the development of our application!
Developing the Angular 2 Shared Provider
It is good practice in any Angular 2 application to keep database related activity separated in what is known as a shared provider or shared service. This allows us to have a singleton database instance and keep our database code out of the page logic.
To create a provider in Ionic 2, execute the following command:
1 |
iónico g proveedor pouchdb-proveedor |
The above command should create a file at src/providers/pouchdb-provider.ts that we can work with. Open this new file and include the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
importar { Inyectable, Emisor de eventos } de @angular/core; var PouchDB = requiere("pouchdb"); @Inyectable() exportar clase PouchDBProvider { privado isInstantiated: booleano; privado base de datos: cualquier; privado oyente: Emisor de eventos = nuevo Emisor de eventos(); público constructor() { } público buscar() { } público consiga(id: cadena) { } público poner(documento: cualquier, id: cadena) { } público sincronizar(remoto: cadena) { } público getChangeListener() { } } |
Before we start populating each of the methods, let’s break down what we have so far.
This provider will be injectable in each of the pages we wish to use it. Because PouchDB allows us to sync changes we want to be able to emit those changes and subscribe to them, thus the need for the EventImitter
import. The PouchDB library is imported like standard JavaScript.
We only want to keep one database instance open and we can do that by making use of an isInstantiated
variable. This is done in the constructor
method like so:
1 2 3 4 5 6 |
público constructor() { si(!este.isInstantiated) { este.base de datos = nuevo PouchDB("nraboy"); este.isInstantiated = verdadero; } } |
With an open database we want to be able to work with the data. PouchDB has its own APIs for working with data, but being that it is vanilla JavaScript, certain things should be enhanced in our provider to make it more Angular 2 friendly.
1 2 3 |
público buscar() { devolver este.base de datos.allDocs({incluir_docs: verdadero}); } |
Fetching all data can be done using the allDocs
method in PouchDB. By including the incluir_docs
property, the document data is included rather than just id values.
1 2 3 |
público consiga(id: cadena) { devolver este.base de datos.consiga(id); } |
If you know the id of the document you’re after then you can do a lookup directly rather than querying for all the documents. Saving documents is where things can get a little confusing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
público poner(documento: cualquier, id: cadena) { documento._id = id; devolver este.consiga(id).entonces(resultado => { documento._rev = resultado._rev; devolver este.base de datos.poner(documento); }, error => { si(error.estado == "404") { devolver este.base de datos.poner(documento); } si no { devolver nuevo Promesa((resolver, rechace) => { rechace(error); }); } }); } |
En el poner
method we have a way for creating or updating a document. Documents that have no revision are created, otherwise they are updated. This is possible by first doing a lookup on a document by its id. If the document exists, use the revision and update it, otherwise create it.
1 2 3 4 5 6 7 8 9 10 |
público sincronizar(remoto: cadena) { deje remoteDatabase = nuevo PouchDB(remoto); este.base de datos.sincronizar(remoteDatabase, { en directo: verdadero }).en(cambio, cambiar => { este.oyente.emite(cambiar); }).en(error, error => { consola.error(JSON.stringify(error)); }); } |
Without synchronization you aren’t technically using Couchbase in your stack. Also, there are much better solutions for data than PouchDB if you’re only looking for a local database. With that said, the above sincronizar
function will allow you to connect to a remote database. Our remote database is actually a running Sync Gateway instance. We are choosing to do a two-way sync with Sync Gateway and emitting the changes to the Emisor de eventos
in Angular 2.
1 2 3 |
público getChangeListener() { devolver este.oyente; } |
When we wish to subscribe to those changes we must first obtain a copy of the listener that is emitting them.
At this point the Angular 2 provider is complete, but not ready to be used. It needs to be added the project’s @NgModule
block found in the src/app/app.module.ts file. The file would look something like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
importar { NgModule, ErrorHandler } de @angular/core; importar { IonicApp, IonicModule, IonicErrorHandler } de iónico-angular; importar { MyApp } de './app.component'; importar { Página de inicio } de '../páginas/hogar/hogar'; importar { PouchDBProvider } de "../providers/pouchdb-provider"; @NgModule({ declaraciones: [ MyApp, Página de inicio ], importaciones: [ IonicModule.forRoot(MyApp) ], arranque: [IonicApp], entryComponents: [ MyApp, Página de inicio ], proveedores: [{proporcionar: ErrorHandler, useClass: IonicErrorHandler}, PouchDBProvider] }) exportar clase AppModule {} |
Notice how the provider was imported and then added to the proveedores
array. It can now be used in each page of our application.
Adding PouchDB Logic for Saving and Syncing
The rest of our application is actually quite simple now that we’ve laid the foundation to our capa de datos. Remember this is a very simple application, but heavily data driven.
Abra el archivo src/pages/home/home.ts e incluya el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
importar { Componente, NgZone } de @angular/core; importar { NavController, AlertController } de iónico-angular; importar { PouchDBProvider } de "../../providers/pouchdb-provider"; importar * como Uuid de "uuid"; @Componente({ selector: página de inicio, templateUrl: Inicio.html }) exportar clase Página de inicio { público artículos: Matriz; público constructor(público navCtrl: NavController, público alertCtrl: AlertController, privado base de datos: PouchDBProvider, privado zona: NgZone) { este.artículos = []; } público ionViewDidEnter() { } público insertar() { } } |
In the above code we are importing various Ionic and Angular 2 components as well as our PouchDB provider and UUID library. Many of these components are injected in the constructor
método. En constructor
method also initializes our public array which will be bound to the UI. This array will contain the data we sync with PouchDB.
While we can initialize our variables in the constructor
method, it is never a good idea to load data into them in the constructor
method. Instead we should use the Ionic ionViewDidEnter
método:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
público ionViewDidEnter() { este.base de datos.sincronizar("http://192.168.57.1:4984/example"); este.base de datos.getChangeListener().suscríbase a(datos => { para(deje i = 0; i < datos.cambiar.docs.longitud; i++) { este.zona.ejecute(() => { este.artículos.pulse(datos.cambiar.docs[i]); }); } }); este.base de datos.buscar().entonces(resultado => { este.artículos = []; para(deje i = 0; i < resultado.filas.longitud; i++) { este.artículos.pulse(resultado.filas[i].doc); } }, error => { consola.error(error); }); } |
En el ionViewDidEnter
method we are starting the two-way sync with PouchDB and Couchbase Sync Gateway. The hostname is that of my currently running Couchbase Sync Gateway instance. The database does not need to match that of my local database.
When we are syncing we are also going to subscribe to the change listener. This is a simple application so we won’t worry about changing or deleting data, only adding data. Listeners can be a little iffy in Angular 2 where as there becomes a disconnect between the UI and the data. This disconnect can be corrected by adding the listener data into a zone.run
método.
With the application subscribing to changes, we need to do a first-time query of the data when the application opens. We want to buscar
all the data and add it to our public array.
The last method we have is the insertar
method and it is mostly Ionic driven:
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 |
público insertar() { deje consulte = este.alertCtrl.crear({ título: Todo, mensaje: "Añadir un nuevo elemento a la lista de tareas pendientes", entradas: [ { nombre: título, marcador de posición: Título }, ], botones: [ { texto: Cancelar, manipulador: datos => {} }, { texto: Guardar, manipulador: datos => { este.base de datos.poner({tipo: "lista", título: datos.título}, Uuid.v4()); } } ] }); consulte.presente(); } |
Here we craft a prompt popup. When the save button is pressed, the text in the form is saved with PouchDB. The id of the document is uniquely generated with the UUID library.
So what does our UI look like?
Creating a Simple UI with HTML
The UI is the easy part because the application is so simplistic. Open the project’s src/pages/home/home.html e incluya el siguiente código HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<ion-cabecera> <ion-barra de navegación> <ion-título> PouchDB w/ Iónico 2 </ion-título> <ion-botones fin> <botón ion-botón icono-sólo (haga clic en)="insert()"> <ion-icono nombre="añadir"></ion-icono> </botón> </ion-botones> </ion-barra de navegación> </ion-cabecera> <ion-contenido acolchado> <ion-lista> <ion-artículo *ngFor="let item of items"> {{ artículo.título }} </ion-artículo> </ion-lista> </ion-contenido> |
The UI has an action bar with a button, that when pressed will trigger the insertar
method found in our TypeScript. The core content of the UI is a list that loops through the public array printing each item to the screen.
Taking the Project for a Test Run
We went over many concepts and a lot of code. To make life easier, I cargado un proyecto de trabajo to GitHub that you can download and test out for yourself.
With the project downloaded, execute the following commands to restore the dependencies, plugins, and platforms:
1 2 |
npm instale iónico estado restaurar |
As long as you don’t forget to update the sincronizar
method found in the project’s src/pages/home/home.ts file with your Sync Gateway host, the project should be runnable.
Conclusión
You just saw how to create an Android and iOS application that syncs using Ionic 2, PouchDB, and Couchbase. This is an alternative method to the previous guide that I wrote which uses Couchbase Lite.