Estou em uma função quando se trata de JavaScript e Couchbase. Nas últimas semanas, criei conteúdo sobre a estrutura Angular 2, a biblioteca JavaScript PouchDB e a estrutura Ionic para dispositivos móveis. O que eu ainda não explorei foi o Couchbase em um aplicativo de desktop criado com o Angular 2.
Há dois anos, escrevi sobre o uso de Couchbase em um aplicativo de desktop usando AngularJS 1.0 e ElectronMas, com a tecnologia, poderia muito bem ter havido dinossauros naquele período. Muita coisa mudou e essa postagem, que já foi excelente, merece uma atualização.
Veremos como criar um aplicativo de desktop usando Elétrons que é alimentado por Angular 2, PouchDB, Ionic 2 e Couchbase.
Os requisitos
Há vários requisitos que devem ser atendidos para que esse projeto seja bem-sucedido. Eles são os seguintes:
- Node.js 4.0+
- Estrutura iônica 2.0
- Gateway de sincronização do Couchbase
O foco e o objetivo deste tutorial não é o Ionic 2. No entanto, o Ionic 2 tem uma camada de interface do usuário muito boa que nos poupará tempo em relação a soluções alternativas como Bootstrap ou Foundation. O Ionic 2 também tem o Angular 2 incorporado. Dito isso, precisaremos instalar a CLI do Ionic 2. O Node.js é um requisito por causa do Node Package Manager (NPM) que usaremos para reunir dependências.
Não incluiremos o Couchbase Server neste exemplo, mas sim o Couchbase Sync Gateway e seu banco de dados de prototipagem na memória. Não é preciso mais do que uma única linha para mudar para o Couchbase Server a partir deste exemplo. O PouchDB se comunicará do nosso aplicativo de desktop com o Sync Gateway e também na outra direção.
Configuração do Couchbase Sync Gateway
O Sync Gateway lida com toda a orquestração de dados e precisa ser configurado de acordo com o aplicativo. Para esse aplicativo específico, permitiremos que todos os dispositivos conectados possam ler e gravar dados.
Essa configuração seria parecida com a seguinte:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ "log":["CRUD+", "REST+", "Changes+", "Attach+"], "databases": { "example": { "server":"walrus:", "sync":` function (doc) { channel (doc.channels); } `, "users": { "GUEST": { "disabled": false, "admin_channels": ["*"] } } } } } |
Na configuração de exemplo acima, usaremos um banco de dados chamado exemplomas ele pode ser chamado como você quiser. A configuração deve ser salva em um arquivo, por exemplo, sync-gateway-config.json.
Com o download e a instalação do Couchbase Sync Gateway, a configuração pode ser executada da seguinte forma:
|
1 |
/path/to/sync_gateway /path/to/sync-gateway-config.json |
O Sync Gateway pode então ser acessado em https://localhost:4985/_admin/.
Criando um projeto Ionic 2 destinado ao PC
Se você viu meu guia anterior sobre o tópico de Couchbase com Ionic 2 e PouchDBPor isso, você deve estar se perguntando o que será diferente aqui. A verdade é que, na verdade, nada. Os aplicativos Ionic 2 que não usam recursos nativos do Android e do iOS podem ser agrupados em aplicativos Electron sem problemas. No entanto, foram feitas algumas otimizações nesse aplicativo em comparação com o anterior.

Na animação acima, você pode ver um aplicativo Electron de lista de tarefas simples em que é possível adicionar itens à lista e sincronizá-los com o Couchbase Sync Gateway e, eventualmente, com outros dispositivos e o Couchbase Server.
Vamos começar a criar esse exemplo simples.
Criando um novo projeto com as dependências
Antes de começarmos a desenvolver o aplicativo e empacotá-lo no Electron, precisamos criar um novo projeto com cada uma das dependências.
No prompt de comando ou no terminal, execute o seguinte:
|
1 2 |
ionic start ElectronExample blank --v2 cd ElectronExample |
Com o projeto Ionic 2 criado, precisamos reunir algumas dependências, como Electron e PouchDB. Para fazer isso, execute o seguinte:
|
1 2 3 |
npm install pouchdb --save npm install uuid @types/uuid --save npm install electron --save-dev |
Como este aplicativo Angular 2 usará TypeScript, é melhor usar bibliotecas JavaScript com definições de tipo. No entanto, o PouchDB não tem um conjunto oficial de definições de tipo, o que o torna desatualizado. Podemos contornar isso incluindo a seguinte dependência:
|
1 |
npm install @types/node --save |
A dependência acima nos dará acesso ao exigir para que possamos importar bibliotecas JavaScript para o nosso projeto.
Adição do Electron para suporte de desktop
Com o projeto criado, precisamos adicionar suporte ao Electron. Isso é feito adicionando um arquivo de configuração JavaScript especial que o Electron processa na inicialização.
Na raiz de seu projeto, crie um arquivo chamado electron.js com o seguinte código JavaScript:
|
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 |
const electron = require('electron') const app = electron.app const BrowserWindow = electron.BrowserWindow const path = require('path') const url = require('url') let mainWindow function createWindow () { mainWindow = new BrowserWindow({width: 800, height: 600}) mainWindow.loadURL(url.format({ pathname: path.join(__dirname, 'www/index.html'), protocol: 'file:', slashes: true })) mainWindow.on('closed', function () { mainWindow = null }) } app.on('ready', createWindow) app.on('window-all-closed', function () { if (process.platform !== 'darwin') { app.quit() } }) app.on('activate', function () { if (mainWindow === null) { createWindow() } }) |
A maior parte do código acima foi extraída diretamente do Modelo inicial do Electroncom exceção desta linha:
|
1 |
pathname: path.join(__dirname, 'www/index.html'), |
Na linha acima, estamos informando ao Electron qual página do Ionic 2 deve ser carregada quando o aplicativo for iniciado. Enquanto o bootstrapping do Electron é feito, precisamos agrupá-lo em nossos scripts de compilação.
Abra o arquivo package.json e inclua essa linha:
|
1 |
"main": "electron.js", |
A linha acima informa ao Electron qual arquivo é o arquivo de configuração. Isso é necessário porque talvez você não tenha nomeado seu arquivo electron.js como eu fiz. Também precisamos adicionar um script específico que criará o projeto e o iniciará com o Electron:
|
1 2 3 4 5 |
"scripts": { "ionic:build": "ionic-app-scripts build", "ionic:serve": "ionic-app-scripts serve", "electron": "ionic-app-scripts build; electron ." }, |
Ele não empacotará o aplicativo para implantação em uma loja de aplicativos, mas permitirá que o testemos corretamente com o Electron em nosso computador.
Neste ponto, podemos nos concentrar no código do Angular 2, que você pode ou não ter visto antes.
Desenvolvimento de um provedor PouchDB para Angular 2
É uma boa prática manter o código relacionado aos dados separado da lógica da página. No Angular 2, podemos realizar essa separação por meio do uso de um provedor compartilhado.
Para criar um provedor no Ionic 2, execute o seguinte na CLI:
|
1 |
ionic g provider pouchdb-provider |
Você deve terminar com src/providers/pouchdb-provider.ts ou similar. O nome não é realmente importante, desde que você se lembre do que se trata.
Abra o arquivo do provedor e inclua o seguinte TypeScript:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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 get(id: string) { } public put(document: any, id: string) { } public sync(remote: string) { } public getChangeListener() { } } |
O provedor será injetado em várias páginas e emitirá alterações, portanto, o Injetável e Emissor de eventos importações. Também estamos importando a biblioteca JavaScript do PouchDB.
O provedor atuará como um singleton com uma instância de banco de dados aberta durante o período em que o nosso aplicativo estiver aberto. Essa configuração pode ser criada na seção construtor método:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
public constructor() { if(!this.isInstantiated) { this.database = new PouchDB("nraboy"); this.database.changes({ live: true, include_docs: true }).on('change', change => { this.listener.emit(change); }); this.isInstantiated = true; } } |
No construtor Se o banco de dados ainda não estiver instanciado, vamos instanciá-lo, abri-lo e configurar os eventos de alteração. Para cada alteração no banco de dados, nós os emitiremos e, por fim, os coletaremos assinando o ouvinte.
Digamos que queiramos obter um determinado documento NoSQL por seu ID. Podemos criar uma função como a seguinte:
|
1 2 3 |
public get(id: string) { return this.database.get(id); } |
Para esse aplicativo específico, o método acima é mais útil para o nosso método de criação de documentos, pois queremos ver se um documento existe antes de tentar atualizá-lo:
|
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); }); } }); } |
Se o documento não existir, ele será criado em vez de atualizado.
Como o objetivo aqui é usar o Couchbase em nosso aplicativo Electron para desktop, precisamos ter uma função de sincronização:
|
1 2 3 4 5 6 7 |
public sync(remote: string) { let remoteDatabase = new PouchDB(remote); this.database.sync(remoteDatabase, { live: true, retry: true }); } |
O sincronização O método Sync Gateway utilizará o nome do host e o banco de dados e fará uma sincronização bidirecional entre o banco de dados local e o banco de dados remoto.
|
1 2 3 |
public getChangeListener() { return this.listener; } |
Por fim, fornecemos uma maneira de obter e assinar o ouvinte de alterações.
O provedor PouchDB está pronto, mas não está pronto para ser usado. Para usá-lo em cada uma de nossas páginas, precisamos adicioná-lo à seção @NgModule encontrado no bloco src/app/app.module.ts arquivo. Abra esse arquivo e inclua o seguinte:
|
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 {} |
Essencialmente, apenas importamos o provedor e o adicionamos ao provedores matriz do @NgModule bloco. Agora o provedor pode ser usado em nossas páginas de aplicativos.
Adição da lógica de página para um aplicativo funcional
Esse aplicativo específico tem apenas uma tela e essa tela é bastante simples. Mostrar uma lista de dados e permitir a entrada de novos dados.
Começando com a lógica do TypeScript, abra o arquivo src/pages/home/home.ts e inclua o seguinte:
|
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 zone: NgZone, private database: PouchDBProvider) { this.items = []; } public ionViewDidEnter() { } public insert() { } } |
Importamos vários componentes Angular 2, Ionic 2 e personalizados para a página e injetamos muitos deles no construtor método. O itens manterá nossos dados sincronizados que serão exibidos na tela. A matriz construtor apenas inicializa nossas variáveis.
Para carregar dados em nossas variáveis, devemos usar o ionViewDidEnter método:
|
1 2 3 4 5 6 7 8 |
public ionViewDidEnter() { this.database.sync("https://192.168.57.1:4984/example"); this.database.getChangeListener().subscribe(data => { this.zone.run(() => { this.items.push(data.doc); }); }); } |
No ionViewDidEnter estamos iniciando a sincronização com nosso Sync Gateway e assinando os eventos de alteração. À medida que as alterações forem chegando, elas serão adicionadas ao itens array. Estamos usando NgZone porque os ouvintes de alterações são duvidosos no Angular 2 e queremos garantir que a interface do usuário seja atualizada corretamente.
O inserir é praticamente toda a lógica do Ionic 2:
|
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 |
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 => { if(data.title) { this.database.put({type: "list", title: data.title}, Uuid.v4()); } } } ] }); prompt.present(); } |
Quando o método for chamado, será exibido um prompt que permitirá a entrada do usuário. Quando salvos, os dados serão salvos como um documento no PouchDB e sincronizados com o Couchbase Server.
Projetando a interface do usuário
A interface do usuário por trás da lógica TypeScript é curta e simples. Abra a seção src/pages/home/home.html e inclua a seguinte marcação 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> Electron w/ Couchbase </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> |
A interface do usuário tem uma barra de ação com um botão que executará o inserir quando pressionado. O conteúdo da tela é uma lista que exibe cada elemento da lista itens como uma linha.
Vendo o projeto em ação
Fizemos muitas coisas no exemplo do Electron com o Couchbase. Eu fez o upload de um projeto funcional para o GitHub se você quiser dar uma olhada nele.
Faça o download do projeto e execute o seguinte comando:
|
1 |
npm install |
O comando acima restaura todas as dependências do projeto. Com o Sync Gateway em execução, execute o seguinte para executar o aplicativo com o Electron:
|
1 |
npm run electron |
Observe que você provavelmente terá que alterar o host do Sync Gateway no arquivo src/pages/home/home.ts para corresponder ao seu nome de host.
Conclusão
Você acabou de ver como criar um aplicativo de desktop que sincroniza com Couchbase. Este aplicativo é alimentado por Elétronsmas usa o Angular 2 e o PouchDB. Embora a camada de interface do usuário seja do Ionic 2, ela não precisa ser assim. Você poderia usar sua própria estrutura de interface do usuário, como o Bootstrap ou algo semelhante. O objetivo aqui era mais demonstrar o Electron com o Couchbase e o Angular 2. Esse guia foi um passo à frente do meu anterior sobre o tópico de AngularJS 1.0 com Couchbase e Electron.
Ao executar
npm run electronO resultado é um erro:
[19:46:53] ionic-app-scripts 0.0.47
[19:46:53] tarefa ionic-app-script: "build;"
[19:46:53] Erro: Não é possível encontrar o módulo '../dist/build;'
(E isso é do seu próprio exemplo do github, baixei, instalei o npm e executei o npm electron).
Alguma ideia?
Obrigado :)
Oi Martín e Nic,
Estou recebendo o mesmo erro, usando o Ionic 2.1.12
[15:44:02] scripts-app-iônicos 1.1.4
[15:44:02] tarefa ionic-app-script: "build;"
[15:44:02] Erro: Não é possível encontrar o módulo '../dist/build;'
Algum de vocês resolveu o problema?
Obrigado :)
Que versão do Ionic você está usando? Acredito que o Ionic sofreu grandes alterações entre as versões atual e a que eu publiquei.
O conteúdo e a lógica devem permanecer corretos, mas o comando de compilação pode ser ligeiramente diferente.
Hi,
Primeiramente, obrigado Nic por esta postagem!
Recebi o mesmo erro ("Error: Cannot find module '../dist/build;'"), e acho que é porque o script "electron" supõe que o módulo npm do electron está instalado globalmente.
Com o Electron instalado localmente, você deve usar algo como isto:
"./node_modules/.bin/electron ."
Veja https://electron.atom.io/docs/tutorial/quick-start/
Então, alterei o script do elétron com o seguinte (estou no sistema operacional Windows):
"scripts": {
…
"electron": "ionic-app-scripts build && .\\node_modules\\.bin\\electron ."
}
Finalmente, a execução de "npm run electron" funciona para mim!