El año pasado escribí un artículo en el que explicaba cómo sincronizar entre plataformas y Couchbase con solo AngularJS y PouchDB. Por aquel entonces AngularJS estaba en auge y probablemente era mi framework favorito de todos los tiempos. Un año después, AngularJS sigue existiendo, pero poco a poco está siendo reemplazado por Angular 2, la próxima versión del popular framework. Después de usar Angular 2, miro hacia atrás a AngularJS 1.0 y me pregunto qué estaba pasando por mi cerebro porque Angular 2 es pura genialidad.
En esta ocasión vamos a ver cómo crear una aplicación web sencilla que se sincroniza usando únicamente Angular 2, PouchDB y Couchbase Mobile.
Requisitos
Los pocos requisitos para tener éxito con este proyecto son los siguientes:
- Node.js 4.0+
- Angular 2 CLI
- Tipologías
- Pasarela de sincronización Couchbase
La CLI de Angular 2 se puede instalar utilizando el Node Package Manager (NPM) que se incluye con Node.js. NPM también se utiliza cuando se trata de reunir varias dependencias del proyecto. No vamos a usar Couchbase Server en este ejemplo, pero podríamos. En su lugar sólo vamos a utilizar Couchbase Sync Gateway y sus características de prototipado.
Configuración de Couchbase Sync Gateway
Couchbase Sync Gateway es necesario para manejar toda la sincronización. Sin él sólo tenemos almacenamiento local en nuestra aplicación web, y con toda honestidad, hay soluciones mucho mejores que PouchDB si sólo quisieras almacenamiento local.
Descargar la última Sync Gateway e impleméntala con 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:4200"], "LoginOrigin": ["https://localhost:4200"], "Headers": ["Content-Type"], "MaxAge": 17280000 } } |
Guárdelo en un archivo llamado sync-gateway-config.json si lo desea. Esta es una configuración muy básica en la que estamos utilizando un base de datos en memoria llamado ejemplo. No hay permisos de lectura y escritura así que todo vale. En la parte inferior de esta configuración tenemos algunos elementos relacionados con la compartición de recursos entre orígenes (CORS). Debido a que estaremos sirviendo nuestra aplicación localmente, necesitamos identificar esto para prevenir errores de JavaScript.
Creación de un nuevo proyecto Angular 2
Para hacer las cosas fáciles de entender vamos a crear un proyecto con un diseño increíblemente simplista. Vamos a crear una sencilla aplicación de gestión de usuarios donde podremos añadir personas y se sincronizarán donde queramos. Por ejemplo, mira la animación de abajo:

En la animación anterior tenemos dos navegadores web. Añade una persona a uno y se sincronizará con el otro. Esto es algo que sería particularmente difícil sin Couchbase.
Empecemos creando un nuevo proyecto Angular 2 usando el Angular CLI. Ejecute lo siguiente desde su símbolo del sistema (Windows) o Terminal (Mac y Linux):
|
1 |
ng new PouchDBProject |
Puede tardar un poco, pero cuando se cree el proyecto queremos instalar PouchDB y cualquier dependencia necesaria. Ejecuta lo siguiente para instalar la última versión de PouchDB:
|
1 |
npm install pouchdb --save |
Los mantenedores de PouchDB tienen sentimientos encontrados cuando se trata de TypeScript, una tecnología crítica cuando se trata del desarrollo de Angular 2. Por esta razón no hay definiciones de tipos fiables disponibles. Sin embargo, esto no es un gran problema.
Usando Typings, vamos a instalar lo siguiente:
|
1 |
typings install dt~require --save --global |
Las definiciones de tipo anteriores nos permitirán utilizar la función requiere dentro de TypeScript. Con ella podemos importar el PouchDB descargado a nuestro proyecto.
Llegados a este punto, ¡ya podemos empezar a desarrollar nuestro proyecto!
Desarrollo de un proveedor Angular 2 para mantener los datos
Cuando se trabaja con datos siempre es una buena idea crear un servicio Angular 2, también conocido como proveedor. Esto nos permite tener una instancia singleton y una clase segregada del resto de nuestro código. Es genial desde una perspectiva de mantenibilidad.
Puede crear el proveedor manualmente o utilizando la CLI. Desde la CLI, ejecute lo siguiente:
|
1 |
ng g service pouchdb |
Deben crearse dos archivos en el directorio del proyecto src/app directorio. Debería tener un pouchdb.service.ts y un pouchdb.service.spec.ts archivo. Pueden tener un nombre similar, pero el nombre no es realmente importante.
En spec es un archivo de pruebas unitarias, algo de lo que no nos preocuparemos en este ejemplo en particular. En su lugar vamos a empezar a desarrollar en el pouchdb.service.ts archivo.
Abra el archivo e incluya el siguiente código TypeScript:
|
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 PouchDBService { private isInstantiated: boolean; private database: any; private listener: EventEmitter = new EventEmitter(); public constructor() { } public fetch() { } public get(id: string) { } public put(id: string, document: any) { } public sync(remote: string) { } public getChangeListener() { } } |
Antes de empezar a rellenar cada uno de los métodos, vamos a averiguar lo que estamos tratando de hacer. Sólo queremos una instancia de base de datos mientras se ejecuta la aplicación. Esto se puede lograr mediante el método constructor método:
|
1 2 3 4 5 6 |
public constructor() { if(!this.isInstantiated) { this.database = new PouchDB("nraboy"); this.isInstantiated = true; } } |
En este caso, la base de datos local se llama nraboy. PouchDB tiene una API realmente buena. Sólo que no está optimizada para Angular 2, lo que significa que trabajar con PouchDB en su estado vainilla en nuestro proyecto podría tener algunos contratiempos.
En el caso de que queramos recuperar todos los documentos locales, podríamos hacer algo así:
|
1 2 3 |
public fetch() { return this.database.allDocs({include_docs: true}); } |
Utilizamos el incluir_docs para que los documentos se incluyan en los resultados y no sólo sus valores de id. Tal vez sólo queramos obtener un único documento. Haríamos algo como lo siguiente
|
1 2 3 |
public get(id: string) { return this.database.get(id); } |
Hasta ahora hemos estado utilizando la API de PouchDB exactamente como recomienda la documentación. ¿Qué tal si lo cambiamos un poco? La API ofrece una manera de crear o actualizar documentos, pero vamos a combinar esto en un solo método?:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public put(id: string, document: any) { 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); }); } }); } |
Lo anterior poner comprobará si existe un documento basándose en su id. Si existe, copia la revisión en los nuevos datos y lo guarda de nuevo. Si el documento no existe, lo creará sin revisión.
Seguramente se habrá dado cuenta de que Emisor de eventos que se incluyó cerca de la parte superior. Esto es necesario para suscribirse a eventos de cambio en las páginas de Angular 2. Tome el siguiente sincronizar por ejemplo:
|
1 2 3 4 5 6 7 8 |
public sync(remote: string) { let remoteDatabase = new PouchDB(remote); this.database.sync(remoteDatabase, { live: true }).on('change', change => { this.listener.emit(change); }); } |
Aquí estamos definiendo una fuente de datos remota, que será Couchbase Sync Gateway, eligiendo hacer una sincronización bidireccional en vivo, y emitiendo los cambios cada vez que son descubiertos.
Se puede acceder al escuchador de cambios en cada página accediendo a lo siguiente:
|
1 2 3 |
public getChangeListener() { return this.listener; } |
Con lo anterior acabaríamos suscribiéndonos al oyente.
Aunque el proveedor está creado, aún no está disponible en todas las páginas. Tenemos que importarlo en el proyecto de @NgModule que se encuentra en el src/app/app.module.ts archivo. Este archivo tendrá 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 |
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { AppComponent } from './app.component'; import { PouchDBService } from "./pouchdb.service"; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, FormsModule, HttpModule ], providers: [PouchDBService], bootstrap: [AppComponent] }) export class AppModule { } |
Fíjate en que hemos importado la clase provider y la hemos añadido a la clase proveedores de la @NgModule bloque. Ahora podemos utilizar el proveedor en toda la aplicación.
Aplicando el Proveedor de Base de Datos dentro de la Aplicación Angular 2
Para mantener las cosas simples, esta será una aplicación de una sola página. Vamos a pasar el resto de nuestro tiempo en el proyecto de src/app/app.component.ts y src/app/app.component.html archivos.
Abra el archivo src/app/app.component.ts e incluya el siguiente código TypeScript:
|
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 |
import { Component, OnInit, NgZone } from '@angular/core'; import { PouchDBService } from "./pouchdb.service"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { public people: Array; public form: any; public constructor(private database: PouchDBService, private zone: NgZone) { this.people = []; this.form = { "username": "", "firstname": "", "lastname": "" } } public ngOnInit() { } public insert() { } } |
Verás que estamos importando el archivo PouchDBService junto con algunas otras cosas. Las analizaremos a medida que las vayamos encontrando.
Dentro de la AppComponent tenemos un array público llamado gente que se vinculará a nuestra interfaz de usuario. Contendrá todos los datos que se guarden y sincronicen. La página formulario representará un objeto que contiene cada uno de los elementos de nuestro formulario. Podríamos dividirlo en cadenas, pero un objeto es más conveniente porque vamos a terminar guardando todo el objeto como un documento en Couchbase.
En el constructor inyectamos nuestro servicio PouchDB y NgZone. Utilizamos NgZone para refrescar la zona de Angular 2 que a veces se lía parda cuando se trabaja con eventos y otro tipo de listeners. No es gran cosa como verás pronto. Por último, el constructor inicializa nuestras dos variables.
Es una mala práctica cargar datos en el constructor por lo que en su lugar utilizaremos el método ngOnInit método:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public ngOnInit() { this.database.sync("https://localhost:4984/example"); this.database.getChangeListener().subscribe(data => { for(let i = 0; i { this.people.push(data.change.docs[i]); }); } }); this.database.fetch().then(result => { this.people = []; for(let i = 0; i { console.error(error); }); } |
La mayor parte de la aplicación grunt trabajo sucede por encima. En el código anterior estamos definiendo nuestro host remoto Sync Gateway y el nombre de la base de datos. No es necesario que coincida con la de nuestra base de datos local. A continuación, suscribirse a nuestro oyente y el bucle a través de los cambios a medida que llegan. Todos los cambios se añaden a la matriz pública dentro de un NgZone para que se refresquen en la pantalla.
Dado que los eventos de cambio sólo se activan cuando se producen, necesitamos obtener todos los documentos cuando inicializamos la aplicación por primera vez.
Esto nos lleva a la insertar método:
|
1 2 3 4 5 6 7 8 9 10 |
public insert() { if(this.form.username && this.form.firstname && this.form.lastname) { this.database.put(this.form.username, this.form); this.form = { "username": "", "firstname": "", "lastname": "" } } } |
Si los elementos de nuestro formulario no están en blanco, podemos guardar los datos como un documento en la base de datos. Una vez guardados, el evento de cambio se disparará y los datos se añadirán al array público y se mostrarán en pantalla.
Todo esto es perfecto entre la aplicación web y Couchbase Sync Gateway.
Prueba de conducción
Hay mucho que asimilar cuando se trata de esta guía de Angular 2 y PouchDB. I cargado un proyecto de trabajo en GitHub si quieres probarlo.
Descargue el proyecto y ejecute lo siguiente desde su Terminal o Símbolo del sistema:
|
1 |
npm install |
El comando anterior obtendrá todas las dependencias del proyecto. Asegúrese de actualizar el src/app/app.component.ts para que refleje el nombre de host correcto de su Sync Gateway y ya puede empezar.
El proyecto puede ejecutarse ejecutando:
|
1 |
ng serve |
Mientras sirve, se puede acceder al proyecto desde https://localhost:4200.
Conclusión
Acabas de ver cómo construir una sencilla aplicación web que sincroniza datos usando PouchDB y Couchbase. Esta es una aplicación Angular 2 que es un paso adelante de la guía que escribí el año pasado que utilizaba AngularJS 1.0.
Gracias por el tutorial, realmente lo aprecio. Sólo una rápida, estoy tratando de instalar "requieren" según el comando sugerido, pero no tener mucha suerte. Estoy corriendo Angular 5.
typings install dt~require -save -global
En "ng serve" obtengo lo siguiente:
"ERROR in src/app/services/pouchdb.service.ts(3,17): error TS2304: Cannot find name 'require'."
¿Alguna idea?
¡Estoy teniendo el mismo problema! Me las arreglé para encontrar algunos paquetes npm obsoletos, incluyendo el viejo angular-cli que necesitaba ser eliminado y reemplazado por @angular/cli, me pregunto si es algo similar porque cuando instalé las tipografías, recibí un aviso de que está obsoleto en favor del uso de @types. Lo intentaré...
PouchDB funciona con TypeScript ahora, sin usar require.js. Consulta la documentación de PouchDB: https://pouchdb.com/guides/setup-pouchdb.html#typescript
Pasos:
1. Instalar PoutchDB
> npm install pouchdb -save
2. Instalar tipos de PouchDB
> npm install pouchdb @types/pouchdb
3. Edite tsconfig.json y active las importaciones sintéticas por defecto
{
"compilerOptions": {
"allowSyntheticDefaultImports": true
}
}
4. Importa el PouchDB en tu clase Typescript: pouchdb.service.ts.
importar PouchDB de 'pouchdb';