No hace mucho escribí sobre el uso de Couchbase en una aplicación Ionic 2. En el ejemplo anterior vimos cómo usar Couchbase Lite y la API RESTful para guardar datos y trabajar con Couchbase Sync Gateway. Aunque ese es el enfoque preferido, no es el único.
Existe una magnífica biblioteca JavaScript llamada PouchDB que realmente puede conectarse a Couchbase Sync Gateway, sacando a Couchbase Lite de la ecuación. Esto significa que PouchDB se puede utilizar con Ionic 2 para sincronizar datos entre Couchbase Server y cualquier aplicación que se haya creado.
¿Cómo podemos conseguirlo?
Requisitos
Si viste el tutorial anterior, los requisitos aquí no serán diferentes. Son los siguientes:
- Node.js 4.0+
- Marco iónico 2.0
- Pasarela de sincronización Couchbase
- Android SDK para Android y Xcode para iOS
La CLI de Ionic Framework 2.0 se utiliza para crear y construir aplicaciones. Utiliza el Node Package Manager (NPM) que se encuentra con Node.js para descargar todas las dependencias. En este ejemplo no sincronizaremos con Couchbase Server, pero usaremos Couchbase Sync Gateway. El plan aquí es hacer uso de la opción in-memory que Sync Gateway tiene para prototipado. Incluir Couchbase Server no es difícil después de esto.
Preparación de Couchbase Sync Gateway
Couchbase Sync Gateway maneja toda la orquestación de datos entre dispositivos, plataformas y Couchbase Server. Esto incluye cualquier permiso de lectura y escritura. Debido a esto necesita tener su propia configuración basada en las necesidades de tu aplicación.
Tomemos como ejemplo la siguiente configuración:
|
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+", "Changes+", "Attach+"], "databases": { "example": { "server":"walrus:", "sync":` function (doc) { channel (doc.channels); } `, "users": { "GUEST": { "disabled": false, "admin_channels": ["*"] } } } }, "CORS": { "Origin": ["https://localhost:8100"], "LoginOrigin": ["https://localhost:8100"], "Headers": ["Content-Type"], "MaxAge": 17280000 } } |
La configuración anterior crea una base de datos llamada ejemplo sin reglas en cuanto a la canalización de datos. Esto significa que todos los dispositivos podrán leer y escribir. Tus necesidades pueden ser distintas.
En la parte inferior de la configuración hay información sobre el uso compartido de recursos entre orígenes (CORS). La parte inferior no es necesaria a menos que planee utilizar funciones como servicio iónico. Si se intenta acceder a Sync Gateway desde el mismo host, pero desde un puerto diferente, se producirán errores de JavaScript. La adición de CORS evitará esto. Una vez más, esto es sólo si desea servir la aplicación en su navegador web. Ejecutar la aplicación desde su dispositivo no tendrá problemas.
Creación de una aplicación Ionic Framework con PouchDB
La aplicación que crearemos será igual a la que creamos anteriormente con Couchbase Lite y Ionic 2. Vamos a crear una sencilla aplicación de lista de tareas que sincroniza los elementos entre dispositivos.

Esta es una aplicación de una sola página, pero cubre todas las mejores prácticas necesarias de Ionic 2 y Angular 2. Para crear este proyecto, ejecuta lo siguiente desde tu Símbolo del sistema (Windows) o Terminal (Mac y Linux):
|
1 2 3 4 |
ionic start PouchDBProject blank --v2 cd PouchDBProject ionic platform add ios ionic platform add android |
Los comandos anteriores crearán un nuevo proyecto Ionic 2 que utiliza Angular 2 y TypeScript. Aunque he añadido las plataformas de compilación de Android e iOS, no podrás compilar para iOS a menos que utilices un Mac con Xcode instalado.
Dado que este proyecto utilizará PouchDBes necesario instalarlo en nuestro proyecto. Esto se puede hacer ejecutando el siguiente comando:
|
1 |
npm install pouchdb --save |
PouchDB es un proyecto JavaScript y no tiene soporte adecuado para TypeScript. Esto no es un problema ya que podemos sortear el obstáculo de la falta de definiciones de tipo. Sin embargo, vamos a necesitar obtener definiciones de tipos para otra librería que cargará el plugin.
Desde la línea de comandos, ejecute lo siguiente:
|
1 |
npm install @type/node --save |
La instalación de las definiciones de tipos de Node.js nos permitirá utilizar la función requiere que es esencial cuando se importan dependencias de JavaScript.
Hay una dependencia más de JavaScript para nuestro proyecto, pero esta vez no tenemos que preocuparnos de que las definiciones de tipo no existan. Necesitamos instalar una librería para generar valores id únicos que representarán nuestras claves de documento. Esta librería puede ser instalada a través del siguiente comando:
|
1 |
npm install uuid @type/uuid --save |
El comando anterior instalará la biblioteca y sus definiciones de tipos para TypeScript.
En este punto podemos centrarnos en el desarrollo de nuestra aplicación.
Desarrollo del proveedor compartido de Angular 2
Es una buena práctica en cualquier aplicación Angular 2 mantener la actividad relacionada con la base de datos separada en lo que se conoce como un proveedor compartido o servicio compartido. Esto nos permite tener una instancia de base de datos singleton y mantener nuestro código de base de datos fuera de la lógica de la página.
Para crear un proveedor en Ionic 2, ejecute el siguiente comando:
|
1 |
ionic g provider pouchdb-provider |
El comando anterior debería crear un archivo en src/providers/pouchdb-provider.ts con el que podamos trabajar. Abra este nuevo archivo 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 23 |
import { Injectable, EventEmitter } from '@angular/core'; var PouchDB = require("pouchdb"); @Injectable() export class PouchDBProvider { private isInstantiated: boolean; private database: any; private listener: EventEmitter = new EventEmitter(); public constructor() { } public fetch() { } public get(id: string) { } public put(document: any, id: string) { } public sync(remote: string) { } public getChangeListener() { } } |
Antes de empezar a rellenar cada uno de los métodos, vamos a desglosar lo que tenemos hasta ahora.
Este proveedor será inyectable en cada una de las páginas que deseemos utilizarlo. Dado que PouchDB nos permite sincronizar cambios, queremos ser capaces de emitir esos cambios y suscribirnos a ellos, de ahí la necesidad del módulo Emisor de eventos importar. La biblioteca PouchDB se importa como JavaScript estándar.
Sólo queremos mantener abierta una instancia de la base de datos y podemos hacerlo utilizando una función isInstantiated variable. Esto se hace en el constructor así:
|
1 2 3 4 5 6 |
public constructor() { if(!this.isInstantiated) { this.database = new PouchDB("nraboy"); this.isInstantiated = true; } } |
Con una base de datos abierta queremos ser capaces de trabajar con los datos. PouchDB tiene sus propias APIs para trabajar con datos, pero siendo que es vanilla JavaScript, ciertas cosas deben ser mejoradas en nuestro proveedor para hacerlo más amigable con Angular 2.
|
1 2 3 |
public fetch() { return this.database.allDocs({include_docs: true}); } |
La obtención de todos los datos puede realizarse mediante la función allDocs en PouchDB. Al incluir el método incluir_docs se incluyen los datos del documento en lugar de sólo los valores de id.
|
1 2 3 |
public get(id: string) { return this.database.get(id); } |
Si conoce el identificador del documento que busca, puede buscarlo directamente en lugar de consultar todos los documentos. Guardar documentos es donde las cosas se pueden poner un poco confusas:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public put(document: any, id: string) { document._id = id; return this.get(id).then(result => { document._rev = result._rev; return this.database.put(document); }, error => { if(error.status == "404") { return this.database.put(document); } else { return new Promise((resolve, reject) => { reject(error); }); } }); } |
En el poner tenemos un método para crear o actualizar un documento. Los documentos que no tienen revisión se crean, en caso contrario se actualizan. Esto es posible haciendo primero una búsqueda en un documento por su id. Si el documento existe, se utiliza la revisión y se actualiza, en caso contrario se crea.
|
1 2 3 4 5 6 7 8 9 10 |
public sync(remote: string) { let remoteDatabase = new PouchDB(remote); this.database.sync(remoteDatabase, { live: true }).on('change', change => { this.listener.emit(change); }).on('error', error => { console.error(JSON.stringify(error)); }); } |
Sin sincronización no estás técnicamente usando Couchbase en tu pila. Además, hay soluciones mucho mejores para los datos que PouchDB si sólo estás buscando una base de datos local. Dicho esto, lo anterior sincronizar le permitirá conectarse a una base de datos remota. Nuestra base de datos remota es en realidad una instancia de Sync Gateway en ejecución. Estamos optando por hacer una sincronización bidireccional con Sync Gateway y la emisión de los cambios a la Emisor de eventos en Angular 2.
|
1 2 3 |
public getChangeListener() { return this.listener; } |
Cuando deseamos suscribirnos a esos cambios, primero debemos obtener una copia del oyente que los emite.
En este punto el proveedor de Angular 2 está completo, pero no listo para ser utilizado. Es necesario añadir el proyecto de @NgModule que se encuentra en el bloque src/app/app.module.ts archivo. El archivo tendría un aspecto similar al siguiente
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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 { PouchDBProvider } from "../providers/pouchdb-provider"; @NgModule({ declarations: [ MyApp, HomePage ], imports: [ IonicModule.forRoot(MyApp) ], bootstrap: [IonicApp], entryComponents: [ MyApp, HomePage ], providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}, PouchDBProvider] }) export class AppModule {} |
Fíjese en cómo se importó el proveedor y luego se añadió a la carpeta proveedores array. Ahora se puede utilizar en cada página de nuestra aplicación.
Añadir lógica PouchDB para guardar y sincronizar
El resto de nuestra aplicación es en realidad bastante simple ahora que hemos sentado las bases de nuestro capa de datos. Recuerde que se trata de una aplicación muy sencilla, pero muy orientada a los datos.
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 |
import { Component, NgZone } from '@angular/core'; import { NavController, AlertController } from 'ionic-angular'; import { PouchDBProvider } from "../../providers/pouchdb-provider"; import * as Uuid from "uuid"; @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { public items: Array; public constructor(public navCtrl: NavController, public alertCtrl: AlertController, private database: PouchDBProvider, private zone: NgZone) { this.items = []; } public ionViewDidEnter() { } public insert() { } } |
En el código anterior estamos importando varios componentes de Ionic y Angular 2, así como nuestro proveedor PouchDB y la biblioteca UUID. Muchos de estos componentes se inyectan en el directorio constructor método. En constructor también inicializa nuestra matriz pública que se vinculará a la interfaz de usuario. Este array contendrá los datos que sincronizamos con PouchDB.
Si bien podemos inicializar nuestras variables en el constructor nunca es una buena idea cargar datos en ellos en el método constructor método. En su lugar debemos utilizar el método Ionic ionViewDidEnter método:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public ionViewDidEnter() { this.database.sync("https://192.168.57.1:4984/example"); this.database.getChangeListener().subscribe(data => { for(let i = 0; i < data.change.docs.length; i++) { this.zone.run(() => { this.items.push(data.change.docs[i]); }); } }); this.database.fetch().then(result => { this.items = []; for(let i = 0; i < result.rows.length; i++) { this.items.push(result.rows[i].doc); } }, error => { console.error(error); }); } |
En el ionViewDidEnter estamos iniciando la sincronización bidireccional con PouchDB y Couchbase Sync Gateway. El nombre de host es el de mi instancia actual de Couchbase Sync Gateway. La base de datos no necesita coincidir con la de mi base de datos local.
Cuando estemos sincronizando también vamos a suscribirnos al escuchador de cambios. Esta es una aplicación simple por lo que no nos preocuparemos de cambiar o borrar datos, sólo de añadir datos. Los escuchadores pueden ser un poco dudosos en Angular 2 ya que se produce una desconexión entre la interfaz de usuario y los datos. Esta desconexión se puede corregir añadiendo los datos del listener en un fichero zona.ejecutar método.
Con la aplicación suscrita a los cambios, necesitamos hacer una primera consulta de los datos cuando se abra la aplicación. Queremos buscar todos los datos y añadirlos a nuestra matriz pública.
El último método que tenemos es el insertar y está impulsado principalmente por Ionic:
|
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 |
public insert() { let prompt = this.alertCtrl.create({ title: 'Todo Items', message: "Add a new item to the todo list", inputs: [ { name: 'title', placeholder: 'Title' }, ], buttons: [ { text: 'Cancel', handler: data => {} }, { text: 'Save', handler: data => { this.database.put({type: "list", title: data.title}, Uuid.v4()); } } ] }); prompt.present(); } |
Aquí creamos una ventana emergente. Cuando se pulsa el botón de guardar, el texto del formulario se guarda con PouchDB. El id del documento se genera unívocamente con la librería UUID.
¿Qué aspecto tiene nuestra interfaz de usuario?
Creación de una interfaz de usuario sencilla con HTML
La interfaz de usuario es la parte fácil porque la aplicación es muy simplista. Abra el proyecto 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-header> <ion-navbar> <ion-title> PouchDB w/ Ionic 2 </ion-title> <ion-buttons end> <button ion-button icon-only (click)="insert()"> <ion-icon name="add"></ion-icon> </button> </ion-buttons> </ion-navbar> </ion-header> <ion-content padding> <ion-list> <ion-item *ngFor="let item of items"> {{ item.title }} </ion-item> </ion-list> </ion-content> |
La interfaz de usuario tiene una barra de acción con un botón que, al pulsarlo, activa la función insertar que se encuentra en nuestro TypeScript. El contenido central de la interfaz de usuario es una lista que recorre la matriz pública imprimiendo cada elemento en la pantalla.
Prueba del proyecto
Repasamos muchos conceptos y mucho código. Para facilitar las cosas, yo cargado un proyecto de trabajo a GitHub que puedes descargar y probar por ti mismo.
Con el proyecto descargado, ejecute los siguientes comandos para restaurar las dependencias, plugins y plataformas:
|
1 2 |
npm install ionic state restore |
Siempre y cuando no se olvide de actualizar el sincronizar del proyecto src/pages/home/home.ts con su host Sync Gateway, el proyecto debería poder ejecutarse.
Conclusión
Acabas de ver cómo crear una aplicación Android e iOS que se sincroniza usando Ionic 2, PouchDB y Couchbase. Este es un método alternativo a la guía anterior que escribí que utiliza Couchbase Lite.