Recentemente, fui anfitrião de um Encontro do Couchbase em Mountain View, Califórnia, sobre o tema do desenvolvimento de NativeScript, Angular e NoSQL. Com um convidado especial, TJ VanToll do Progress, discutimos o desenvolvimento de aplicativos móveis e como Couchbase pode ser incluído para armazenamento NoSQL e sincronização de dados.
Houve um bom comparecimento ao evento e, por solicitação popular, eu queria compartilhar e revisar o código usado para tornar possível o projeto Couchbase.
Supondo que você tenha o NativeScript CLI e o Xcode ou o Android SDK instalados e configurados em seu computador, podemos criar um novo projeto no prompt de comando ou no terminal:
1 |
tns create couchbase-project --ng |
O --ng
no comando acima indica que estamos criando um projeto Angular em vez de um projeto Core.
O projeto que criaremos consistirá em duas páginas e um serviço de dados.
No aplicativo, você poderá exibir uma lista de filmes armazenados no seu banco de dados, bem como adicionar filmes a esse banco de dados. Tudo isso é gerenciado por meio do serviço de dados. Com o Couchbase Sync Gateway disponível, a sincronização poderá ser feita.
Adicione os seguintes diretórios e arquivos ao seu novo projeto NativeScript:
1 2 3 4 5 6 7 8 |
mkdir -p app/components/create mkdir -p app/components/list mkdir -p app/providers toque em app/components/create/create.component.ts toque em app/components/create/create.component.html tocar em app/components/list/list.component.ts toque em app/components/list/list.component.html toque em app/providers/database.service.ts |
No desenvolvimento do Angular, cada componente terá um arquivo TypeScript e um arquivo HTML. Cada serviço terá apenas um arquivo TypeScript.
Vamos começar projetando nosso serviço de dados que tratará das interações com o banco de dados Couchbase Lite instalado localmente. Abra a seção app/providers/database.service.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 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
importar { Injectable, EventEmitter } de "@angular/core"; importar { Couchbase } de "nativescript-couchbase"; @Injetável() export class DatabaseService { banco de dados privado: qualquer um; pushReplicator privado: any; pullReplicator privado: any; listener privado: EventEmitter = new EventEmitter(); public constructor() { this.database = new Couchbase("movie-db"); this.database.createView("movies", "1", function(document, emitter) { Se (document.type == "movie") { emitter.emit(document._id, document); } }); } consulta pública(viewName: string): Array { return this.database.executeQuery(viewName); } public startReplication(gateway: string, bucket: string) { 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(changes); }); this.pushReplicator.start(); this.pullReplicator.start(); } public getDatabase() { return this.database; } public getChangeListener() { return this.listener; } } |
Há muita coisa acontecendo no serviço acima, portanto, devemos dividi-lo em partes.
Depois de importar todas as dependências de componentes e definir nossas variáveis, temos nosso construtor
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) { Se (document.type == "movie") { emitter.emit(document._id, document); } }); } |
No construtor
criamos e abrimos o banco de dados NoSQL do Couchbase, bem como criamos uma exibição que será usada para consultar os dados. A lógica da exibição diz que, quando consultada, retorne um par de valores-chave para cada documento que tenha uma propriedade chamada tipo
que é igual a filme
. Quaisquer outros documentos que não correspondam a essa condição não serão incluídos nos resultados.
1 2 3 |
consulta pública(viewName: string): Array { return this.database.executeQuery(viewName); } |
Ao consultar a exibição, receberemos uma matriz de resultados que podemos escolher para exibir na tela. Isso é algo que faremos no componente apropriado.
Para aproveitar o poder e a grandiosidade do Couchbase, queremos ter suporte à replicação/sincronização dentro do aplicativo. Dentro do aplicativo startReplication
temos o seguinte:
1 2 3 4 5 6 7 8 9 10 11 |
public startReplication(gateway: string, bucket: string) { 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(changes); }); this.pushReplicator.start(); this.pullReplicator.start(); } |
Se fornecermos as informações da nossa instância do Couchbase Sync Gateway, poderemos replicar os dados em ambas as direções continuamente. Como o aplicativo móvel nunca lê os dados remotos, configuramos um ouvinte de alterações quando os dados locais são alterados. Essas alterações são emitidas por meio de um emissor Angular.
Para podermos injetar esse serviço de dados em cada um de nossos componentes, precisamos importá-lo para a pasta @NgModule
bloco. Abra a seção app/app.module.ts e faça com que ele se pareça com o seguinte:
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 |
importar { NgModule, NO_ERRORS_SCHEMA } de "@angular/core"; importar { NativeScriptModule } de "nativescript-angular/nativescript.module"; importar { NativeScriptFormsModule } de "nativescript-angular/forms"; importar { AppRoutingModule } de "./app.routing"; importar { AppComponent } de "./app.component"; importar { ListComponent } de "./components/list/list.component"; importar { CreateComponent } de "./components/create/create.component"; importar { DatabaseService } de "./providers/database.service"; @NgModule({ bootstrap: [ Componente de aplicativo ], importações: [ NativeScriptModule, NativeScriptFormsModule, AppRoutingModule ], Declarações: [ AppComponent, ListComponent, CriarComponente ], provedores: [DatabaseService], schemas: [ NO_ERRORS_SCHEMA ] }) export class AppModule { } |
Observe que o serviço foi importado e incluído no provedores
matriz do @NgModule
bloco. Para nos anteciparmos, também importamos cada um dos componentes que estamos criando.
Agora, vamos nos concentrar no componente para adicionar novos filmes ao banco de dados. Abra a seção app/components/create/create.component.ts e inclua o seguinte 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 |
importar { Component } de "@angular/core"; importar { Location } de "@angular/common"; importar { DatabaseService } de "../../providers/database.service"; @Component({ moduleId: module.id, seletor: "ns-create", templateUrl: "create.component.html", }) exportar classe CreateComponent { banco de dados privado: qualquer um; comentários do público: qualquer um; public constructor(private location: Location, private couchbase: DatabaseService) { this.database = this.couchbase.getDatabase(); this.input = { "title": "", "gênero": "", "type": "movie" } } public save() { if(this.input.title && this.input.genre) { this.database.createDocument(this.input); this.location.back(); } } } |
No código acima, estamos injetando o Serviço de banco de dados
criado anteriormente junto com o Angular Localização
serviço. Usando o Serviço de banco de dados
podemos obter a instância do banco de dados e salvar o entrada
a ele quando o objeto salvar
é chamado. O método entrada
está vinculado a um formulário na interface do usuário.
A interface do usuário desse componente pode ser descrita na seção 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> |
No XML acima, observe os dois TextField
são vinculadas às tags entrada
por meio da variável Angular ngModel
atributos.
A parte final do aplicativo NativeScript envolve a listagem dos filmes que estão em nosso banco de dados. Abra a seção app/components/list/list.component.ts e inclua o seguinte 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 |
importar { Component, NgZone, OnInit } de "@angular/core"; importar { Location } de "@angular/common"; importar { isAndroid } de "platform"; importar { DatabaseService } de "../../providers/database.service"; @Component({ moduleId: module.id, seletor: "ns-list", templateUrl: "list.component.html", }) export class ListComponent implements OnInit { filmes públicos: Array; public constructor(private location: Location, private zone: NgZone, private couchbase: DatabaseService) { this.movies = []; } público 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); }); } }); } } |
Novamente, estamos injetando o Serviço de banco de dados
, Localização
e NgZone
nos serviços do componente construtor
método.
Nunca é uma boa ideia carregar dados no construtor
portanto, estamos usando o método ngOnInit
em vez disso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
público 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); }); } }); } |
Algumas coisas estão acontecendo na ngOnInit
método. Queremos carregar os dados do banco de dados, mas isso precisa ser feito de duas maneiras diferentes. Precisamos carregar os dados quando o aplicativo for aberto e precisamos carregar os dados ao navegar para trás a partir da tela de criação.
Porque o ngOnInit
não é acionado ao navegar para trás, precisamos assinar os eventos de localização. Em ambos os cenários, consultamos a exibição que criamos.
Como queremos suporte à sincronização, podemos chamar a função startReplication
e passe as informações do Sync Gateway. Se estiver testando localmente, certifique-se de fornecer as informações de host apropriadas para Android e iOS.
Enquanto estiver ouvindo as alterações, todos os dados que chegarem devem ser pesquisados por id e adicionados à lista.
A interface do usuário que combina com o componente para listar filmes pode ser encontrada no 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> |
No XML acima, temos um simples Visualização de lista
em que cada linha contém informações dos objetos que estamos armazenando no Couchbase.
Para encerrar o aplicativo NativeScript, precisamos corrigir nosso arquivo de roteamento, que é responsável pela navegação. Abra o arquivo app/app.routing.ts e inclua o seguinte TypeScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
importar { NgModule } de "@angular/core"; importar { NativeScriptRouterModule } de "nativescript-angular/router"; importar { Routes } de "@angular/router"; importar { ListComponent } de "./components/list/list.component"; importar { CreateComponent } de "./components/create/create.component"; const routes: Routes = [ { path: "", redirectTo: "/list", pathMatch: "full" }, { path: "list", component: ListComponent }, { path: "create", component: CreateComponent } ]; @NgModule({ importações: [NativeScriptRouterModule.forRoot(routes)], exportações: [NativeScriptRouterModule] }) export class AppRoutingModule { } |
No código acima, estamos apenas importando os dois componentes e listando-os como possíveis rotas dentro do aplicativo. Mais informações sobre roteamento com um aplicativo NativeScript com Angular podem ser encontradas em um artigo anterior que escrevi intitulado, Navegando em um aplicativo NativeScript com o Angular Router.
Lembre-se de que nosso projeto não consiste apenas no NativeScript, mas também no Sync Gateway, que é uma entidade separada. Precisamos definir um arquivo de configuração sobre como a sincronização deve funcionar.
Crie um arquivo chamado, sync-gateway-config.json e incluem o 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+"], "bancos de dados": { "movie-db": { "servidor": "walrus:data", "sync":` function (doc) { channel (doc.channels); } `, "users": { "GUEST": { "disabled": falso, "admin_channels": ["*"] } } } } } |
Ao iniciar o Couchbase Sync Gateway, a configuração acima deve ser usada. Ela é apenas um exemplo básico do que você pode fazer com a sincronização de dados.
Conclusão
Pessoalmente, acredito que NativeScript é uma excelente tecnologia para o desenvolvimento móvel. Ao usar o plug-in Couchbase suportado pela comunidade para NativeScript, você pode incluir NoSQL e suporte à sincronização de dados em seu aplicativo.
Se você não estiver registrado atualmente no Couchbase Vale do Silício e estiver na área de Mountain View, recomendo que reserve um momento para se registrar. Se estiver interessado em ver mais sobre o Couchbase com NativeScript em ação, dê uma olhada em um artigo anterior que escrevi sobre aqui.
Para obter mais informações sobre o Couchbase Lite, consulte o Portal do desenvolvedor do Couchbase.