Siguiendo por la senda de los almacenes de perfiles de usuario, habíamos visto anteriormente cómo crear uno con Node.js y Couchbase NoSQL así como un cliente web front-end para él usando Angular. ¿Qué pasaría si quisiéramos llevar esto a una aplicación móvil, que es la norma para todas las aplicaciones a partir de ahora.
Hay muchos frameworks móviles diferentes por ahí y tenemos la suerte de que algunos incluso soportan Angular, que es lo que habíamos utilizado en el ejemplo anterior. Vamos a ver cómo convertir nuestro front-end cliente a móvil usando NativeScript y Angular.
Antes de seguir adelante, se supone que has completado los dos tutoriales anteriores, uno sobre creación del backend de la tienda de perfiles y el otro en creación del front-end web de la tienda de perfiles. También vamos a suponer que su entorno de desarrollo está configurado para el desarrollo móvil, ya sea Android, iOS, o ambos.
El flujo de eventos en esta aplicación coincidirá con lo que vimos en la versión web.
Crear un nuevo proyecto NativeScript con Angular
Asumiendo que tienes el NativeScript CLI instalado y configurado, ejecuta lo siguiente para crear un nuevo proyecto:
1 |
tns crear perfil-proyecto-ns --ng |
En --ng
en el comando anterior es importante porque significa que estamos creando un proyecto Angular en lugar de un proyecto NativeScript Core.
Por ahora, el CLI de NativeScript no comparte las capacidades del CLI de Angular de generar componentes. Por esta razón, vamos a tener que crear cada uno de los archivos HTML y TypeScript manualmente.
Si estás en Mac o Linux, ejecuta lo siguiente desde el proyecto NativeScript:
1 2 3 4 5 6 7 8 9 10 11 12 |
mkdir -p aplicación/inicio de sesión mkdir -p aplicación/regístrese en mkdir -p aplicación/blogs mkdir -p aplicación/blog toque aplicación/inicio de sesión/inicio de sesión.componente.html toque aplicación/inicio de sesión/inicio de sesión.componente.ts toque aplicación/regístrese en/regístrese en.componente.html toque aplicación/regístrese en/regístrese en.componente.ts toque aplicación/blogs/blogs.componente.html toque aplicación/blogs/blogs.componente.ts toque aplicación/blog/blog.componente.html toque aplicación/blog/blog.componente.ts |
Si estás en Windows, simplemente crea esos directorios y archivos manualmente. Si realmente quisieras, podrías copiar estos directorios y archivos de tu proyecto web desde la carpeta tutorial anterior.
Definición de los componentes para representar cada pantalla
Empezando en la misma dirección que la versión web, vamos a centrarnos en el inicio de sesión del usuario. Abra el proyecto app/login/login.component.ts e incluya el siguiente 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 |
importar { Componente } de @angular/core; importar { Http, Cabeceras, RequestOptions } de "@angular/http"; importar { Router } de "@angular/router"; importar "rxjs/Rx"; @Componente({ moduleId: módulo.id, selector: app-login, templateUrl: './ingreso.component.html' }) exportar clase Componente de inicio de sesión { público entrada: cualquier; constructor(privado http: Http, privado enrutador: Router) { este.entrada = { "email": "", "contraseña": "" }; } público inicio de sesión() { si(este.entrada.correo electrónico && este.entrada.contraseña) { deje cabeceras = nuevo Cabeceras({ "tipo de contenido": "application/json" }); deje opciones = nuevo RequestOptions({ cabeceras: cabeceras }); este.http.Correo electrónico:("http://localhost:3000/login", JSON.stringify(este.entrada), opciones) .mapa(resultado => resultado.json()) .suscríbase a(resultado => { este.enrutador.navegue por(["/blogs"], { "queryParams": resultado }); }); } } } |
El código anterior es exactamente lo que vimos en la versión web con dos excepciones. He eliminado la referencia CSS ya que no creamos un archivo CSS por componente. También he añadido un moduleId
para que las rutas relativas puedan funcionar con nuestro componente. Ambos son elementos relacionados con Angular que no tienen nada que ver con NativeScript.
El HTML es donde las cosas son diferentes. Abra el archivo app/login/login.component.html e incluya el siguiente marcado XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<ActionBar título="{N} Tienda de perfiles"></ActionBar> <StackLayout clase="formulario"> <StackLayout clase="campo de entrada"> <Etiqueta texto="Email" clase="label font-weight-bold m-b-5"></Etiqueta> <Campo de texto clase="entrada" [(ngModel)]="entrada.email" autocapitalizationType="ninguno"></Campo de texto> <StackLayout clase="hr-light"></StackLayout> </StackLayout> <StackLayout clase="campo de entrada"> <Etiqueta texto="Contraseña" clase="label font-weight-bold m-b-5"></Etiqueta> <Campo de texto clase="entrada" seguro="true" [(ngModel)]="input.password" autocapitalizationType="ninguno"></Campo de texto> <StackLayout clase="hr-light"></StackLayout> </StackLayout> <StackLayout clase="campo de entrada"> <Botón clase="btn btn-primary" texto="Iniciar sesión" (pulse)="login()"></Botón> </StackLayout> <StackLayout clase="campo de entrada"> <Etiqueta texto="Regístrese aquí". [nsRouterLink]="['/register']" clase="texto-centro"></Etiqueta> </StackLayout> </StackLayout> |
NativeScript tiene su propio marcado para la interfaz de usuario. Las mismas reglas se aplican en términos de Angular, pero la creación de componentes de interfaz de usuario es un poco diferente.
Por ejemplo, seguimos utilizando el [(ngModel)]
pero en lugar de div
etiquetas, tenemos StackLayout
tags.
Veamos ahora el componente de registro. Abra el archivo app/register/register.component.ts e incluir el siguiente 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 { Componente } de @angular/core; importar { Http, Cabeceras, RequestOptions } de "@angular/http"; importar { Router } de "@angular/router"; importar "rxjs/Rx"; @Componente({ moduleId: módulo.id, selector: app-register, templateUrl: './register.component.html' }) exportar clase RegistrarComponente { público entrada: cualquier; público constructor(privado http: Http, privado enrutador: Router) { este.entrada = { "nombre": "", "apellido": "", "email": "", "contraseña": "" }; } público regístrese en() { si(este.entrada.correo electrónico && este.entrada.contraseña) { deje cabeceras = nuevo Cabeceras({ "tipo de contenido": "application/json" }); deje opciones = nuevo RequestOptions({ cabeceras: cabeceras }); este.http.Correo electrónico:("http://localhost:3000/account", JSON.stringify(este.entrada), opciones) .mapa(resultado => resultado.json()) .suscríbase a(resultado => { este.enrutador.navegue por(["/login"]); }); } } } |
Una vez más, los únicos cambios que hemos hecho en el código anterior, en comparación con el ejemplo anterior, es en la eliminación de CSS y moduleId
Además.
No está nada mal cuando se trata de crear aplicaciones web y móviles multiplataforma, ¿verdad?
El HTML de la interfaz de usuario que alimenta la lógica de TypeScript se encuentra en el archivo app/register/register.component.html y tiene este 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 |
<ActionBar título="{N} Tienda de perfiles"></ActionBar> <StackLayout clase="formulario"> <StackLayout clase="campo de entrada"> <Etiqueta texto="Nombre" clase="label font-weight-bold m-b-5"></Etiqueta> <Campo de texto clase="entrada" [(ngModel)]="entrada.nombre" autocapitalizationType="ninguno"></Campo de texto> <StackLayout clase="hr-light"></StackLayout> </StackLayout> <StackLayout clase="campo de entrada"> <Etiqueta texto="Apellido" clase="label font-weight-bold m-b-5"></Etiqueta> <Campo de texto clase="entrada" [(ngModel)]="input.lastname" autocapitalizationType="ninguno"></Campo de texto> <StackLayout clase="hr-light"></StackLayout> </StackLayout> <StackLayout clase="campo de entrada"> <Etiqueta texto="Email" clase="label font-weight-bold m-b-5"></Etiqueta> <Campo de texto clase="entrada" [(ngModel)]="entrada.email" autocapitalizationType="ninguno"></Campo de texto> <StackLayout clase="hr-light"></StackLayout> </StackLayout> <StackLayout clase="campo de entrada"> <Etiqueta texto="Contraseña" clase="label font-weight-bold m-b-5"></Etiqueta> <Campo de texto clase="entrada" seguro="true" [(ngModel)]="input.password" autocapitalizationType="ninguno"></Campo de texto> <StackLayout clase="hr-light"></StackLayout> </StackLayout> <StackLayout clase="campo de entrada"> <Botón clase="btn btn-primary" texto="Registro" (pulse)="register()"></Botón> </StackLayout> <StackLayout clase="campo de entrada"> <Etiqueta texto="Inicie sesión aquí". [nsRouterLink]="['/login']" clase="texto-centro"></Etiqueta> </StackLayout> </StackLayout> |
Los dos últimos componentes no van a ser diferentes de lo que ya estamos experimentando.
Abra el archivo app/blogs/blogs.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 37 38 39 40 41 42 43 44 45 46 47 |
importar { Componente, OnInit } de @angular/core; importar { Http, Cabeceras, RequestOptions } de "@angular/http"; importar { Router, Ruta activada } de "@angular/router"; importar { Ubicación } de "@angular/common"; importar "rxjs/Rx"; @Componente({ moduleId: módulo.id, selector: app-blogs, templateUrl: './blogs.component.html' }) exportar clase BlogsComponente implementa OnInit { privado sid: cadena; público entradas: Matriz<cualquier>; público constructor(privado http: Http, privado enrutador: Router, privado ruta: Ruta activada, privado ubicación: Ubicación) { este.entradas = []; } público ngOnInit() { este.ubicación.suscríbase a(() => { deje cabeceras = nuevo Cabeceras({ "autorización": "Portador" + este.sid }); deje opciones = nuevo RequestOptions({ cabeceras: cabeceras }); este.http.consiga("http://localhost:3000/blogs", opciones) .mapa(resultado => resultado.json()) .suscríbase a(resultado => { este.entradas = resultado; }); }); este.ruta.queryParams.suscríbase a(parámetros => { este.sid = parámetros["sid"]; deje cabeceras = nuevo Cabeceras({ "autorización": "Portador" + parámetros["sid"] }); deje opciones = nuevo RequestOptions({ cabeceras: cabeceras }); este.http.consiga("http://localhost:3000/blogs", opciones) .mapa(resultado => resultado.json()) .suscríbase a(resultado => { este.entradas = resultado; }); }); } público crear() { este.enrutador.navegue por(["/blog"], { "queryParams": { "sid": este.sid } }); } } |
No hay cambios que ver en lo anterior aparte de los dos mencionados anteriormente, por lo que podemos pasar a nuestro HTML para la página.
Abra el archivo app/blogs/blogs.component.html e incluya el siguiente código HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<ActionBar título="{N} Tienda de perfiles"> <Elemento de acción texto="Nuevo" ios.posición="derecha" (pulse)="create()"></Elemento de acción> <Botón de navegación texto="Atrás"></Botón de navegación> </ActionBar> <GridLayout filas="*, auto" columnas="*"> <ListView [artículos]="entradas" clase="lista-grupo" fila="0" col="0"> <ng-plantilla deje-entrada="item"> <StackLayout clase="lista-grupo-elemento"> <Etiqueta texto="{{ entry.title }}" clase="h2"></Etiqueta> <Etiqueta texto="{{ entry.content }}"></Etiqueta> </StackLayout> </ng-plantilla> </ListView> <StackLayout fila="1" col="0" acolchado="10" backgroundColor="#F0F0F0"> <Etiqueta texto="Cerrar sesión" [nsRouterLink]="['/login']"></Etiqueta> </StackLayout> </GridLayout> |
Finalicemos esta aplicación con el último componente que ofrece nuestra API de tienda de perfiles y el front-end web.
Abra el archivo app/blog/blog.component.ts e incluye esto:
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 { Componente, OnInit } de @angular/core; importar { Http, Cabeceras, RequestOptions } de "@angular/http"; importar { Ruta activada } de "@angular/router"; importar { Ubicación } de "@angular/common"; importar "rxjs/Rx"; @Componente({ moduleId: módulo.id, selector: app-blog, templateUrl: './blog.component.html' }) exportar clase BlogComponent implementa OnInit { privado sid: cadena; público entrada: cualquier; público constructor(privado http: Http, privado ruta: Ruta activada, privado ubicación: Ubicación) { este.entrada = { "título: "", "contenido": "" }; } público ngOnInit() { este.ruta.queryParams.suscríbase a(parámetros => { este.sid = parámetros["sid"]; }); } público guardar() { si(este.entrada.título && este.entrada.contenido) { deje cabeceras = nuevo Cabeceras({ "tipo de contenido": "application/json", "autorización": "Portador" + este.sid }); deje opciones = nuevo RequestOptions({ cabeceras: cabeceras }); este.http.Correo electrónico:("http://localhost:3000/blog", JSON.stringify(este.entrada), opciones) .mapa(resultado => resultado.json()) .suscríbase a(resultado => { este.ubicación.volver(); }); } } } |
Si no ha copiado el archivo CSS, no olvide eliminarlo del directorio @Componente
como se ha visto en los componentes anteriores.
La interfaz de usuario HTML que acompaña a este TypeScript se encuentra en el proyecto app/blog/blog.component.html y tiene el siguiente aspecto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<ActionBar título="{N} Tienda de perfiles"> <Botón de navegación texto="Atrás"></Botón de navegación> </ActionBar> <StackLayout clase="formulario"> <StackLayout clase="campo de entrada"> <Etiqueta texto="Título clase="label font-weight-bold m-b-5"></Etiqueta> <Campo de texto clase="entrada" [(ngModel)]="entrada.titulo" autocapitalizationType="ninguno"></Campo de texto> <StackLayout clase="hr-light"></StackLayout> </StackLayout> <StackLayout clase="campo de entrada"> <Etiqueta texto="Contenido clase="label font-weight-bold m-b-5"></Etiqueta> <Vista de texto clase="entrada" [(ngModel)]="input.content" autocapitalizationType="ninguno"></Vista de texto> <StackLayout clase="hr-light"></StackLayout> </StackLayout> <StackLayout clase="campo de entrada"> <Botón clase="btn btn-primary" texto="Guardar" (pulse)="save()"></Botón> </StackLayout> </StackLayout> |
Ahora mismo puedes estar rascándote la cabeza con todo este marcado XML de NativeScript. Cómo Angular trabaja con la interfaz de usuario no ha cambiado, pero si usted está interesado en aprender sobre el marcado NativeScript, echa un vistazo a su documentación oficial. Familiarícese con el StackLayout
, GridLayout
y etiquetas de componentes de interfaz de usuario individuales.
La unión hace la fuerza
Hemos creado todos estos componentes Angular para NativeScript, pero no los hemos unido a través del Router Angular.
En la versión web de esta guía, la información sobre la ruta se encontraba en la sección app.module.ts archivo. Si bien podríamos hacer eso, NativeScript lo dividió en un archivo separado.
Abra el archivo app/app.routing.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 |
importar { NgModule } de "@angular/core"; importar { NativeScriptRouterModule } de "nativescript-angular/router"; importar { Rutas } de "@angular/router"; importar { Componente de inicio de sesión } de "./login/login.component"; importar { RegistrarComponente } de "./register/register.component"; importar { BlogsComponente } de "./blogs/blogs.component"; importar { BlogComponent } de "./blog/blog.component"; const rutas: Rutas = [ { ruta: "", redirectTo: "/login", pathMatch: "completo" }, { ruta: "login", componente: Componente de inicio de sesión }, { ruta: "registrar", componente: RegistrarComponente }, { ruta: "blogs", componente: BlogsComponente }, { ruta: "blog", componente: BlogComponent } ]; @NgModule({ importaciones: [NativeScriptRouterModule.forRoot(rutas)], exportaciones: [NativeScriptRouterModule] }) exportar clase AppRoutingModule { } |
Gran parte del código anterior vino con nuestra nueva plantilla de proyecto. Importamos cada uno de nuestros componentes, y creamos una ruta para ellos.
Del mismo modo, los componentes deben importarse en la carpeta del proyecto app/app.module.ts archivo. Abra este archivo 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 |
importar { NgModule, NO_ERRORES_ESQUEMA } de "@angular/core"; importar { Módulo NativeScript } de "nativescript-angular/nativescript.module"; importar { AppRoutingModule } de "./app.routing"; importar { NativeScriptHttpModule } de "nativescript-angular/http"; importar { Módulo NativeScriptForms } de "nativescript-angular/forms"; importar { AppComponent } de "./app.component"; importar { Componente de inicio de sesión } de "./login/login.component"; importar { RegistrarComponente } de "./register/register.component"; importar { BlogsComponente } de "./blogs/blogs.component"; importar { BlogComponent } de "./blog/blog.component"; @NgModule({ arranque: [ AppComponent ], importaciones: [ Módulo NativeScript, AppRoutingModule, NativeScriptHttpModule, Módulo NativeScriptForms ], declaraciones: [ AppComponent, Componente de inicio de sesión, RegistrarComponente, BlogsComponente, BlogComponent ], proveedores: [], esquemas: [ NO_ERRORES_ESQUEMA ] }) exportar clase AppModule { } |
Además de importar cada componente y añadirlo a la carpeta declaraciones
también importamos algunos módulos, como el módulo NativeScriptHttpModule
y Módulo NativeScriptForms
. En Angular puro, se denominan HttpModule
y Módulo de formularios
.
En teoría, la aplicación está lista para funcionar.
Resolución de problemas de seguridad en el transporte de aplicaciones (ATS) para iOS
Como la API de Node.js y Couchbase se ejecuta localmente, estamos usando HTTP en lugar de HTTPS. iOS arrojará errores si intentamos acceder a recursos HTTP.
Esto se puede solucionar fácilmente añadiendo una política ATS.
Abra el archivo app/App_Resources/iOS/Info.plist y añade lo siguiente junto al otro XML:
1 2 3 4 |
<dic> <clave>NSAllowsArbitraryLoads</clave> <verdadero /> </dic> |
Lo anterior esencialmente listas blancas todos los puntos finales HTTP. No es seguro para producción, pero sí para pruebas.
Más información sobre ATS en aplicaciones NativeScript se puede leer en un artículo anterior que escribí titulado, Solucionar problemas de seguridad en el transporte de aplicaciones de iOS 9 en NativeScript.
Conclusión
Usted acaba de ver lo fácil que era tomar su cliente web front-end y convertirlo en móvil usando NativeScript y Angular. El ejemplo de la tienda de perfiles de usuario se convirtió rápidamente en un ejemplo de pila completa utilizando la pila de JavaScript. Teníamos un Node.js con Servidor Couchbase backend, Angular web front-end y NativeScript mobile front-end.
El siguiente paso, u opción, sería utilizar los componentes de Couchbase Mobile en lugar de llamadas HTTP contra la API.
Hola,
He intentado ejecutar este código pero POST no funciona. Cuando intento registrarme obtengo el siguiente error. De en línea me doy cuenta de que http://localhost no funciona en android sdk. Yo uso la dirección IP tanto 10.0.2.2 y 127.0.0.1 pero sigue recibiendo el mismo error.
JS: ERROR Respuesta con estado: 200 para URL: null