Continuando no caminho dos armazenamentos de perfis de usuários, vimos anteriormente como criar um com Node.js e Couchbase NoSQL bem como um front-end de cliente web para ele usando o Angular. E se quisermos levar isso para um aplicativo móvel, que é a norma para todos os aplicativos atualmente.
Há muitas estruturas móveis diferentes por aí, e temos a sorte de que algumas até suportam o Angular, que foi o que usamos no exemplo anterior. Veremos como converter o front-end do nosso cliente para dispositivos móveis usando NativeScript e Angular.
Antes de prosseguir, presume-se que você tenha concluído completamente os dois tutoriais anteriores, um sobre Criação do back-end do armazenamento de perfis e o outro em Criação do front-end da Web do Profile Store. Também assumiremos que seu ambiente de desenvolvimento está configurado para desenvolvimento móvel, seja Android, iOS ou ambos.

O fluxo de eventos nesse aplicativo corresponderá ao que vimos na versão da Web.
Criar um novo projeto NativeScript com Angular
Supondo que você tenha instalado e configurado a CLI do NativeScript, execute o seguinte para criar um novo projeto:
|
1 |
tns create profile-project-ns --ng |
O --ng no comando acima é importante porque significa que estamos criando um projeto Angular em vez de um projeto NativeScript Core.
Até o momento, a CLI do NativeScript não compartilha os recursos de geração de componentes da CLI do Angular. Por esse motivo, teremos que criar cada um dos arquivos HTML e TypeScript manualmente.
Se você estiver no Mac ou no Linux, execute o seguinte no projeto NativeScript:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
mkdir -p app/login mkdir -p app/register mkdir -p app/blogs mkdir -p app/blog touch app/login/login.component.html touch app/login/login.component.ts touch app/register/register.component.html touch app/register/register.component.ts touch app/blogs/blogs.component.html touch app/blogs/blogs.component.ts touch app/blog/blog.component.html touch app/blog/blog.component.ts |
Se você estiver no Windows, basta criar esses diretórios e arquivos manualmente. Se realmente quiser, você pode copiar esses diretórios e arquivos do seu projeto da Web a partir do diretório tutorial anterior.
Definição dos componentes para representar cada tela
Começando na mesma direção que a versão Web, vamos nos concentrar no login do usuário. Abra a seção app/login/login.component.ts e inclua o seguinte 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 24 25 26 27 28 29 30 31 32 33 34 |
import { Component } from '@angular/core'; import { Http, Headers, RequestOptions } from "@angular/http"; import { Router } from "@angular/router"; import "rxjs/Rx"; @Component({ moduleId: module.id, selector: 'app-login', templateUrl: './login.component.html' }) export class LoginComponent { public input: any; constructor(private http: Http, private router: Router) { this.input = { "email": "", "password": "" }; } public login() { if(this.input.email && this.input.password) { let headers = new Headers({ "content-type": "application/json" }); let options = new RequestOptions({ headers: headers }); this.http.post("https://localhost:3000/login", JSON.stringify(this.input), options) .map(result => result.json()) .subscribe(result => { this.router.navigate(["/blogs"], { "queryParams": result }); }); } } } |
O código acima é exatamente o que vimos na versão da Web, com duas exceções. Removi a referência CSS, pois não criamos um arquivo CSS com base em um componente. Também adicionei um moduleId para que os caminhos relativos pudessem funcionar com nosso componente. Esses dois itens estão relacionados ao Angular e não têm nada a ver com o NativeScript.
É no HTML que as coisas são diferentes. Abra o arquivo app/login/login.component.html e inclua a seguinte marcação XML:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<ActionBar title="{N} Profile Store"></ActionBar> <StackLayout class="form"> <StackLayout class="input-field"> <Label text="Email" class="label font-weight-bold m-b-5"></Label> <TextField class="input" [(ngModel)]="input.email" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Password" class="label font-weight-bold m-b-5"></Label> <TextField class="input" secure="true" [(ngModel)]="input.password" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Button class="btn btn-primary" text="Login" (tap)="login()"></Button> </StackLayout> <StackLayout class="input-field"> <Label text="Register here." [nsRouterLink]="['/register']" class="text-center"></Label> </StackLayout> </StackLayout> |
O NativeScript tem sua própria marcação para a interface do usuário. As mesmas regras se aplicam em termos de Angular, mas a criação de componentes de IU é um pouco diferente.
Por exemplo, ainda estamos usando o [(ngModel)] mas em vez de atributos div tags, temos Layout de pilha tags.
Agora vamos dar uma olhada no componente de registro. Abra a seção app/register/register.component.ts 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 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import { Component } from '@angular/core'; import { Http, Headers, RequestOptions } from "@angular/http"; import { Router } from "@angular/router"; import "rxjs/Rx"; @Component({ moduleId: module.id, selector: 'app-register', templateUrl: './register.component.html' }) export class RegisterComponent { public input: any; public constructor(private http: Http, private router: Router) { this.input = { "firstname": "", "lastname": "", "email": "", "password": "" }; } public register() { if(this.input.email && this.input.password) { let headers = new Headers({ "content-type": "application/json" }); let options = new RequestOptions({ headers: headers }); this.http.post("https://localhost:3000/account", JSON.stringify(this.input), options) .map(result => result.json()) .subscribe(result => { this.router.navigate(["/login"]); }); } } } |
Novamente, as únicas alterações que fizemos no código acima, em comparação com o exemplo anterior, foram a remoção do CSS e o moduleId Além disso.
Nada mal quando se trata de criar aplicativos móveis e da Web entre plataformas, certo?
O HTML da interface do usuário que alimenta a lógica do TypeScript é encontrado no app/register/register.component.html e ele tem a seguinte aparência:
|
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 |
<ActionBar title="{N} Profile Store"></ActionBar> <StackLayout class="form"> <StackLayout class="input-field"> <Label text="First Name" class="label font-weight-bold m-b-5"></Label> <TextField class="input" [(ngModel)]="input.firstname" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Last Name" class="label font-weight-bold m-b-5"></Label> <TextField class="input" [(ngModel)]="input.lastname" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Email" class="label font-weight-bold m-b-5"></Label> <TextField class="input" [(ngModel)]="input.email" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Password" class="label font-weight-bold m-b-5"></Label> <TextField class="input" secure="true" [(ngModel)]="input.password" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Button class="btn btn-primary" text="Register" (tap)="register()"></Button> </StackLayout> <StackLayout class="input-field"> <Label text="Login here." [nsRouterLink]="['/login']" class="text-center"></Label> </StackLayout> </StackLayout> |
Os dois últimos componentes não serão diferentes do que já estamos experimentando.
Abra o arquivo app/blogs/blogs.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 37 38 39 40 41 42 43 44 45 46 47 |
import { Component, OnInit } from '@angular/core'; import { Http, Headers, RequestOptions } from "@angular/http"; import { Router, ActivatedRoute } from "@angular/router"; import { Location } from "@angular/common"; import "rxjs/Rx"; @Component({ moduleId: module.id, selector: 'app-blogs', templateUrl: './blogs.component.html' }) export class BlogsComponent implements OnInit { private sid: string; public entries: Array<any>; public constructor(private http: Http, private router: Router, private route: ActivatedRoute, private location: Location) { this.entries = []; } public ngOnInit() { this.location.subscribe(() => { let headers = new Headers({ "authorization": "Bearer " + this.sid }); let options = new RequestOptions({ headers: headers }); this.http.get("https://localhost:3000/blogs", options) .map(result => result.json()) .subscribe(result => { this.entries = result; }); }); this.route.queryParams.subscribe(params => { this.sid = params["sid"]; let headers = new Headers({ "authorization": "Bearer " + params["sid"] }); let options = new RequestOptions({ headers: headers }); this.http.get("https://localhost:3000/blogs", options) .map(result => result.json()) .subscribe(result => { this.entries = result; }); }); } public create() { this.router.navigate(["/blog"], { "queryParams": { "sid": this.sid } }); } } |
Não há outras alterações a serem vistas nas informações acima além das duas mencionadas anteriormente, portanto, podemos passar para o HTML da página.
Abra o arquivo app/blogs/blogs.component.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 |
<ActionBar title="{N} Profile Store"> <ActionItem text="New" ios.position="right" (tap)="create()"></ActionItem> <NavigationButton text="Back"></NavigationButton> </ActionBar> <GridLayout rows="*, auto" columns="*"> <ListView [items]="entries" class="list-group" row="0" col="0"> <ng-template let-entry="item"> <StackLayout class="list-group-item"> <Label text="{{ entry.title }}" class="h2"></Label> <Label text="{{ entry.content }}"></Label> </StackLayout> </ng-template> </ListView> <StackLayout row="1" col="0" padding="10" backgroundColor="#F0F0F0"> <Label text="Logout" [nsRouterLink]="['/login']"></Label> </StackLayout> </GridLayout> |
Vamos encerrar este aplicativo com o componente final que a API do armazenamento de perfis e o front-end da Web têm a oferecer.
Abra o arquivo app/blog/blog.component.ts e inclua isso:
|
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 { Component, OnInit } from '@angular/core'; import { Http, Headers, RequestOptions } from "@angular/http"; import { ActivatedRoute } from "@angular/router"; import { Location } from "@angular/common"; import "rxjs/Rx"; @Component({ moduleId: module.id, selector: 'app-blog', templateUrl: './blog.component.html' }) export class BlogComponent implements OnInit { private sid: string; public input: any; public constructor(private http: Http, private route: ActivatedRoute, private location: Location) { this.input = { "title": "", "content": "" }; } public ngOnInit() { this.route.queryParams.subscribe(params => { this.sid = params["sid"]; }); } public save() { if(this.input.title && this.input.content) { let headers = new Headers({ "content-type": "application/json", "authorization": "Bearer " + this.sid }); let options = new RequestOptions({ headers: headers }); this.http.post("https://localhost:3000/blog", JSON.stringify(this.input), options) .map(result => result.json()) .subscribe(result => { this.location.back(); }); } } } |
Se você não copiou seu arquivo CSS, não se esqueça de removê-lo da pasta @Componente como visto nos componentes anteriores.
A interface do usuário em HTML para acompanhar esse TypeScript é encontrada na seção app/blog/blog.component.html e ele se parece com o seguinte:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<ActionBar title="{N} Profile Store"> <NavigationButton text="Back"></NavigationButton> </ActionBar> <StackLayout class="form"> <StackLayout class="input-field"> <Label text="Title" class="label font-weight-bold m-b-5"></Label> <TextField class="input" [(ngModel)]="input.title" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Content" class="label font-weight-bold m-b-5"></Label> <TextView class="input" [(ngModel)]="input.content" autocapitalizationType="none"></TextView> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Button class="btn btn-primary" text="Save" (tap)="save()"></Button> </StackLayout> </StackLayout> |
A partir de agora, você pode estar se perguntando sobre toda essa marcação XML do NativeScript. A forma como o Angular trabalha com a interface do usuário não mudou, mas se você estiver interessado em aprender sobre a marcação do NativeScript, dê uma olhada no site documentação oficial. Familiarize-se com o Layout de pilha, GridLayout e tags individuais de componentes da IU.
Reunindo tudo
Criamos todos esses componentes do Angular para o NativeScript, mas não os reunimos por meio do Angular Router.
Na versão web deste guia, as informações sobre a rota estavam na seção app.module.ts arquivo. Embora pudéssemos fazer isso, o NativeScript o dividiu em um arquivo separado.
Abra o arquivo app/app.routing.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 { NgModule } from "@angular/core"; import { NativeScriptRouterModule } from "nativescript-angular/router"; import { Routes } from "@angular/router"; import { LoginComponent } from "./login/login.component"; import { RegisterComponent } from "./register/register.component"; import { BlogsComponent } from "./blogs/blogs.component"; import { BlogComponent } from "./blog/blog.component"; const routes: Routes = [ { path: "", redirectTo: "/login", pathMatch: "full" }, { path: "login", component: LoginComponent }, { path: "register", component: RegisterComponent }, { path: "blogs", component: BlogsComponent }, { path: "blog", component: BlogComponent } ]; @NgModule({ imports: [NativeScriptRouterModule.forRoot(routes)], exports: [NativeScriptRouterModule] }) export class AppRoutingModule { } |
Grande parte do código acima veio com nosso novo modelo de projeto. Importamos cada um de nossos componentes e criamos uma rota para eles.
Da mesma forma, os componentes precisam ser importados na pasta 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 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; import { NativeScriptModule } from "nativescript-angular/nativescript.module"; import { AppRoutingModule } from "./app.routing"; import { NativeScriptHttpModule } from "nativescript-angular/http"; import { NativeScriptFormsModule } from "nativescript-angular/forms"; import { AppComponent } from "./app.component"; import { LoginComponent } from "./login/login.component"; import { RegisterComponent } from "./register/register.component"; import { BlogsComponent } from "./blogs/blogs.component"; import { BlogComponent } from "./blog/blog.component"; @NgModule({ bootstrap: [ AppComponent ], imports: [ NativeScriptModule, AppRoutingModule, NativeScriptHttpModule, NativeScriptFormsModule ], declarations: [ AppComponent, LoginComponent, RegisterComponent, BlogsComponent, BlogComponent ], providers: [], schemas: [ NO_ERRORS_SCHEMA ] }) export class AppModule { } |
Além de importar cada componente e adicioná-los ao declarações também importamos alguns módulos, como o NativeScriptHttpModule e NativeScriptFormsModule. No Angular puro, eles são chamados de HttpModule e FormsModule.
Em teoria, o aplicativo está pronto para ser usado.
Resolução de problemas de segurança de transporte de aplicativos (ATS) para iOS
Como o Node.js e a API do Couchbase estão sendo executados localmente, estamos usando HTTP em vez de HTTPS. O iOS emitirá erros se tentarmos acessar recursos HTTP.
Isso pode ser facilmente corrigido com a inclusão de uma política de ATS.
Abra o arquivo app/App_Resources/iOS/Info.plist e adicione o seguinte ao lado do outro XML:
|
1 2 3 4 |
<dict> <key>NSAllowsArbitraryLoads</key> <true /> </dict> |
Basicamente, o procedimento acima coloca todos os endpoints HTTP em uma lista branca. Não é seguro para produção, mas é seguro para testes.
Mais informações sobre o ATS em aplicativos NativeScript podem ser lidas em um artigo anterior que escrevi intitulado, Corrigir problemas de segurança de transporte de aplicativos do iOS 9 no NativeScript.
Conclusão
Você acabou de ver como foi fácil pegar o front-end do seu cliente Web e convertê-lo para dispositivos móveis usando NativeScript e Angular. O exemplo do armazenamento de perfil de usuário rapidamente se tornou um exemplo de pilha completa usando a pilha JavaScript. Tínhamos um Node.js com Servidor Couchbase back-end, front-end web Angular e front-end móvel NativeScript.
A próxima etapa, ou opção, seria usar os componentes do Couchbase Mobile em vez de chamadas HTTP para a API.
Olá,
Tentei executar esse código, mas o POST não está funcionando. Quando tento me registrar, recebo o erro abaixo. Na Internet, observei que https://localhost não está funcionando no sdk do Android. Eu uso o endereço IP 10.0.2.2 e 127.0.0.1, mas ainda recebo o mesmo erro.
JS: ERRO Resposta com status: 200 para URL: null