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 criar perfil-projeto-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 aplicativo/login mkdir -p aplicativo/registro mkdir -p aplicativo/blogs mkdir -p aplicativo/blog toque aplicativo/login/login.componente.html toque aplicativo/login/login.componente.ts toque aplicativo/registro/registro.componente.html toque aplicativo/registro/registro.componente.ts toque aplicativo/blogs/blogs.componente.html toque aplicativo/blogs/blogs.componente.ts toque aplicativo/blog/blog.componente.html toque aplicativo/blog/blog.componente.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 |
importação { Componente } de '@angular/core'; importação { Http, Cabeçalhos, RequestOptions } de "@angular/http"; importação { Roteador } de "@angular/router"; importação "rxjs/Rx"; @Componente({ moduleId: módulo.id, seletor: 'app-login', templateUrl: './login.component.html' }) exportação classe Componente de login { público entrada: qualquer; construtor(privado http: Http, privado roteador: Roteador) { este.entrada = { "email": "", "senha": "" }; } público login() { se(este.entrada.e-mail && este.entrada.senha) { deixar cabeçalhos = novo Cabeçalhos({ "content-type": "application/json" }); deixar opções = novo RequestOptions({ cabeçalhos: cabeçalhos }); este.http.postagem("http://localhost:3000/login", JSON.stringify(este.entrada), opções) .mapa(resultado => resultado.json()) .assinar(resultado => { este.roteador.navegar(["/blogs"], { "queryParams": resultado }); }); } } } |
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 |
<Barra de ação título="{N} Profile Store"></Barra de ação> <Layout de pilha classe="formulário"> <Layout de pilha classe="input-field" (campo de entrada)> <Rótulo texto="E-mail" classe="rótulo font-weight-bold m-b-5"></Rótulo> <TextField classe="input" (entrada) [(ngModel)]="input.email" autocapitalizationType="nenhum"></TextField> <Layout de pilha classe="hr-light"></Layout de pilha> </Layout de pilha> <Layout de pilha classe="input-field" (campo de entrada)> <Rótulo texto="Senha" classe="rótulo font-weight-bold m-b-5"></Rótulo> <TextField classe="input" (entrada) seguro="true" (verdadeiro) [(ngModel)]="input.password" autocapitalizationType="nenhum"></TextField> <Layout de pilha classe="hr-light"></Layout de pilha> </Layout de pilha> <Layout de pilha classe="input-field" (campo de entrada)> <Botão classe="btn btn-primary" texto="Login" (toque)="login()"></Botão> </Layout de pilha> <Layout de pilha classe="input-field" (campo de entrada)> <Rótulo texto="Registre-se aqui". [nsRouterLink]="['/register']" classe="text-center"></Rótulo> </Layout de pilha> </Layout de pilha> |
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 |
importação { Componente } de '@angular/core'; importação { Http, Cabeçalhos, RequestOptions } de "@angular/http"; importação { Roteador } de "@angular/router"; importação "rxjs/Rx"; @Componente({ moduleId: módulo.id, seletor: 'app-register', templateUrl: './register.component.html' }) exportação classe RegistrarComponente { público entrada: qualquer; público construtor(privado http: Http, privado roteador: Roteador) { este.entrada = { "firstname": "", "lastname" (sobrenome): "", "email": "", "senha": "" }; } público registro() { se(este.entrada.e-mail && este.entrada.senha) { deixar cabeçalhos = novo Cabeçalhos({ "content-type": "application/json" }); deixar opções = novo RequestOptions({ cabeçalhos: cabeçalhos }); este.http.postagem("http://localhost:3000/account", JSON.stringify(este.entrada), opções) .mapa(resultado => resultado.json()) .assinar(resultado => { este.roteador.navegar(["/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 |
<Barra de ação título="{N} Profile Store"></Barra de ação> <Layout de pilha classe="formulário"> <Layout de pilha classe="input-field" (campo de entrada)> <Rótulo texto="Primeiro nome" classe="rótulo font-weight-bold m-b-5"></Rótulo> <TextField classe="input" (entrada) [(ngModel)]="input.firstname" autocapitalizationType="nenhum"></TextField> <Layout de pilha classe="hr-light"></Layout de pilha> </Layout de pilha> <Layout de pilha classe="input-field" (campo de entrada)> <Rótulo texto="Sobrenome" classe="rótulo font-weight-bold m-b-5"></Rótulo> <TextField classe="input" (entrada) [(ngModel)]="input.lastname" autocapitalizationType="nenhum"></TextField> <Layout de pilha classe="hr-light"></Layout de pilha> </Layout de pilha> <Layout de pilha classe="input-field" (campo de entrada)> <Rótulo texto="E-mail" classe="rótulo font-weight-bold m-b-5"></Rótulo> <TextField classe="input" (entrada) [(ngModel)]="input.email" autocapitalizationType="nenhum"></TextField> <Layout de pilha classe="hr-light"></Layout de pilha> </Layout de pilha> <Layout de pilha classe="input-field" (campo de entrada)> <Rótulo texto="Senha" classe="rótulo font-weight-bold m-b-5"></Rótulo> <TextField classe="input" (entrada) seguro="true" (verdadeiro) [(ngModel)]="input.password" autocapitalizationType="nenhum"></TextField> <Layout de pilha classe="hr-light"></Layout de pilha> </Layout de pilha> <Layout de pilha classe="input-field" (campo de entrada)> <Botão classe="btn btn-primary" texto="Registrar" (toque)="register()"></Botão> </Layout de pilha> <Layout de pilha classe="input-field" (campo de entrada)> <Rótulo texto="Faça login aqui". [nsRouterLink]="['/login']" classe="text-center"></Rótulo> </Layout de pilha> </Layout de pilha> |
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 |
importação { Componente, OnInit } de '@angular/core'; importação { Http, Cabeçalhos, RequestOptions } de "@angular/http"; importação { Roteador, ActivatedRoute } de "@angular/router"; importação { Localização } de "@angular/common"; importação "rxjs/Rx"; @Componente({ moduleId: módulo.id, seletor: 'app-blogs', templateUrl: './blogs.component.html' }) exportação classe BlogsComponent implementa OnInit { privado lado: string; público entradas: Matriz<qualquer>; público construtor(privado http: Http, privado roteador: Roteador, privado rota: ActivatedRoute, privado localização: Localização) { este.entradas = []; } público ngOnInit() { este.localização.assinar(() => { deixar cabeçalhos = novo Cabeçalhos({ "autorização": "Portador" + este.lado }); deixar opções = novo RequestOptions({ cabeçalhos: cabeçalhos }); este.http.obter("http://localhost:3000/blogs", opções) .mapa(resultado => resultado.json()) .assinar(resultado => { este.entradas = resultado; }); }); este.rota.queryParams.assinar(parâmetros => { este.lado = parâmetros["sid"]; deixar cabeçalhos = novo Cabeçalhos({ "autorização": "Portador" + parâmetros["sid"] }); deixar opções = novo RequestOptions({ cabeçalhos: cabeçalhos }); este.http.obter("http://localhost:3000/blogs", opções) .mapa(resultado => resultado.json()) .assinar(resultado => { este.entradas = resultado; }); }); } público criar() { este.roteador.navegar(["/blog"], { "queryParams": { "sid": este.lado } }); } } |
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 |
<Barra de ação título="{N} Profile Store"> <ActionItem texto="Novo" ios.posição="direito" (toque)="create()"></ActionItem> <NavigationButton texto="Voltar"></NavigationButton> </Barra de ação> <GridLayout linhas="*, auto" colunas="*"> <Visualização de lista [itens]="entradas" classe="grupo de listas" fila="0" col="0"> <ng-modelo deixar-entrada="item"> <Layout de pilha classe="list-group-item"> <Rótulo texto="{{ entry.title }}" classe="h2"></Rótulo> <Rótulo texto="{{ entry.content }}"></Rótulo> </Layout de pilha> </ng-modelo> </Visualização de lista> <Layout de pilha fila="1" col="0" acolchoamento="10" cor de fundo="#F0F0F0"> <Rótulo texto="Logout" [nsRouterLink]="['/login']"></Rótulo> </Layout de pilha> </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 |
importação { Componente, OnInit } de '@angular/core'; importação { Http, Cabeçalhos, RequestOptions } de "@angular/http"; importação { ActivatedRoute } de "@angular/router"; importação { Localização } de "@angular/common"; importação "rxjs/Rx"; @Componente({ moduleId: módulo.id, seletor: 'app-blog', templateUrl: './blog.component.html' }) exportação classe Componente de blog implementa OnInit { privado lado: string; público entrada: qualquer; público construtor(privado http: Http, privado rota: ActivatedRoute, privado localização: Localização) { este.entrada = { "título": "", "content" (conteúdo): "" }; } público ngOnInit() { este.rota.queryParams.assinar(parâmetros => { este.lado = parâmetros["sid"]; }); } público salvar() { se(este.entrada.título && este.entrada.conteúdo) { deixar cabeçalhos = novo Cabeçalhos({ "content-type": "application/json", "autorização": "Portador" + este.lado }); deixar opções = novo RequestOptions({ cabeçalhos: cabeçalhos }); este.http.postagem("http://localhost:3000/blog", JSON.stringify(este.entrada), opções) .mapa(resultado => resultado.json()) .assinar(resultado => { este.localização.voltar(); }); } } } |
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 |
<Barra de ação título="{N} Profile Store"> <NavigationButton texto="Voltar"></NavigationButton> </Barra de ação> <Layout de pilha classe="formulário"> <Layout de pilha classe="input-field" (campo de entrada)> <Rótulo texto="Título" classe="rótulo font-weight-bold m-b-5"></Rótulo> <TextField classe="input" (entrada) [(ngModel)]="input.title" autocapitalizationType="nenhum"></TextField> <Layout de pilha classe="hr-light"></Layout de pilha> </Layout de pilha> <Layout de pilha classe="input-field" (campo de entrada)> <Rótulo texto="Conteúdo" classe="rótulo font-weight-bold m-b-5"></Rótulo> <TextView classe="input" (entrada) [(ngModel)]="input.content" autocapitalizationType="nenhum"></TextView> <Layout de pilha classe="hr-light"></Layout de pilha> </Layout de pilha> <Layout de pilha classe="input-field" (campo de entrada)> <Botão classe="btn btn-primary" texto="Salvar" (toque)="save()"></Botão> </Layout de pilha> </Layout de pilha> |
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 |
importação { NgModule } de "@angular/core"; importação { NativeScriptRouterModule } de "nativescript-angular/router"; importação { Rotas } de "@angular/router"; importação { Componente de login } de "./login/login.component"; importação { RegistrarComponente } de "./register/register.component"; importação { BlogsComponent } de "./blogs/blogs.component"; importação { Componente de blog } de "./blog/blog.component"; const rotas: Rotas = [ { caminho: "", redirectTo: "/login", pathMatch: "completo" }, { caminho: "login", componente: Componente de login }, { caminho: "registrar", componente: RegistrarComponente }, { caminho: "blogs", componente: BlogsComponent }, { caminho: "blog", componente: Componente de blog } ]; @NgModule({ importações: [NativeScriptRouterModule.forRoot(rotas)], exportações: [NativeScriptRouterModule] }) exportação classe 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 |
importação { NgModule, NÃO_ERROS_ESQUEMA } de "@angular/core"; importação { NativeScriptModule } de "nativescript-angular/nativescript.module"; importação { AppRoutingModule } de "./app.routing"; importação { NativeScriptHttpModule } de "nativescript-angular/http"; importação { NativeScriptFormsModule } de "nativescript-angular/forms"; importação { Componente de aplicativo } de "./app.component"; importação { Componente de login } de "./login/login.component"; importação { RegistrarComponente } de "./register/register.component"; importação { BlogsComponent } de "./blogs/blogs.component"; importação { Componente de blog } de "./blog/blog.component"; @NgModule({ bootstrap: [ Componente de aplicativo ], importações: [ NativeScriptModule, AppRoutingModule, NativeScriptHttpModule, NativeScriptFormsModule ], declarações: [ Componente de aplicativo, Componente de login, RegistrarComponente, BlogsComponent, Componente de blog ], provedores: [], esquemas: [ NÃO_ERROS_ESQUEMA ] }) exportação classe 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 |
<ditado> <chave>NSAllowsArbitraryLoads</chave> <verdadeiro /> </ditado> |
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 http://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