Hace poco organicé un Meetup de Couchbase en Mountain View, California, sobre el tema de NativeScript, Angular y desarrollo NoSQL. Con un invitado especial, TJ VanToll de Progress, hablamos del desarrollo de aplicaciones móviles y de cómo Couchbase para el almacenamiento NoSQL y la sincronización de datos.
Hubo una buena asistencia al evento y, a petición popular, quise compartir y revisar el código utilizado para hacer posible el proyecto Couchbase.
Suponiendo que tienes el CLI NativeScript y Xcode o el SDK de Android instalado y configurado en tu máquina, podemos crear un nuevo proyecto desde el símbolo del sistema o Terminal:
1 |
tns create couchbase-project --ng |
En --ng
en el comando anterior indica que estamos creando un proyecto Angular en lugar de un proyecto Core.
El proyecto que crearemos constará de dos páginas y un servicio de datos.
Dentro de la aplicación podrás mostrar una lista de películas almacenadas en tu base de datos, así como añadir películas a esa base de datos. Todo esto se gestiona a través del servicio de datos. Con Couchbase Sync Gateway disponible, la sincronización podrá ocurrir.
Añade los siguientes directorios y archivos a tu nuevo proyecto NativeScript:
1 2 3 4 5 6 7 8 |
mkdir -p app/components/create mkdir -p app/componentes/lista mkdir -p app/proveedores toca app/components/create/create.component.ts toca app/components/create/create.component.html toca app/components/list/list.component.ts toca app/components/list/list.component.html toca app/providers/database.service.ts |
En el desarrollo Angular, cada componente tendrá un archivo TypeScript y HTML. Cada servicio solo tendrá un archivo TypeScript.
Empecemos diseñando nuestro servicio de datos que manejará las interacciones con la base de datos Couchbase Lite instalada localmente. Abre el archivo app/providers/database.service.ts e incluya lo siguiente:
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import { Injectable, EventEmitter } from "@angular/core"; import { Couchbase } from "nativescript-couchbase"; @Inyectable() exportar clase DatabaseService { base de datos privada: cualquiera; private pushReplicator: any; privado pullReplicator: any; privado listener: EventEmitter = new EventEmitter(); public constructor() { this.database = new Couchbase("movie-db"); this.database.createView("movies", "1", function(document, emitter) { if(document.type == "movie") { emitter.emit(document._id, document); } }); } public consulta(nombreVista: cadena): Array { return this.database.executeQuery(viewName); } public startReplication(gateway: cadena, bucket: cadena) { this.pushReplicator = this.database.createPushReplication("http://" + gateway + ":4984/" + bucket); this.pullReplicator = this.database.createPullReplication("http://" + gateway + ":4984/" + bucket); this.pushReplicator.setContinuous(true); this.pullReplicator.setContinuous(true); this.database.addDatabaseChangeListener(changes => { this.listener.emit(cambios); }); this.pushReplicator.start(); this.pullReplicator.start(); } public getDatabase() { return esta.base.datos; } public getChangeListener() { return this.listener; } } |
En el servicio anterior ocurren muchas cosas, así que deberíamos desglosarlas.
Después de importar todas las dependencias de los componentes y definir nuestras variables, tenemos nuestro constructor
método:
1 2 3 4 5 6 7 8 |
public constructor() { this.database = new Couchbase("movie-db"); this.database.createView("movies", "1", function(document, emitter) { if(document.type == "movie") { emitter.emit(document._id, document); } }); } |
En el constructor
creamos y abrimos la base de datos NoSQL de Couchbase y creamos una vista que se utilizará para consultar los datos. La lógica de la vista dice que cuando se consulte, devuelva un par clave-valor por cada documento que tenga una propiedad llamada tipo
que es igual a película
. Cualquier otro documento que no cumpla esta condición no se incluirá en los resultados.
1 2 3 |
public consulta(nombreVista: cadena): Array { return this.database.executeQuery(viewName); } |
Al consultar la vista, recibiremos un array de resultados que podemos elegir para mostrar en pantalla. Esto es algo que haremos en el componente apropiado.
Para aprovechar el poder y la maravilla de Couchbase, queremos tener soporte de replicación / sincronización dentro de la aplicación. Dentro de la startReplication
tenemos lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 |
public startReplication(gateway: cadena, bucket: cadena) { this.pushReplicator = this.database.createPushReplication("http://" + gateway + ":4984/" + bucket); this.pullReplicator = this.database.createPullReplication("http://" + gateway + ":4984/" + bucket); this.pushReplicator.setContinuous(true); this.pullReplicator.setContinuous(true); this.database.addDatabaseChangeListener(changes => { this.listener.emit(cambios); }); this.pushReplicator.start(); this.pullReplicator.start(); } |
Si proporcionamos la información de nuestra instancia de Couchbase Sync Gateway, podemos replicar los datos en ambas direcciones continuamente. Como la aplicación móvil nunca lee los datos remotos, configuramos un escuchador de cambios cuando los datos locales cambian. Estos cambios se emiten a través de un emisor Angular.
Para poder inyectar este servicio de datos en cada uno de nuestros componentes, tenemos que importarlo en el directorio del proyecto @NgModule
bloque. Abra el archivo app/app.module.ts y que tenga el siguiente aspecto:
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 28 29 30 |
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; import { NativeScriptModule } from "nativescript-angular/nativescript.module"; import { NativeScriptFormsModule } de "nativescript-angular/forms"; import { AppRoutingModule } from "./app.routing"; import { AppComponent } from "./app.component"; import { ListComponent } from "./components/list/list.component"; import { CrearComponente } from "./componentes/crear/crear.componente"; import { DatabaseService } from "./providers/database.service"; @NgModule({ bootstrap: [ AppComponent ], importaciones: [ Módulo NativeScript, Módulo NativeScriptForms, AppRoutingModule ], declaraciones: [ AppComponent, ListComponent, CrearComponente ], proveedores: [DatabaseService], esquemas: [ NO_ERRORS_SCHEMA ] }) export class AppModule { } |
Observe que el servicio se ha importado e incluido en el archivo proveedores
de la @NgModule
bloque. Adelantándonos, también hemos importado cada uno de los componentes que estamos creando.
Ahora pasemos al componente para añadir nuevas películas a la base de datos. Abra el proyecto app/components/create/create.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 28 29 30 31 |
import { Component } from "@angular/core"; import { Location } from "@angular/common"; import { DatabaseService } from "../../providers/database.service"; @Componente({ moduleId: módulo.id, selector: "ns-create", templateUrl: "crear.componente.html", }) export class CrearComponente { base de datos privada: cualquiera; aportaciones del público: cualquiera; public constructor(private location: Ubicación, private couchbase: ServicioBaseDatos) { this.database = this.couchbase.getDatabase(); this.input = { "título": "", "género": "", "tipo": "película" } } public guardar() { if(this.input.title && this.input.genre) { this.database.createDocument(this.input); this.location.back(); } } } |
En el código anterior estamos inyectando el archivo Servicio de base de datos
creado previamente junto con el Angular Ubicación
servicio. Utilizando el Servicio de base de datos
podemos obtener la instancia de la base de datos y guardar el entrada
objeto cuando el guardar
. La dirección entrada
está vinculado a un formulario de la interfaz de usuario.
La interfaz de usuario de este componente puede describirse en el proyecto app/components/create/create.component.html file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<ActionBar title="{N} Couchbase Example"> <ActionItem text="Save" ios.position="right" (tap)="save()"></ActionItem> </ActionBar> <StackLayout class="form"> <StackLayout class="input-field"> <TextField class="input" [(ngModel)]="input.title"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <TextField class="input" [(ngModel)]="input.genre"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> </StackLayout> |
En el XML anterior, observe los dos Campo de texto
están vinculadas a las etiquetas entrada
mediante la variable Angular ngModel
atributos.
La parte final de la aplicación NativeScript consiste en listar las películas que están en nuestra base de datos. Abre el archivo app/components/list/list.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 28 29 30 31 32 33 34 35 36 |
import { Component, NgZone, OnInit } from "@angular/core"; import { Location } from "@angular/common"; import { isAndroid } from "plataforma"; import { DatabaseService } from "../../providers/database.service"; @Componente({ moduleId: módulo.id, selector: "ns-list", templateUrl: "list.component.html", }) export class ListComponent implements OnInit { películas públicas: Array; public constructor(private localización: Location, private zone: NgZone, privado couchbase: DatabaseService) { this.movies = []; } public ngOnInit() { this.location.subscribe(() => { this.movies = this.couchbase.query("movies"); }); this.movies = this.couchbase.query("movies"); this.couchbase.startReplication(isAndroid ? "10.0.2.2" : "localhost", "movie-db"); this.couchbase.getChangeListener().subscribe(data => { for (let i = 0; i < data.length; i++) { let documentId = data[i].getDocumentId(); let document = this.couchbase.getDatabase().getDocument(documentId); this.zone.run(() => { this.movies.push(document); }); } }); } } |
De nuevo, estamos inyectando el Servicio de base de datos
, Ubicación
y NgZone
en el componente constructor
método.
Nunca es una buena idea cargar datos en el constructor
por lo que utilizaremos el método ngOnInit
en su lugar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public ngOnInit() { this.location.subscribe(() => { this.movies = this.couchbase.query("movies"); }); this.movies = this.couchbase.query("movies"); this.couchbase.startReplication(isAndroid ? "10.0.2.2" : "localhost", "movie-db"); this.couchbase.getChangeListener().subscribe(data => { for (let i = 0; i < data.length; i++) { let documentId = data[i].getDocumentId(); let document = this.couchbase.getDatabase().getDocument(documentId); this.zone.run(() => { this.movies.push(document); }); } }); } |
Están ocurriendo algunas cosas en el ngOnInit
método. Queremos cargar los datos de la base de datos, pero hay que hacerlo de dos maneras diferentes. Necesitamos cargar los datos al abrir la aplicación y necesitamos cargar los datos al navegar hacia atrás desde la pantalla de creación.
Porque el ngOnInit
no se dispara al navegar hacia atrás, necesitamos suscribirnos a los eventos de localización. En ambos escenarios consultamos la vista que habíamos creado.
Dado que queremos soporte de sincronización, podemos llamar a la función startReplication
y pase la información de la puerta de enlace de sincronización. Si está realizando pruebas localmente, asegúrese de proporcionar la información de host adecuada para Android e iOS.
Mientras se escuchan los cambios, cualquier dato que llegue debe buscarse por id y añadirse a la lista.
La interfaz de usuario que se empareja con el componente para listar películas se puede encontrar en la sección app/components/list/list.component.html file:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<ActionBar title="{N} Couchbase Example"> <ActionItem text="Add" ios.position="right" [nsRouterLink]="['/create']"></ActionItem> </ActionBar> <GridLayout> <ListView [items]="movies" class="list-group"> <ng-template let-movie="item"> <StackLayout class="list-group-item"> </StackLayout> </ng-template> </ListView> </GridLayout> |
En el XML anterior, tenemos un simple ListView
donde cada fila contiene información de los objetos que estamos almacenando en Couchbase.
Llevando la aplicación NativeScript a su fin, tenemos que arreglar nuestro archivo de enrutamiento que es responsable de la navegación. Abre el archivo app/app.routing.ts e incluir el siguiente TypeScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { NgModule } from "@angular/core"; import { NativeScriptRouterModule } from "nativescript-angular/router"; import { Rutas } from "@angular/router"; import { ListComponent } from "./components/list/list.component"; import { CrearComponente } from "./componentes/crear/crear.componente"; const rutas: Rutas = [ { ruta: "", redirectTo: "/list", pathMatch: "full" }, { ruta: "lista", component: ListComponent }, { ruta: "crear", componente: CreateComponent } ]; @NgModule({ importaciones: [NativeScriptRouterModule.forRoot(routes)], exportaciones: [NativeScriptRouterModule] }) export class AppRoutingModule { } |
En el código anterior sólo estamos importando los dos componentes y listándolos como posibles rutas dentro de la aplicación. Más información sobre el enrutamiento con una aplicación NativeScript con Angular se puede encontrar en un artículo anterior que escribí titulado, Navegación de una aplicación NativeScript con el router Angular.
Recuerde, nuestro proyecto no sólo consiste en NativeScript, sino que también consiste en Sync Gateway que es una entidad separada. Tenemos que definir un archivo de configuración sobre cómo debe funcionar la sincronización.
Crea un archivo llamado, sync-gateway-config.json e incluyen lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ "log":["CRUD+", "REST+", "Changes+", "Attach+"], "bases de datos": { "movie-db": { "servidor": "morsa:datos", "sync":` function (doc) { canal (doc.canales); } `, "usuarios": { "INVITADO": { "disabled": falso, "admin_channels": ["*"] } } } } } |
Al lanzar Couchbase Sync Gateway, se debe utilizar la configuración anterior. Es sólo un ejemplo básico de lo que puedes lograr con la sincronización de datos.
Conclusión
Personalmente creo NativeScript es una gran tecnología para el desarrollo móvil. Al utilizar el plugin de Couchbase para NativeScript, que cuenta con el apoyo de la comunidad, puedes incluir NoSQL y soporte de sincronización de datos dentro de tu aplicación.
Si no está registrado en el Couchbase Silicon Valley y estás en la zona de Mountain View, te recomiendo que te tomes un momento para registrarte. Si estás interesado en ver más Couchbase con NativeScript en acción, echa un vistazo a un artículo anterior que escribí sobre aquí.
Para obtener más información sobre Couchbase Lite, consulte la página Portal para desarrolladores de Couchbase.