En los últimos años se está dando un cambio de rumbo en la programación de aplicaciones informáticas: el paso de aplicaciones de escritorio a aplicaciones web. Esto responde por una parte a …Descripción completa
Descripción completa
Prueba de Aplicaciones Web-pressmanDescripción completa
Informatica
Descripción: Desarrollo de Aplicaciones Móviles Web
aplicaciones webDescripción completa
Descripción completa
Despliegue Aplicaciones WEB - GarcetaDespliegue Aplicaciones WEB - Garceta Despliegue Aplicaciones WEB - GarcetaDespliegue Aplicaciones WEB - GarcetaDespliegue Aplicaciones WEB - GarcetaDesp…Descripción completa
Descripción: Ultima actualizacion 10/12/2008
1. Explique para qué es y para qué sirve un lenguaje de programación. 2. Realice un cuadro comparativo de las ventajas y desventajas que ofrecen las aplicaciones web en relación con las apl…Descripción completa
Esta obra tiene una orientación fundamentalmente práctica. Consiste en una secuencia de prácticas resueltas diseña-das para facilitar al lector/alumno la adquisición de habilidades y conocimientos ...Full description
Desarrollo de aplicaciones web
2015
Descripción: El presente trabajo tiene como principal objetivo el análisis de vulnerabilidades en diferentes Aplicaciones Web y su solución.
Para crear un programa de consola en Java utilizando NetBeans 5.0 lo primero que hay que hacer es crear un proyecto. Un proyecto nos permite administrar los archivos con el código fuente y…Descripción completa
Modulo III Desarrollo de Aplicaciones WEB y MÓVILES Submódulo I Desarrollo de aplicaciones WEBDescripción completa
Evolución de las aplicaciones web a través del tiempo.
Descripción completa
Unidad completa.Descripción completa
Descripción completa
Creación de aplicaciones web modernas usando Angular
Aprenda a crear aplicaciones web ricas y atractivas con Angular
Editor de desarrollo de contenido Juliana Nair Editor técnico Mohd Riyan Khan Coordinador de produccion Melwyn Dsa
Indexador Rekha Nair Gráficos Kirk D'Penha
Sobre el Autor Shravan Kumar Kasagoni es un desarrollador, fanático de los dispositivos, evangelizador
de tecnología, mentor, blogger y orador que vive en Hyderabad. Ha sido un apasionado de las computadoras y la tecnología desde la infancia. Tiene una licenciatura en ciencias de la computación e ingeniería, y es un profesional certificado de Microsoft.
Su experiencia incluye React.js, tecnologías web modernas (HTML5,ha JavaScript Node.js) y frameworks (Angular, Knockout.js, etc.). También trabajadoy con muchas tecnologías de Microsoft, como ASP.NET MVC, ASP.NET WEB API, WCF, C #, SSRS y la plataforma en la nube Microsoft Azure. Es miembro principal de Microsoft User Group Hyderabad, donde ayuda a miles de desarrolladores en tecnologías web modernas y tecnologías de Microsoft. También contribuye activamente a la comunidad de open source. Él es un orador habitual en grupos de usuarios locales y conferencias. Shravan ha sido galardonado con el prestigioso premio de Microsoft Most Valuable Professional por los últimos 6 años consecutivos por su experiencia y contribuciones a la comunidad en tecnologías web modernas que utilizan ASP.NET y tecnologías de código abierto. Actualmente trabaja con Novartis India, donde es responsable del diseño y desarrollo de aplicaciones web empresariales modernas de alto rendimiento y RESTful APIs. Anteriormente, estuvo asociado con Thomson Reuters y Pramati Technologies. Me gustaría agradecer a mi esposa por aguantar mis sesiones de escritura nocturnas, mis padres y mi hermano por su constante apoyo. También doy las gracias profundamente y expreso mi gratitud a mis amigos cercanos, Pranav y Ashwini Reddy, que siempre han estado allí para alentarme, guiarme y ayudarme. También me gustaría dar las gracias a mis ex colegas Monisha y Dharmendra, y a mis amigos, Abhijit Jana, Sudhakar, Subhendu, Sai Kiran, Srikar Ananthula y Raghu Ram. Estoy agradecido con mis mentores, Nagaraju Bende y Mallikarjun, sin los cuales quizás no haya llegado aquí.
Sobre los revisores Hemant Singh es un desarrollador que vive en Hyderabad/AP, India. Actualmente, él
está trabajando para Microsoft como un consultor UX. Él ama el código abierto, y está activo en varios proyectos. Hemant no es muy blogger, pero intenta compartir información siempre que sea posible. Él elabora documentos CSS y HTML y maneja JavaScript (las partes buenas). No será sorprendente si te dice que se enamoró de HTML5 y, por supuesto, de CSS3. Hemant también tiene una pasión por la interfaz de usuario y el diseño de la experiencia y trata de mostrar parte de su trabajo en su portafolio. Phodal Huang es un desarrollador, creador y autor. Él trabaja para ThoughtWorks
como consultor. Actualmente, se enfoca en el desarrollo de IoT y frontend. Es autor de Design Internet of Things and Growth: Thinking in Full Stack en chino. Es un entusiasta del codigo abierto y ha creado una serie de proyectos en GitHub. Después de su trabajo diario, le gusta reinventar algunas ruedas para divertirse. Creó la aplicación Growth with Ionic 2 y Angular 2, que trata de entrenar a los novatos sobre programación. Puede encontrar más información sobre ruedas en su página de GitHub, http://github.c om/phodal .
Le encanta diseñar, escribir, hackear y viajar. También puede obtener más información sobre él en su sitio web personal en http://www.phodal.com .
www.PacktPub.com Para ver los archivos de soporte y las descargas relacionadas con su libro, visite
www.PacktPub.com.
¿Sabía que Packt ofrece versiones de eBook de cada libro publicado, con archivos PDF y ePub disponibles? Puede actualizar a la versión de libro electrónico en www.PacktPub.com y como cliente de libro impresión, tiene derecho a un descuento en la para copiaobtener de libromás electrónico. Póngase ende contacto con nosotros en [email protected] detalles. En www.PacktPub.com, también puede leer una colección de artículos técnicos gratuitos, suscribirse a una gama de boletines gratuitos y recibira descuentos y ofertas exclusivas en libros y libros electrónicos de Packt.
https://www.packtpub.com/mapt
Obtenga las habilidades de software más demandadas con Mapt. Mapt le brinda acceso completo a todos los libros y videos de Packt, así como a las herramientas líderes de la industria para ayudarle a planificar su desarrollo personal y avanzar en su carrera.
¿Por qué suscribirse? Se puede buscar a través de cada libro publicado por Packt Copie y pegue, imprima y marque el contenido A pedido y accesible a través de un navegador web
Comentarios de los clientes Gracias por comprar este libro de Packt. En Packt, la calidad está en el corazón de nuestro proceso editorial. Para ayudarnos a mejorar, por favor déjenos una review honesta en la página de Amazon de este libro en http s://www.a mazon.com/dp/1785880721. Si desea unirse a nuestro equipo de revisores regulares, puede enviarnos un correo electrónico a [email protected]. Premiamos a nuestros revisores habituales con eBooks y videos gratuitos a cambio de sus valiosos comentarios. ¡Ayúdanos a ser implacables para mejorar nuestros productos!
Table of Contents Tabla de contenido
1
Capítulo 1: Comenzando
6
Introducción a Angular ¿Qué hay de nuevo en Angular? Configuración del entorno Instalando Node.js y npm Opciones de lenguaje ECMAScript 5 ECMAScript 2015 TypeScript Instalación de TypeScript Conceptos básicos de TypeScript - tipos
6 7 7 8 8 9 9 9 10
String Number Boolean Array Enum Any Void Functions
10 11 11 11 11 11 12 12 13
Declaración de función - función nombrada Expresión de función - función anónima Classes
14 14 14
Escribiendo tu primera aplicación Angular Configurar la aplicación angular Paso 1 Paso 2 Paso 4 Paso 5 Paso 6 Paso 7 Paso 8 SystemJS Uso del componente angular Comprender los paquetes npm Paso 9 Paso 10
Uso de Angular CLI
16 16 16 18 19 20 21 22 22 23 24 25 26 26 28
Empezar conAngular CLI Resumen
28 29
Capítulo 2: Conceptos básicos de los componentes Empezando Configuración del proyecto Trabajando con datos Visualización de datos
31 31 31 34 35 35 37
sintaxis de interpolación Enlace de propiedad Enlace de eventos
Enlace de atributos
38 38
Enlace de Datos bidireccionales
41
Directivas incorporadas
42
Directivas estructurales
ngIf ngFor La comprensión de la sintaxis ngFor ngSwitch
Directivas de atributo
42 43 43 43 44 45 45 46
ngStyle ngClass
Construyendo el componente master-detail Resumen
Capítulo 3: Componentes, servicios e inyección de dependencias Introducción Trabajando con múltiples componentes Propiedades Input Propiedades Aliasing input
Propiedades Output
47 53 54 54 55 57 58 59 61
Propiedades Aliasing output
Compartir datos usando servicios Inyección de dependencia Usando un proveedor de clase Usar un proveedor de clase con dependencias Usar proveedores de clases alternativas Usar proveedores de clase aliased Resumen
Capítulo 4: Trabajando con Observab les Conceptos básicos de RxJS y O bservables Programación reactiva
63 67 67 68 69 70 71 72 72 72
[ ii ]
Observer Observable Subscription Operators Observables en Angular Valores observables de stream y mapping Fusionando Observables streams Usando el método Observable.interval() Usando AsyncPipe
73
Crear un componente de búsqueda de libros Resumen
82 90
Capítulo 5: Manejo de formularios
74 76 76 77 77 78 79 81
91
¿Por qué son difíciles los formularios? API de formularios en Angular FormControl, FormGroup y FormArray FormControl Crear un control de formulario Accediendo al valor de un control de entrada Establecer el valor del control de entrada Restablecer el valor de un control de entrada Estados de control de entrada
FormGroup FormArray
Formularios impulsados por plantillas Crear un formulario de registro Usando la directiva ngModel Accediendo a un valor de control de entrada usando ngModel Usar ngModel para enlazar un valor de cadena Usar ngModel para enlazar una propiedad del componente Uso de la directiva ngForm Envío de un formulario usando el método ngSubmit Usando la directiva ngModelGroup Agregar validaciones al formulario de registro
Pros y contras de formularios basados en plantilla formularios reactivos
Crear un formulario de registro usando formularios reactivos Uso de FormGroup, FormControl y Validators Usando [formGroup], formControlName, y formGroupName Usando FormBuilder CustomValidators
Pros y contras de formularios reactivos Resumen
117 118 119 121 122 125 125
[ iii ]
Capítulo 6: Creación de una aplicación de tienda de libros Aplicación de tienda de libros HTTP Hacer solicitudes GET Routing Definiendo rutas Directiva RouterOutlet
126 126 126 131 137 137 139 142
Nombre RouterOutlet
Navegación
142
Parámetros de ruta Animar componentes enrutados Módulos de características usando @NgModule () Resumen
142 148 150 153
Capítulo 7: Pruebas
154
Pruebas
154
Pruebas unitarias
155
Prueba de extremo a extremo Herramientas Archivos de configuración Conceptos básicos de Jasmine
155
Prueba de unidad
157
155 155 156
Pruebas unitarias aisladas Escribir pruebas unitarias básicas aisladas Prueba de Servicios Dependencias Mocking Prueba de Componentes
Pruebas unitarias integradas Prueba de Componentes Prueba de componentes con dependencias Resumen
157 159 161 162 164 165 165 169 172
Capítulo 8: Angular Material
173
Introducción Empezando Configuración del proyecto Uso de componentes de Angular Ma terial Página Master-detail Página de lista de libros Agregar diálogo de libro Formulario de registro de usuario
[ iv ]
173 173 174 176 176 183 189 193
Agregar temas Resumen
197 199
Index
200
[v]
Prefacio Creación de aplicaciones web modernas usando Angular ayuda a los lectores a diseñar y desarrollar aplicaciones web modernas. Proporciona una sólida comprensión del framework Angular 4. Los lectores aprenderán a construir y diseñar aplicaciones web de alto rendimiento que se centren principalmente en la interfaz de usuario. Esta es una guía completa para todas las nuevas características en Angular 4. Este libro también cubre algunos de los últimos conceptos de JavaScript en ECMAScript 2015, ECMAScript 2016 y TypeScript. Este libro lo llevará de la nada cuando se trata de construir aplicaciones de interfaz de usuario para que la Web y el móvil a convertirse en un maestro usando Angular 4. Explicará casi todas las características del framework Angular 4 con un enfoque de partículas y muchos ejemplos, mostrando cómo utilizarlos en escenarios del mundo real para construir aplicaciones de interfaz de usuario convincentes. Los capítulos al final del libro están dedicados a mostrar cómo crear una UI de aplicación de extremo a extremo utilizando las características individuales de Angular 4 que se explicaron en los capítulos anteriores.
Lo que cubre este libro Capítulo 1, Comenzando, presenta el framework Angular 4 y sus nuevas características, cómo Angular 4 es mejor y más potente que su predecesor Angular 1, configuración del entorno de desarrollo para aplicaciones Angular 4. Además, proporciona una visión rápida de TypeScript y sus características, cómo escribir una aplicación básica de Angular 4 y
comprender su anatomía. Capítulo 2, Conceptos básicos de los componentes, recorre los aspectos básicos de los componentes de Angular 4, comenzando con los datos de visualización mediante la sintaxis de interpolación, el enlace de propiedades, el enlace de atributos, el trabajo con eventos DOM y el enlace de datos bidireccional. También introduce directivas estructurales para visualizar condicionalmente directivas de datos y atributos para el estilo condicional. Capítulo 3, Componentes, Servicios e Inyección de Dependencia, describe cómo desarrollar aplicaciones Angular 4 usando múltiples componentes, comunicándose entre componentes, compartiendo los datos entre estos componentes utilizando servicios y servicios de inyección usando inyección de dependencia, y cómo funciona la inyección de dependencia. Capítulo 4, Trabajando con Observables, se enfoca en la programación reactiva, Observables, RxJS, cómo Angular 4 implementa Observables usando RxJS, y usar Observables y
operadores RxJS en aplicaciones Angular 4.
Preface
Capítulo 5, Manejo de formularios, presenta cómo crear diferentes tipos de formularios en aplicaciones Angular 4 para aceptar la entrada del usuario y validarlo mediante formularios basados en plantillas, formularios reactivos y directivas de validación. Capítulo 6, Creación de una aplicación de tienda de libros, describe cómo estructurar una aplicación pequeña a una compleja utilizando módulos Angular 4, crea varios tipos de interfaces de usuario en una aplicación Book Store, navega entre componentes usando el enrutamiento e interactúa con el servidor utilizando el servicio HTTP. Capítulo 7, Pruebas, presenta cómo escribir pruebas unitarias usando Jasmine y
Angular Test Utilities para varias partes de aplicaciones Angular 4. Capítulo 8, Angular Material, describe cómo construir una única interfaz de usuario convincente, que fluya a través de los escritorios, tabletas y dispositivos móviles, utilizando material design y aprendiendo a personalizar material design según la marca del cliente.
Lo que necesitas para este libro Este libro asume un conocimiento básico de JavaScript, desarrollo web, cómo usar la línea de comando, Git, y el node package manager (npm). En este libro, necesitarás la siguiente lista de software: Sistema operativo: MAC OS X 10.9 o superior WINDOWS 7 o superior Node.js 6: MAC: https://nodejs.org/dist/v6.10.3/node-v6.10.3.pkg Windows: https://nodejs.org/dist/v6.10.3/node-v6.10.3-x 64.msi
Cualquier editor de código Visual Studio Code Sublime Se requiere conectividad a Internet para instalar los paquetes npm necesarios.
[2]
Preface
Para quien es este libro Este libro es para desarrolladores que crean aplicaciones web que son nuevos en el mundo angular y están interesados en crear aplicaciones de interfaz de usuario complejas, modernas y con capacidad de respuesta usando Angular 4. Para desarrolladores que ya están trabajando con el framework AngularJS 1, este libro proporciona una ruta de actualización con nuevos conceptos.
Convenciones En este libro, encontrará una serie de estilos de texto que distinguen entre diferentes tipos de información. Aquí hay algunos ejemplos de estos estilos y una explicación de su significado. Las palabras de código en el texto, nombres de carpetas, nombres de archivo, extensiones de archivos, nombres de ruta, URL, entrada de usuario y manejadores de Twitter se muestran de la siguiente manera: Un bloque de código se establece de la siguiente manera: import { Component } from '@angular/core'; @Component({ selector: 'hello-world-app', template: '
Say Hello to Angular
' }) class HelloWorldAppComponent { }
Cualquier entrada o salida de la línea de comandos se escribe de la siguiente manera: $ npm install json-server -save
Los nuevos términos y las palabras importantes se muestran en negrita. Las palabras que ve en la pantalla, por ejemplo, en menús o cuadros de diálogo, aparecen en el texto de esta manera: "Al hacer clic en el botón Siguiente se pasa a la siguiente pantalla". Las advertencias o notas importantes aparecen en un recuadro como este.
Consejos y trucos aparecen así.
[3]
Preface
Comentarios de los lectores Los comentarios de nuestros lectores son siempre bienvenidos. Háganos saber lo que piensa sobre este libro: lo que le gustó o no. La retroalimentación de los lectores es importante para nosotros, ya que nos ayuda a desarrollar títulos a los que realmente les sacará el máximo provecho. Para enviarnos sus comentarios generales, simplemente envíe un correo electrónico a [email protected] y mencione el título del libro en el asunto de su mensaje. Si hay un tema en el que tiene experiencia y le interesa escribir o contribuir a un libro, consulte nuestra guía de autores en www.packtpub.com/authors.
Atención al cliente Ahora que usted es el orgulloso propietario de un libro de Packt, tenemos varias cosas que lo ayudarán a aprovechar su compra al máximo.
Descargar el código de ejemplo Puede descargar los archivos de código de ejemplo para este libro de su cuenta en http://www.packtpub.com. Si compró este libro en otro lugar, puede visitar http://www.packtpub.com/support y regístrese para recibir los archivos por correo electrónico directamente a usted. Puede descargar los archivos de código siguiendo estos pasos: 1. Inicie sesión o regístrese en nuestro sitio web usando su dirección de correo electrónico y contraseña. 2. 3. 4. 5. 6. 7.
Coloque el puntero del mouse en la pestaña SOPORTE en la parte superior. Haga clic en Descargas de código y errata. Ingrese el nombre del libro en el cuadro de búsqueda. Seleccione el libro para el que desea descargar los archivos de código. Elija del menú desplegable donde compró este libro. Haga clic en Descargar Código.
Una vez que se haya descargado el archivo, asegúrese de descomprimir o extraer la carpeta con la última versión de: WinRAR / 7-Zip para Windows Zipeg / iZip / UnRarX para Mac 7-Zip / PeaZip para Linux
[4]
Preface
El conjunto completo de códigos también se puede descargar desde el siguiente repositorio de GitHub: ht tps://github.com /Packt Publishing/B uilding-Moder n-Web-Application -using-An gu la r .
También tenemos otros paquetes de códigos de nuestro rico catálogo de libros y videos disponibles en: https://github.c om/PacktPub lishing /. Échales un vistazo!
Errata Aunque hemos tomado todas las precauciones para garantizar la precisión de nuestro contenido, los errores ocurren. Si encuentra un error en uno de nuestros libros, tal vez un error en puede el textosalvar o el código, estaríamos pudiera informarnos deversiones esto. Al hacerlo, a otroslelectores de laagradecidos frustración ysiayudarnos a mejorar las posteriores de este libro. Si encuentra alguna errata, infórmenos visitando http:// www.packtpub.com/submit-errata, seleccionando su libro, haciendo clic en el enlace Errata Submission Form e ingresando los detalles de su errata. Una vez que se verifique su errata, se aceptará su envío y la errata se cargará en nuestro sitio web o se agregará a cualquier lista de erratas existentes en la sección de Erratas de ese título. Para ver la errata enviada anteriormente, vaya a https://www.packtpub.com/books/content/ support e ingrese el nombre del libro en el campo de búsqueda. La información requerida aparecerá debajo de la sección Errata.
Piratería La piratería de material protegido por derechos de autor en Internet es un problema constante en todos los medios. En Packt, tomamos muy en serio la protección de nuestros derechos de autor y licencias. Si encuentra alguna copia ilegal de nuestros trabajos en cualquier forma en Internet, proporcione la dirección de la ubicación o el nombre del sitio web de inmediato para que podamos buscar un remedio. Contáctenos en [email protected] con un enlace al material sospechoso de piratería. Agradecemos su ayuda para proteger a nuestros autores y nuestra capacidad de brindarle contenido valioso.
Preguntas Si tiene algún problema con algún aspecto de este libro, puede contactarnos en [email protected] y haremos nuestro mejor esfuerzo para resolver el problema.
[5]
Empezando En este capítulo, vamos a aprender los conceptos básicos del framework angular y las nuevas características. Aprenderemos cómo usar las características de futuras versiones de JavaScript y TypeScript para desarrollar aplicaciones web modernas usando Angular. Después de pasar por este capítulo, el lector comprenderá los siguientes conceptos: El framework angular y sus nuevas características Configuración del entorno para el desarrollo Lo básico de TypeScript Conceptos básicos de aplicaciones angulares ¿Cómo escribir tu primera aplicación Angular?
Introducción a Angular ¿Qué es angular? Es un Framework JavaScript moderno de código abierto para construir la web, web móvil, móvil nativo y aplicaciones de escritorio nativas. También se puede usar en combinación con cualquier framework de aplicación web del lado del servidor, como ASP.NET y Node.js. Angular es el sucesor de AngularJS 1, que es uno de los mejores frameworks de JavaScript para construir aplicaciones web del lado del cliente, utilizado por millones de desarrolladores en todo el mundo. Aunque la comunidad de desarrolladores aprecia mucho AngularJS 1, tiene sus desafíos. Muchos desarrolladores consideran que la curva de aprendizaje AngularJS 1 es alta. El rendimiento fuera de la caja en aplicaciones complejas con una gran cantidad de datos no es tan bueno, lo cual es esencial en las aplicaciones empresariales. La API para construir directivas es muy confusa y compleja, incluso para un programador experimentado de AngularJS 1, que es el concepto clave para construir aplicaciones UI basadas en una arquitectura basada en componentes.
Getting Started
Angular es la próxima versión de AngularJS 1.x, pero es una reescritura completa de su predecesor. Está construido sobre los últimos estándares web (componentes web, Observables y decoradores), la curva de aprendizaje es mínima y el rendimiento es mejor. Este libro cubrirá la última versión de Angular, que es la versión 4, al momento de escribir este libro. Angular 4 es una actualización incremental desde Angular 2, a diferencia de Angular 2. Cuando usamos el término Angular, nos referimos a la última versión del framework. Donde sea que necesitemos discutir la versión 1.x, usaremos el término AngularJS.
¿Qué hay de nuevo en Angular? Muchos conceptos en la versión 1.x son irrelevantes en Angular, como controlador, objeto de definición de directiva, jqLite, $scope y $watch. A pesar de que muchos conceptos son irrelevantes, gran cantidad de bondad en AngularJS 1.x se traslada a Angular como servicios, inyección de dependencia y pipes. Aquí hay una lista de las nuevas características en Angular:
Componentes Nueva sintaxis de plantillas Flujo de datos unidireccionales Detección de cambios ultrarápida Nuevo componente router Observables Representación del lado del servidor Nuevos lenguajes para el desarrollo ES2015 TypeScript Compilación anticipada
Configuración del entorno Podemos comenzar a desarrollar nuestras aplicaciones angulares sin ninguna configuración. Sin embargo, vamos a usar Node.js y npm (administrador de paquetes de node) para fines de herramientas. Los necesitamos para descargar herramientas, bibliotecas y paquetes. También los usamos para la automatización de procesos de construcción. Las aplicaciones angulares son las siguientes:
[7]
Getting Started
Node.js es una plataforma construida sobre V8, el runtime de JavaScript de Google, que también impulsa el navegador Chrome Node.js se usa para desarrollar aplicaciones de JavaScript en el lado del servidor El npm es un administrador de paquetes para Node.js, lo que hace que sea bastante simple instalar herramientas adicionales a través de paquetes; viene incluido con Node.js
Instalando Node.js y npm La forma más fácil de instalar Node es seguir las instrucciones en: https://nodejs.org. Descargue la última versión de Node para el sistema operativo respectivo e instálelo. La npm se instala como parte de la instalación de Node.js. Una vez que instalemos Node.js, ejecute los siguientes comandos en la línea de comando en Windows o Terminal en macOS para verificar que Node.js y npm estén instalados y configurados correctamente: $ node -v $ npm -v
Los comandos anteriores mostrarán las versiones actualmente instaladas de Node.js y npm, respectivamente:
La documentación angular recomienda instalar al menos Node.js v4.x.x, NPM 3.x.x o versiones superiores.
Opciones de lenguaje Podemos escribir las aplicaciones de angular en JavaScript o en cualquier idioma que se pueda compilar en JavaScript. Aquí, vamos a discutir las tres opciones principales de lenguaje.
[8]
Getting Started
ECMAScript 5 ECMAScript 5 (ES5) es la versión de JavaScript que se ejecuta en los navegadores de hoy. El código escrito en ES5 no requiere ninguna transpilación o compilación adicional, y no hay una nueva curva de aprendizaje. Podemos usar esto tal como es para desarrollar aplicaciones angulares.
ECMAScript 2015 ECMAScript 2015 (ES2015), anteriormente conocido como ECMAScript 6 (ES6), es la próxima versión de JavaScript. ES2015 es es mucho una actualización del lenguaje JavaScript; el código escrito en ES2015 más limpiosignificativa que ES5. ES2015 incluye muchas características nuevas para aumentar la expresividad del código JavaScript (por ejemplo, classes, arrows, y template strings). Sin embargo, los navegadores de hoy en día no son compatibles con todas las funciones de ES2015 por completo (para obtener más información, consulte:https ://caniuse.com /#sea rch=es6). Necesitamos utilizar transpilers como Babel para transformar el código ES2015 en código compatible con ES5 para que funcione en los navegadores actuales. Babel es un transpiler de JavaScript y transpila nuestro código escrito en ES2015 a ES5 que se ejecuta en nuestros navegadores (o en su servidor) en la actualidad. Obtenga más información sobre Babel en https://babe ljs.io .
TypeScript TypeScript es un superconjunto de JavaScript, lo que significa que todo el código escrito en JavaScript es un código de TypeScript válido, y TypeScript compila de nuevo a código JavaScript simple basado en estándares, que se ejecuta en cualquier navegador, para cualquier host, en cualquier sistema operativo. TypeScript nos permite escribir JavaScript idiomático. Proporciona tipos estáticos opcionales que son útiles para crear aplicaciones JavaScript escalables y otras características como clases, módulos y decoradores de versiones futuras de JavaScript. Proporciona las siguientes características para mejorar la productividad del desarrollador y crear aplicaciones escalables de JavaScript: Comprobación estática Navegación basada en símbolos Finalización de declaración Código de refactorización
[9]
Getting Started
Estas características son críticas en desarrollos de aplicaciones de JavaScript a gran escala, por lo que en este libro, vamos a usar TypeScript para escribir aplicaciones de angular. El framework angular en sí fue desarrollado en TypeScript. Todo el código de ECMAScript 5 es válido en ECMAScript 2015; todo el código de ECMAScript 2015 es un código válido de TypeScript.
Instalando TypeScript Podemos instalar TypeScript de múltiples maneras, y vamos a usar npm, que instalamos anteriormente. Vaya a la línea de comando en Windows o Terminal en macOS y ejecute el siguiente comando: $ npm install -g typescript
El comando anterior instalará el compilador de TypeScript y lo hará disponible globalmente.
Conceptos básicos de TypeScript: tipos TypeScript usa .ts como una extensión de archivo. Podemos compilar código de TypeScript en JavaScript invocando el compilador de TypeScript utilizando el siguiente comando: $ tsc
JavaScript es un lenguaje débilmente tipado, y no es necesario que declare el tipo de una variable antes de tiempo. El tipo se determinará automáticamente mientras se ejecuta el programa. Debido a la naturaleza dinámica de JavaScript, no garantiza ningún tipo de seguridad. TypeScript proporciona un sistema de tipo opcional para garantizar la seguridad del tipo.
[ 10 ]
Getting Started
En esta sección, vamos a aprender los tipos de datos importantes en TypeScript.
String En TypeScript, podemos usar comillas dobles (") o comillas simples (') para rodear cadenas similar a JavaScript. var bookName: string = "Angular"; bookName = 'Angular UI Development';
Number Como en JavaScript, todos los números en TypeScript son valores de punto flotante: var version: number = 4;
Boolean El tipo de datos booleanos representa el valor verdadero/falso: var isCompleted: boolean = false;
Array Tenemos dos sintaxis diferentes para describir arrays, y la primera sintaxis usa el tipo de elemento seguido de []: var fw: string[] = ['Angular', 'React', 'Ember'];
La segunda sintaxis usa un tipo genérico de array , Array: var fw: Array = ['Angular', 'React', 'Ember'];
Enum TypeScript incluye el tipo de datos enum junto con el conjunto estándar de tipos de datos de JavaScript. En idiomas como C # y JAVA, una enumeración es una forma de dar nombres más amigables a conjuntos de valores numéricos, como se muestra en el siguiente fragmento de código: enum Frameworks { Angular, React, Ember }; var f: Frameworks = Frameworks.Angular;
[ 11 ]
Getting Started
La numeración de los miembros en el tipo enum comienza con 0 de manera predeterminada. Podemos cambiar esto configurando manualmente el valor de uno de sus miembros. Como se ilustró anteriormente, en el fragmento de código, Frameworks.Angular su valor es 0, Frameworks.React su valor es 1 y Frameworks.Ember su valor es 2. Podemos cambiar el valor de inicio del tipo enum a 5 en lugar de 0, y los miembros restantes siguen el valor inicial: enum Frameworks { Angular = 5, React, Ember }; var f: Frameworks = Frameworks.Angular; var r: Frameworks = Frameworks.React;
En el fragmento de código anterior, Frameworks.Angular su valor es 5, Frameworks.React su valor es 6 y Frameworks.Ember su valor es 7.
Any Si necesitamos rechazar la verificación de tipos en TypeScript para almacenar cualquier valor en una variable cuyo tipo no se conoce de inmediato, podemos usar cualquier palabra clave para declarar esa variable: var eventId: any = 7890; eventId = 'event1';
En el fragmento de código anterior al declarar una variable, la estamos inicializando con el valor numérico, pero más adelante estamos asignando el valor de cadena a la misma variable. El compilador de TypeScript no informará ningún error debido a una palabra clave. Aquí hay un ejemplo más de una array que almacena diferentes valores de tipo de datos: var myCollection:any[] = ["value1", 100, 'test', true]; myCollection[2] = false;
Void La palabra clave void representa no tener ningún tipo de datos. Las funciones sin palabra clave return no devuelven ningún valor, y utilizamos void para representarlo. function simpleMessage(): void { alert("Hey! I return void"); }
[ 12 ]
Getting Started
Escribamos nuestro primer ejemplo de TypeScript y guárdelo en el archivo example1.ts: var bookName: string = 'Angular UI Development'; var version: number = 2; var isCompleted: boolean = false; var frameworks1: string[] = ['Angular', 'React', 'Ember']; var frameworks2: Array = ['Angular', 'React', 'Ember']; enum Framework { Angular, React, Ember }; var f: Framework = Framework.Angular; var eventId: any = 7890; eventId = 'event1'; var myCollection:any[] = ['value1', 100, 'test', true]; myCollection[2] = false;
Vamos a compilar el archivo example1.ts usando el compilador de línea de comandos de TypeScript: $ tsc example1.ts
El comando anterior compilará código de TypeScript en código JavaScript simple en el archivo example1.js así es como se verá, y es simple JavaScript: var bookName = 'Angular UI Development'; var version = 2; var isCompleted = false; var frameworks1 = ['Angular', 'React', 'Ember']; var frameworks2 = ['Angular', 'React', 'Ember']; var Framework; (function (Framework) { Framework[Framework["Angular"] = 0] = "Angular"; Framework[Framework["React"] = 1] = "React"; Framework[Framework["Ember"] = 2] = "Ember"; })(Framework || (Framework = {})); ; var f = Framework.Angular; var eventId = 7890; eventId = 'event1'; var myCollection = ['value1', 100, 'test', true]; myCollection[2] = false;
Functions Las funciones son los pilares fundamentales de cualquier aplicación de JavaScript. En JavaScript, declaramos las funciones de dos maneras.
[ 13 ]
Getting Started
Declaración de función - función nombrada
El siguiente es un ejemplo de declaración de función: function sum(a, b) { return a + b; }
Expresión de función - función anónima El siguiente es un ejemplo de expresión de función: var result = function(a, b) { return a + b; }
En JavaScript, a diferencia de cualquier otro concepto, tampoco existe ningún tipo de seguridad para las funciones. No tenemos ninguna garantía sobre los tipos de datos de los parámetros, el tipo de devolución, el número de parámetros pasados a la función, TypeScript garantiza todo esto. Es compatible con ambas sintaxis. Estas son las mismas funciones escritas en TypeScript: function sum(a: number, b: number): number { return a + b; } var result = function(a: number, b: number): number { return a + b; }
Las funciones de TypeScript anteriores son muy similares a la sintaxis de JavaScript, excepto los parámetros, y el tipo de retorno tiene texto, lo que garantiza la seguridad del tipo al invocarlos.
Classes El ES5 no tiene el concepto de clases. Sin embargo, podemos imitar la estructura de clases usando diferentes patrones de JavaScript. El ES2015 no admite clases. Sin embargo, hoy en día, ya podemos escribirlos en TypeScript. De hecho, la sintaxis de clase ECMAScript 2015 está inspirada en TypeScript.
[ 14 ]
Getting Started
El siguiente ejemplo muestra una clase persona escrita en TypeScript: class Person { name: string; constructor(name: string) { this.name = name; } sayHello() { return 'Hello ' + this.name; } } var person = new Person('Shravan'); console.log(person.name); console.log(person.sayHello());
La clase Person anterior tiene tres miembros: una propiedad llamada name, un constructor, y un método sayHello. Deberíamos usar esta palabra clave para referirnos a las propiedades de la clase. Creamos una instancia de la clase Person cutilizando el nuevo operador. En el siguiente paso, invocamos el método sayHello() de la clase Person utilizando su instancia creada en el paso anterior. Guarde el código anterior en el archivo person.ts y compílelo utilizando el compilador de línea de comandos de TypeScript. Compilará código de TypeScript en código JavaScript simple en el archivo person.js: $ tsc person.ts
Aquí está el código JavaScript simple, que fue compilado de la clase TypeScript: var Person = (function () { function Person(name) { this.name = name; } Person.prototype.sayHello = function () { return 'Hello ' + this.name; }; return Person; }()); var person = new Person('Shravan'); console.log(person.name); console.log(person.sayHello());
[ 15 ]
Getting Started
Para obtener más información acerca de las funciones, clases y otros conceptos en TypeScript, consulte: http ://typescript lang.org .
Escribiendo tu primera aplicación Angular Angular sigue un enfoque basado en componentes para crear una aplicación. Una aplicación escrita en AngularJS 1 es un conjunto de controladores y vistas individuales, pero en Angular, debemos tratar nuestra aplicación como un árbol de componentes. El árbol de componentes de la aplicación angular tendrá un componente raíz; actuará como el punto de entrada de la aplicación. Todos los demás componentes que forman parte de la aplicación se cargarán dentro del componente raíz, y se pueden anidar de la manera que sea necesaria dentro del componente raíz. Angular también tiene el concepto de módulos, que se usan para agrupar componentes con funcionalidades similares. Una aplicación angular debe tener un mínimo de un módulo y un mínimo de un componente que debe ser parte de ese módulo. El componente actúa como componente raíz y el módulo actúa como módulo raíz.
Configure la aplicación angular Vamos a ycomenzar carpetas archivos:a escribir su primera aplicación Angular creando la siguiente estructura de hello-world €• index.html €• package.json €• tsconfig.json ‚• src ‚• app.ts
Paso 1 Como vamos a escribir nuestra aplicación en TypeScript, comencemos con archivo eltsconfig.json primero. Es el archivo de configuración de TypeScript que contiene instrucciones para su compilador sobre cómo compilar el código de TypeScript en JavaScript. Si no usamos el archivo tsconfig.json, el compilador de TypeScript usa los indicadores predeterminados la compilación, o podemos pasar nuestros indicadores de forma manual cada vez quedurante compilamos.
[ 16 ]
Getting Started
El archivo tsconfig.json es la mejor manera de pasar las banderas al compilador de TypeScript, por lo que no es necesario que las escriba cada vez. Algunos de los indicadores aquí son obligatorios para la aplicación angular escrita en TypeScript; vamos a usar este archivo a lo largo del libro. Agregue el siguiente código al archivo tsconfig.json: { "compilerOptions": { "target": "es5", "module": "commonjs", "moduleResolution": "node", "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "lib": ["es2015", "dom"], "noImplicitAny": true, "suppressImplicitAnyIndexErrors": true, "typeRoots": ["node_modules/@types/"] }, "compileOnSave": true, "exclude": ["node_modules/*"] }
Descargar el código de ejemplo
Los pasos detallados para descargar el paquete de códigos se mencionan en el Prefacio de este libro. El paquete de códigos para el libro también está alojado en GitHub en: ht t ps://github.com/Pa cktPublishing /Buildi ng-Modern- Web-A pplicatio
ambién tenemos otros paquetes de códigos de nuestro rico catálogo de libros y videos disponibles en: https://github.com/PacktPublishin g /. Échales un vistazo! n-using-Angu la r .
La explicación para los indicadores especificados en el archivo tsconfig.json son los siguientes: target: Especifica la versión de destino de ECMAScript: es3 (predeterminado), es5 o es2015 module: Especifica la generación de código del módulo: commonjs, amd, system, umd
o es2015
Especifica la estrategia de resolución del módulo: node (Node.js) o classic (TypeScript pre-1.6) sourceMap: Si es verdadero, genera el archivo .map correspondiente para el archivo .js emitDecoratorMetadata: Si es verdadero, habilita el JavaScript de salida para crear los metadatos para los decoradores experimentalDecorators: Si es verdadero, habilita el soporte experimental para decoradores de ES7 moduleResolution:
[ 17 ]
Getting Started lib:
Especifica los archivos de la biblioteca que se incluirán en la compilación
noImplicitAny: Si es verdadero, genera error si usamos cualquier tipo de
expresiones y declaraciones
Paso 2 Agreguemos el siguiente código al archivo package.json, que contiene metadatos para npm y contiene todos los paquetes angulares y bibliotecas de terceros necesarios para el desarrollo de aplicaciones en angular: { "name": "hello-world", "version": "1.0.0", "scripts": { "prestart": "npm run build", "start": "concurrently \"tsc -w\" \"lite-server\"", "build": "tsc" }, "license": "ISC", "dependencies": { "@angular/common": "^4.0.0", "@angular/compiler": "^4.0.0", "@angular/core": "^4.0.0", "@angular/platform-browser": "^4.0.0", "@angular/platform-browser-dynamic": "^4.0.0", "core-js": "^2.4.1", "rxjs": "^5.1.0", "systemjs": "0.20.12", "zone.js": "^0.8.4" }, "devDependencies": { "@types/node": "^6.0.45", "concurrently": "^3.4.0", "lite-server": "^2.3.0", "typescript": "~2.2.0" } }
En el fragmento de código anterior, hay dos secciones importantes: Esto contiene todos los paquetes necesarios para que la aplicación se ejecute devDependencies: Esto contiene todos los paquetes necesarios solo para el desarrollo dependencies:
[ 18 ]
Getting Started
Una vez que agreguemos el código anterior al archivo package.json en nuestro proyecto, debemos ejecutar el siguiente comando en la raíz de nuestra aplicación: $ npm install
El comando anterior creará la carpeta node_modules en la raíz del proyecto y descargará todos los paquetes mencionados en las secciones de dependencies y devDependencies en la carpeta node_modules, Hay una sección más en el archivo package.json, es decir, scripts. Discutiremos la sección de scripts cuando estemos listos para ejecutar nuestra aplicación.
Paso 4 Tenemos la configuración básica lista para nuestra aplicación; ahora vamos a escribir un código comenzando con un módulo. Un módulo angular en TypeScript es simplemente una clase anotada con el decorador @NgModule(). Agregue el siguiente código al archivo app.ts en la carpeta src: import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @NgModule({ imports: [BrowserModule], declarations: [], bootstrap: [] }) class HelloWorldAppModule {}
Importamos el decorador NgModule desde el módulo @angular/core y BrowserModule desde el módulo @angular/platform-browserusando la sintaxis imports de ES2015; discutiremos estos módulos más adelante. Declaramos una clase y la anotamos con el decorador @NgModule(). El decorador @NgModule() toma un objeto de configuración como parámetro con un par de propiedades; esto es lo que quieren decir.
[ 19 ]
Getting Started imports
Necesitamos especificar los otros módulos de los cuales depende nuestro módulo. Vamos a ejecutar nuestra aplicación en un navegador, por lo que nuestro módulo depende de BrowserModule; lo importamos y lo agregamos a esta matriz.
declarations
Necesitamos especificar que los componentes, las directivas y las pipes que pertenecen a este módulo.
bootstrap
Necesitamos especificar los componentes que deben ser bootstrapped cuando este módulo es bootstrapped. Los componentes agregados aquí se agregarán automáticamente a la propiedad entryComponents. Los componentes especificados en entryComponents se compilarán cuando se defina el módulo.
El @NgModule() es un decorador; el decorador es una función que agrega los metadatos a la clase declarativamente sin modificar su comportamiento srcinal.
Paso 5 En el paso anterior, creamos un módulo, pero el módulo no hace nada. Es solo un contenedor para componentes; la lógica real debe escribirse dentro de un componente. Escribamos nuestro primer componente en Angular. Un componente angular en TypeScript es simplemente una clase anotada con el decorador @Component(): @Component({}) class HelloWorldAppComponent {}
El decorador @Component() le dice a Angular que esta clase es un componente angular, y podemos pasar un objeto de configuración a la función @Component() que tiene dos propiedades: un selector y una plantilla: @Component({ selector: 'hello-world-app', template: '
Say Hello to Angular
' })
La propiedad del selector especifica un selector de CSS (nombre de etiqueta personalizado) para el componente que se puede usar en HTML.
[ 20 ]
Getting Started
La propiedad de la template especifica la plantilla HTML para el componente que le dice a Angular cómo renderizar una vista. Nuestra plantilla es una sola línea de HTML Say Hello to Angular rodeado con la etiqueta h1. También podemos especificar una cadena de plantilla multilínea. En lugar de usar la plantilla en línea, podemos usar la plantilla externa almacenada en un archivo diferente usando la propiedad templateUrl. Agreguemos este código al archivo app.ts en la carpetasrc: import { Component } from '@angular/core'; @Component({ selector: 'hello-world-app', template: '
Say Hello to Angular
' }) class HelloWorldAppComponent {}
Paso 6 Tenemos nuestro componente listo, y necesitamos asociar este componente a un módulo. Agreguemos el componente al array declarations del módulo de la aplicación creada en el Paso 4, y también necesitamos que este componente se inicie tan pronto como se inicialice un módulo, así que agréguelo también al array bootstrap . Vamos a agregar todo este código al archivo app.ts en la carpeta src: import { NgModule, Component } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @Component({ selector: 'hello-world-app', template: '
Say Hello to Angular
' }) class HelloWorldAppComponent {} @NgModule({ imports: [BrowserModule], declarations: [HelloWorldAppComponent], bootstrap: [HelloWorldAppComponent] }) class HelloWorldAppModule {}
[ 21 ]
Getting Started
Paso 7 El siguiente paso es arrancar nuestro módulo. Esto puede hacerse usando el método bootstrapModule(); está disponible en la clase PlatformRef. Podemos obtener la instancia de la clasePlatformRef utilizando la función platformBrowserDynamic() disponible en el módulo @angular/platform-browser-dynamic: import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
El archivo app.ts finalmente se ve de la siguiente manera después de agregar la lógica de arranque del módulo: import { NgModule, Component } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; @Component({ selector: 'hello-world-app', template: '
Say Hello to Angular
' }) class HelloWorldAppComponent { } @NgModule({ imports: [BrowserModule], declarations: [HelloWorldAppComponent], bootstrap: [HelloWorldAppComponent] }) class HelloWorldAppModule { } platformBrowserDynamic().bootstrapModule(HelloWorldAppModule);
Paso 8 Usemos el componente que creamos en el paso anterior, agreguemos el siguiente código enindex.html: Angular Hello World <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1">
Están sucediendo muchas cosas en el index.html; comprendamos paso a paso.
SystemJS Incluimos SystemJS y su configuración, entonces, ¿qué es SystemJS? SystemJS es un cargador universal de módulos dinámicos. Puede cargar ES2015, AMD, módulos CommonJS y scripts globales en el navegador y Node.js.
[ 23 ]
Getting Started <script src="node_modules/systemjs/dist/system.src.js"> <script> System.config({}); System.import('src');
¿Por qué necesitamos SystemJS? Si nos referimos a nuestros pasos anteriores, importamos NgModule, Component desde el modulo @angular/core, BrowserModule del módulo @angular/platform-browser, la función platformBrowserDynamicdel módulo @angular/platform-browser-dynamic. El módulo @angular/core es un archivo físico node_modules/
de JavaScript disponible en nuestra raíz de.la aplicación en la ruta @angular/core/bundles/core.umd.js Normalmente, para usar cualquier cosa (variables, funciones, objetos, etc.) en un archivo JavaScript externo, necesitamos agregarlo a nuestro HTML utilizando la etiqueta <script>. En lugar de usar la etiqueta tradicional <script> para cargar nuestros archivos JavaScript, que es lenta y sincrónica, podemos usar la función de módulos ES2015 para cargar nuestros archivos JavaScript dinámicamente, de forma asíncrona y bajo demanda. La carga del módulo se puede hacer usando SystemJS, y en lugar de SystemJS, también podemos usar otras alternativas como webpack y rollup. Necesitamos decirle al SystemJS cómo cargar módulos usando su configuración. En la configuración de SystemJS, el objeto mapa le dice dónde buscar los archivos de JavaScript, el objeto packages le dice cómo cargarlo cuando no se especifica ningún nombre de archivo y no se especifica ninguna extensión de archivo. Después de la configuración, necesitamos informar una cosa más a SystemJS que es nuestro archivo principal, que sistema carga primero y comienza la ejecución. Esto se hace usando el método System.import(). En nuestro código, especificamos System.import('src'). SystemJS busca la carpeta src en la configuración; especificamos que el archivo app.js es el archivo principal para él. SystemJS cargará el archivo app.js bajo src y comenzará la ejecución. Actualmente, solo tenemos un archivo JavaScript con toda la lógica en nuestra aplicación, app.js.
Uso del componente angular En la sección de etiqueta body estamos usando la etiqueta que hemos declarado en HelloWorldAppComponent y mostrará su vista: ,
,
Loading...
[ 24 ]
Getting Started
Comprender los paquetes npm Veamos el Paso 3; en la sección dependencies del archivos package.json, tenemos muchos paquetes. Los descargamos, y algunos de ellos se cargan usando la etiqueta <script>, mientras que algunos de ellos se cargan usando SystemJS. Algunos de estos paquetes son parte del framework angular; algunos son bibliotecas de terceros. Los siguientes son los paquetes de angular: @angular/core: Este paquete contiene partes de tiempo de ejecución críticas del framework
angular requerido por cada aplicación. Incluye todos los decoradores de metadatos, inyección de dependencia, NgModule, Componente, directiva, los ganchos del ciclo de vida de los componentes, proveedores principales, enlaces para detección de cambios y Observables. @angular/common: Este paquete contiene directivas comunes(ngClass, ngFor, ngIf, ngPlural, ngPluralCase, ngStyle, ngSwitch, ngSwitchCase, ngSwitchDefault, y ngTemplateOutlet), pipes (AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, y UpperCasePipe), y servicios. @angular/compiler:
Este paquete contiene lógica para el compilador de
plantilla angular. @angular/platform-browser: Este paquete contiene todo lo
relacionado
con DOM y navegador. @angular/platform-browser-dynamic: Este paquete contiene todo lo
relacionado con el arranque de nuestras aplicaciones durante el desarrollo. Los siguientes son las dependencies: rxjs: Esta es una biblioteca de extensiones reactivas para JavaScript basada en
Observables, que Angular utiliza. JavaScript nativo no los admite, y hay una propuesta para agregarlos como lenguaje central en futuras versiones. Sin embargo, debemos incluir RxJS y su dependencia obligatoria. Lo estamos cargando usando la configuración de SystemJS. zone.js: Esta biblioteca contiene toda la lógica para la detección de cambios; hay una propuesta para agregarlos como lenguaje central. Sin embargo, debemos incluir zone.js y su dependencia obligatoria. Lo cargamos usando la etiqueta <script>. El paquete @angular/core depende de rxjs, zone.js.
[ 25 ]
Getting Started
es un polyfill para ECMAScript 2015 y las próximas características del lenguaje JavaScript. Necesitamos esto para que Angular funcione en navegadores IE más antiguos, siempre que sea compatible, lo cargamos usando la etiqueta <script>. core-js:
Paso 9 Nuestra aplicación está lista, pero escribimos nuestro código en TypeScript; tiene que ser compilado en JavaScript. Una vez hecho esto, necesitamos que el servidor web ejecute nuestra aplicación. Una vez más, si nos referimos a package.json en el Paso 3 en la sección devDependencies, especificamos los paquetes typescript, lite-server, y concurrently. Estos se descargan durante la ejecución de la instalación npm. El paquete typescript contiene su compilador; El paquete lite-server es un servidor web liviano basado en node y concurrently nos permite ejecutar varios comandos al mismo tiempo. Ahora, si revisamos la sección de scripts hay tres comandos: "scripts": { "prestart": "npm run build", "start": "concurrently \"tsc -w\" "build": "tsc" }
\"lite-server\"",
El comando build cinvoca el compilador de TypeScript utilizando el comando tsc. El comando start invoca al compilador de TypeScript en el modo de vigilancia, y simultáneamente ejecuta el comando lite-sever, que abrirá el servidor web. El comando prestart se invoca automáticamente antes del comando start, y simplemente estamos invocando el comando build en él. Podemos invocar estos comandos en la sección de scripts usando npm run ; Los comandos predefinidos de npm se pueden ejecutar usando npm .
Paso 10 Finalmente, ejecutemos nuestra primera aplicación angular, ejecute el siguiente comando en la carpeta raíz de nuestra aplicación: $ npm start
[ 26 ]
Getting Started
El comando anterior compilará nuestro código TypeScript e iniciará el servidor web. El servidor web iniciará nuestra aplicación e iniciará el navegador predeterminado y mostrará el mensaje Say Hello to Angularen el navegador.
¿Entonces qué pasó? Aquí están los siguientes pasos sucedidos entre una vez que se inició el servidor web, y vimos el resultado en el navegador. Tan pronto como un servidor web carga la página index.html, el navegador carga los archivos especificados en las etiquetas scripts. Una vez que el navegador carga SystemJS, lee su configuración y carga el archivo app.js (que se compila desde el archivo app.ts). En el archivo app.js, se cargan los módulos @angular/core, @angular/platformbrowser, @angular/platform-browser-dynamic; cargarán sus módulos dependientes. platformBrowserDynamic() La función nuestro HelloWorldAppModule, mientras inicia el HelloWorldAppModule, agregaráinicia HelloWorldAppComponenten la propiedad bootstrap a sus entryComponents.
Una vez que se haya agregado HelloWorldAppComponenta entryComponents, Angular lo compilará y creará una instancia de ComponentFactory y la almacenará en la instancia de ComponentFactoryResolver. Finalmente, la etiqueta en index.html representará la plantilla de salida del componente HelloWorldAppComponent. Las características de ES2015 se explicarán siempre que se usen en el código.
[ 27 ]
Getting Started
Uso de
Angular CLI
En la sección anterior Escribiendo su primera aplicación angular, aprendimos cómo escribir una aplicación Hello World desde cero. Para escribir una sencilla aplicación de hello world, inicialmente tuvimos que crear muchos archivos con código repetitivo y configuración de proyecto. Este proceso es común para aplicaciones pequeñas y grandes. Para aplicaciones grandes, creamos una gran cantidad de módulos, componentes, servicios, directivas y pipes con código repetitivo y configuración de proyectos. Este es un proceso muy lento. Como queremos ahorrar tiempo y ser productivos al enfocarnos en resolver problemas comerciales en lugar de perder tiempo en tareas tediosas, las herramientas son útiles. El equipo Angular creó una herramienta de línea de comando conocida como Angular CLI. Angular CLI nos ayuda a generar proyectos angulares con configuraciones requeridas, código repetitivo y también descarga los paquetes de node requeridos con un simple comando. También proporciona comandos para generar componentes, directivas, pipes, servicios, clases, guardias, interfaces, enumeraciones, módulos, módulos con enrutamiento y creación, ejecución y prueba de las aplicaciones localmente.
Empezar con Angular CLI Angular CLI está disponible como un paquete de node. Primero, tenemos que descargarlo e instalarlo con el siguiente comando: $ npm install -g @angular/cli
El comando anterior instalará Angular CLI, luego podemos acceder a él en cualquier lugar a través de la línea de comando o Terminal. Para generar el proyecto angular utilizando CLI, podemos usar el comando ng new project-name. $ ng new first-ng-cli-project
Este comando crea una carpeta llamada first-ng-cli-project, genera un proyecto angular debajo de ella con todos los archivos necesarios y descarga todos los paquetes de nodo. Para ejecutar la aplicación, necesitamos navegar a la carpeta del proyecto y ejecutar el comando ng serve: $ cd first-ng-cli-project $ ng serve
[ 28 ]
Getting Started
El comando ng serve compila y construye el proyecto e inicia el servidor web local en la URL http://localhost:4200. Cuando navegamos a la URL http://localhost:4200 en el navegador, vemos el siguiente resultado:
El resultado es muy simple, pero generamos todo el proyecto y lo pusimos en marcha con dos comandos simples. Aquí hay algunos otros comandos para generar diferentes tipos de archivos usando Angular CLI. Componente
ng g component my-new-component
Directiva Pipe
ng g directive my-new-directive
Servicio
ng g service my-new-service
Módulo
ng g module my-module
Módulo con enrutamiento
ng g module my-module --routing
ng g pipe my-new-pipe
Para obtener más información sobre Angular CLI visite https://cli.angular.io .
[ 29 ]
Getting Started
Resumen Comenzamos este capítulo con una introducción a Angular y algunas de sus nuevas características. Luego, discutimos algunas herramientas que nos ayudarán durante el desarrollo de nuestra aplicación. A continuación, analizamos las diferentes opciones de idioma disponibles para crear aplicaciones angulares y también aprendimos TypeScript y su importancia. Aprendimos a configurar Angular para el desarrollo y escribimos nuestra primera aplicación angular. Finalmente, miramos Angular CLI y cómo acelera el desarrollo del proyecto. En el próximo capítulo, aprenderemos cómo escribir componentes, nuevas directivas y nueva sintaxis de plantillas en Angular.
[ 30 ]
Conceptos básicos de los componentes En este capítulo, aprenderemos cómo usar las nuevas características del framework angular, el enlace de datos de línea, el enlace de eventos y las directivas incorporadas para construir componentes. Después de pasar por este capítulo, el lector comprenderá los siguientes conceptos: Cómo escribir componentes en Angular Enlace de datos en Angular Sintaxis de la plantilla en Angular Directivas incorporadas en Angular
Empezando Angular ha sido completamente reescrito desde cero, por lo que hay muchos conceptos nuevos. En este capítulo, discutiremos algunas de las características importantes, como el enlace de datos, la nueva sintaxis de plantillas y las directivas incorporadas. Vamos a utilizar un enfoque más práctico para aprender estas nuevas características.
Configuración del proyecto Aquí hay una aplicación de muestra con la configuración requerida para Angular y código de muestra. Cree la estructura y los archivos del directorio como se menciona aquí: interpolation-syntax
Podemos usar el mismo código para los archivos tsconfig.json y package.json del Capítulo 1,, Empezando, simplemente cambie la propiedad del nombre a interpolation-syntax en el archivo package.json. En el Capítulo 1, Empezando, ejemplo Hello World, tenemos todo nuestro código solo en un archivo. Sin embargo, una vez que nuestra aplicación comienza a crecer, la capacidad de mantenimiento se convierte en un problema, por lo que vamos a dividir el código en tres archivos: src/main.ts:
Contiene toda la lógica de arranque, de la siguiente manera:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule);
src/app.module.ts :
Esto tendrá lógica de módulo de aplicación:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {}
src/app.component.ts:
Contiene la lógica del componente de aplicación real:
El archivo systemjs-angular-loader.js contiene lógica para cargar la plantilla y los archivos CSS con rutas relativas en el componente. Podemos copiar esto desde el código fuente provisto. Tenemos nuestra aplicación lista, así que ahora ejecute el comando npm install. Una vez que haya terminado, ejecute el comando npm start. Esto inicia nuestra aplicación en el navegador. Hasta ahora, lo que hemos creado no es diferente de la aplicación Hello World Capítulo 1, Empezando, a excepción del mensaje en pantalla:
Trabajando con datos En una aplicación web, necesitamos mostrar los datos en una página HTML y leer los datos de los controles de entrada en una página HTML. En Angular, todo es un componente; la página HTML se representa como una template, y siempre está asociada a una clase Component. Los datos de aplicación viven en las propiedades de clase del componente.
[ 34 ]
Basics of Components
O bien, enviamos valores a la template o extraemos valores de la template. Para hacer esto, debemos vincular las propiedades de la clase Component a los controles de la template. Este mecanismo se conoce como enlace de datos. El enlace de datos en Angular nos permite usar una sintaxis más simple para enviar o extraer datos.
Visualización de datos En esta sección, vamos a ver las diferentes sintaxis provistas por Angular para mostrar datos.
Sintaxis de interpolación Si recordamos nuestra clase AppComponent del ejemplo anterior, estamos mostrando un mensaje estáticamente en HTML dentro de la template. Aprendamos cómo mostrar el mismo mensaje almacenado en una propiedad de clase. Aquí está el código revisado para la clase AppComponent en el archivo src/app.component.ts: import { Component } from '@angular/core'; @Component({ selector: 'display-data-app', template: '
{{message}}
' }) export class AppComponent { message: string = 'Data Binding in Angular - Interpolation Syntax'; }
Una vez que tenemos el fragmento de código anterior, el navegador se actualizará automáticamente con la última versión, por lo que no es necesario que recargue el navegador manualmente. Esto sucede porque estamos ejecutando nuestro compilador de TypeScript en modo de vigilancia, compilará automáticamente cualquier archivo TypeScript modificado en archivos JavaScript. También estamos utilizando un servidor lite como nuestro servidor web, y esto actualizará automáticamente el navegador si cambia alguno de los archivos de la aplicación. Podemos modificar continuamente el código y ver la salida sin tener que volver a cargar el navegador. En la función Component() actualizamos la propiedad de la template con una expresión {{message}} rodeada por una etiqueta h1. Las llaves dobles son la sintaxis de interpolación en Angular.
[ 35 ]
Basics of Components
Para cualquier propiedad de la clase que necesitemos mostrar en la plantilla, podemos usar el nombre de la propiedad rodeado de llaves dobles. Angular representará automáticamente el valor de la propiedad en el navegador. Extendamos nuestro ejemplo y unamos la propiedad del mensaje a un cuadro de texto. Aquí está la template revisada: template: `
{{message}}
`
Observe que la template anterior es una cadena multilínea, y está rodeada por símbolos ` (backtick) en lugar de comillas simples o dobles. Los símbolos
`` (backtick) son la nueva sintaxis de string multilínea en ECMAScript
2015.
Ahora el cuadro de texto también muestra el mismo valor en la propiedad del mensaje. Cambiemos el valor en el cuadro de texto escribiendo algo, luego presione el botón Tabulador. No vemos cambios en el navegador. Siempre que modifiquemos el valor de cualquier control en la template, que está vinculado a una propiedad de una clase Component, debe actualizar el valor de la propiedad. Cualquier otro control vinculado a la misma propiedad también debe mostrar el valor actualizado en la template. En el navegador, la etiqueta
también debería mostrar el mismo texto, sea lo que sea que escribamos en el cuadro de texto, pero esto no sucederá.
[ 36 ]
Basics of Components
La sintaxis de interpolación es un enlace de datos unidireccional, y los flujos de datos desde el srcen de datos (clase Component) para ver (template). Solo el valor de la propiedad se actualiza en la template no ocurrirá al revés, es decir, los cambios realizados en los controles de la template no actualizarán el valor de la propiedad.
Enlace de propiedad Crea un ejemplo de property-binding del ejemplo anterior. Simplemente cambie la propiedad de nombre a property-binding en el package.json. Luego ejecute el comando npm install seguido del comando npm start. Cree la estructura y los archivos del directorio como se menciona aquí: property-binding €• index.html €• package.json €• src ƒ €• app.component.ts ƒ €• app.module.ts ƒ ‚• main.ts €• systemjs-angular-loader.js €• systemjs.config.js ‚• tsconfig.json
La vinculación de propiedad es otra forma de sintaxis de enlace de datos en Angular.
Sintaxis de enlace de propiedad: Puede ser cualquier etiqueta HTML o etiqueta personalizada Especifica la propiedad del elemento DOM correspondiente para la etiqueta HTML o el nombre de propiedad de la etiqueta personalizada rodeada de corchetes component-property-name: Especifica la propiedad de la clase o expresión del componente element-name:
element-property-name:
Aquí hay un ejemplo de enlace de propiedad en Angular: En el fragmento de código anterior, estamos vinculando la propiedad headerImage de la clase Component con el atributo src de la etiqueta .
[ 37 ]
Basics of Components
Un punto importante para recordar es que, a diferencia de AngularJS 1, Angular no vincula valores a atributos de elementos HTML. En cambio, se vinculará a las propiedades de los elementos DOM(Document Object Model) correspondientes. En el fragmento de código anterior para la etiqueta HTML, HTMLImageElement es la interfaz correspondiente en DOM. Para la mayoría de los atributos del elemento HTML, habrá mapeo uno a uno con sus propiedades de interfaz DOM correspondientes, pero hay excepciones. El enlace de propiedad funciona solo con propiedades, no atributos. Actualicemos nuestra template en el ejemplo de property-binding para utilizar el enlace de propiedades: template:
`
En lugar de utilizar la sintaxis de interpolación, estamos ajustando la propiedad textContent de la etiqueta
y la propiedad value de la etiqueta input entre corchetes cuadrados, y en el lado derecho de esta expresión, estamos asignando las propiedades de la clase Component. La salida será la misma que cuando estamos usando la sintaxis de interpolación. La sintaxis de enlace de propiedad también es un enlace de datos unidireccional, y los flujos de datos desde el srcen de datos (clase Component) para ver (template). Para la sintaxis de enlace de propiedad y la sintaxis de interpolación, podemos usarlos indistintamente; no hay más diferencias que la sintaxis. Podemos usar cualquier sintaxis que sea más idiomática para una situación dada. En lugar de utilizar la notación de corchetes para el enlace de propiedades, también podemos usar su nombre de propiedad de formulario canónico con el prefijo bind-:
Enlace de atributos Angular siempre usa propiedades para vincular los datos. Pero si no hay una propiedad correspondiente para el atributo de un elemento, Angular vinculará los datos a los atributos. La sintaxis de enlace de atributos comienza con la palabra clave attr seguida del nombre del atributo y luego lo asigna a la propiedad de la clase Component o una expresión:
[ 38 ]
Basics of Components
Enlace de eventos Mediante la sintaxis de enlace de eventos, podemos enlazar eventos de elementos HTML integrados, como click, change, blur, etc. a métodos de clase Component. También podemos enlazar eventos personalizados en componentes o directivas, que vamos a discutir en los capítulos que siguen. La sintaxis de enlace de eventos usa símbolos de paréntesis (). Necesitamos rodear el nombre de la propiedad del evento con los símbolos de paréntesis () en el lado izquierdo de la expresión, en el lado derecho especificaremos uno de los métodos de Component que se invocarán cuando se desencadene el evento. Cree otro ejemplo de event-binding del ejemplo anterior. Cambia la propiedad de nombre a event-binding en el archivo package.json. Luego ejecute el comando npm install seguido del comando npm start.
El código revisado para la clase AppComponent es el siguiente: export class AppComponent { public message: string = 'Angular - Event Binding'; showMessage() { alert("You pressed a key on keyboard!"); } }
Hemos agregado un método llamado showMessage() a la clase AppComponent class, este método se invocará siempre que ingresemos una clave en el cuadro de texto. El código revisado para la template en AppComponent es el siguiente: template: `
{{message}}
`
Hemos agregado un evento keypress rodeado de símbolos de paréntesis en el cuadro de texto para enlazar con el método showMessage() en la clase AppComponent. Actualicemos nuestro ejemplo para que sea un poco más realista, en lugar de mostrar el mismo diálogo de alert() cada vez que mostramos las claves que estamos escribiendo:
[ 39 ]
Basics of Components
El código para src/app.component.tses el siguiente: import { Component } from '@angular/core'; @Component({ selector: 'event-binding-app', template: `
Estas son las cosas importantes para notar en nuestro código: Para el método showMessage, estamos aprobando un objeto Angular $event especial La palabra clave $event representa el objeto de evento DOM actual En el método showMessage de la clase AppComponent, estamos aceptando $event pasado dede template en el parámetro del método onKeyPressEvent. Cada objeto de evento DOM tiene una propiedad target, que representa el elemento DOM en el que se genera el evento actual Estamos utilizando el objeto onKeyPressEvent.target, que representa el cuadro de texto Estamos utilizando la propiedad onKeyPressEvent.target.valuepara acceder al valor del cuadro de texto Estamos asignando el valor del cuadro de texto a la propiedad message Como resultado del código anterior, cualquier entrada que ingresemos en el cuadro de texto aparecerá en la salida de la etiqueta
porque también está vinculada a la propiedad message estamos actualizando el valor de la propiedad del mensaje siempre que escribamos algo en el cuadro de texto . La sintaxis de enlace de eventos también es un enlace de datos de una vía, pero los datos fluyen de la vista (template) a la clase Component.
[ 40 ]
Basics of Components
En lugar de usar la notación de símbolos () para el enlace de eventos, también podemos usar su nombre de evento de forma canónica con el prefijo on-:
Discutiremos el enlace de eventos nuevamente en capítulos futuros.
Enlace de Datos bidireccionales Requerimos que los datos fluyan en ambas direcciones, Component a template y viceversa. El ejemplo más clásico son los formularios. Necesitamos mostrar los valores de las propiedades en las vistas, y cuando el usuario actualice los valores de las vistas, necesitamos que se actualicen de nuevo a las propiedades.
La sintaxis de enlace de datos bidireccional es la combinación de enlace de propiedad y enlace de evento junto con la directiva incorporada ngModel. Necesitamos rodear la directiva ngModel con paréntesis y corchetes [(ngModel)]: [(ngModel)] = "component-property"
Cree otro ejemplo de two-way-binding del ejemplo anterior. Cambie la propiedad de nombre a two-way-binding en el archivo package.json.
Necesitamos hacer algunas cosas más para que el enlace de datos bidireccional funcione, y requerimos el paquete @angular/forms; la directiva ngModel está disponible en él. Ejecute el siguiente comando en la raíz del proyecto para instalar el paquete @angular/forms: $ npm install @angular/forms --save
El comando anterior descargará el paquete @angular/forms y también agregará la entrada en la sección de dependencias en el archivo package.json. A continuación, agregue la siguiente línea al objeto map systemjs.config.js, para que SystemJS cargue el módulo de forms: '@angular/forms': ng:forms/bundles/forms.umd.js'
Incluya FormsModule en los arrays de importación, nuestro AppModule depende del módulo forms : import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component';
[ 41 ]
Basics of Components @NgModule({ imports: [BrowserModule, FormsModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {}
Ahora tenemos todo listo para usar ngModel, vamos a ejecutar el comando npm install seguido del comando npm start command. Los códigos para src/app.component.ts son los siguientes: import { Component } from '@angular/core'; @Component({ selector: 'two-way-binding-app', template: `
{{message}}
` }) export class AppComponent { public message: string = 'Angular - Two Way Binding'; }
En el fragmento de código anterior, la propiedad message se asigna a [(ngModel)], si cambiamos el texto en el cuadro de texto, la propiedad message se actualizará automáticamente sin líneas adicionales de código y viceversa. Esto también actualiza el texto dentro de la etiqueta
.
Directivas incorporadas El framework AngularJS 1 tiene muchas directivas incorporadas. Angular viene con muy pocas directivas, las directivas restantes en AngularJS 1 son reemplazadas por nuevos conceptos de Angular que discutimos en secciones previas. En esta sección, discutiremos las directivas incorporadas disponibles en Angular.
Directivas estructurales Las directivas estructurales nos permiten cambiar la estructura DOM en una vista mediante la adición o eliminación de elementos. En esta sección, exploraremos las directivas estructurales incorporadas, ngIf, ngFor, y ngSwitch.
[ 42 ]
Basics of Components
ngIf La directiva ngIf se usa para agregar o eliminar elementos DOM dinámicamente: content
Si la condición es verdadera, Angular agregará contenido a DOM, si la condición es falsa, eliminará físicamente ese contenido de DOM:
Structural Directives
They lets us modify DOM structure
En el fragmento de código anterior, cuando el valor de isReady es verdadero, el contenido dentro de la etiqueta
se representará en la página, siempre que sea falso, ambas etiquetas dentro de la etiqueta
se eliminarán por completo de DOM. El símbolo de asterisco (*) antes de ngIf es obligatorio.
ngFor El ngFor es una directiva de repetidor, se usa para mostrar una lista de elementos. Usamos ngFor principalmente con matrices en JavaScript, pero funcionará con cualquier objeto iterable en JavaScript. La directiva ngFor es similar a la instrucción for...in en JavaScript. Aquí hay un ejemplo rápido: public frameworks: string[]
= ['Angular', 'React', 'Ember'];
Framework es un array de nombres frontend framework. Asi es cómo podemos mostrarlos usando ngFor:
{{framework}}
El fragmento de código anterior mostrará la lista de nombres de framework en una lista desordenada.
La comprensión de la sintaxis ngFor
{{framework}}
[ 43 ]
Basics of Components
El código anterior usa ngFor para mostrar la lista de nombres de framework. Permítanos entender cada parte de la sintaxis ngFor: *ngFor="let framework of frameworks"
Hay múltiples segmentos en la sintaxis ngFor, que son *ngFor, let framework, and frameworks. Ahora los veremos en detalle: Esta es un array y fuente de datos para la directiva ngFor sobre la que se repetirá. frameworks:
let framework: let es una palabra clave utilizada para declarar la variable de entrada de template. representa un solo elemento en la lista durante la iteración.
Podemos usar una variable framework dentro de una ngFor template para referirnos al elemento de iteración actual. *ngFor: ngFor representa la directiva en sí misma, el símbolo de asterisco (*) antes de ngFor es obligatorio.
ngSwitch La directiva ngSwitch se comparará de forma similar a una sentencia switch case en JavaScript. ngSwitch tendrá múltiples plantillas, dependiendo del valor pasado, se renderizará una template. plantilla. Esta directiva es similar a la declaración switch() en JavaScript:
I am Bugatti I am Mustang I am Ferrari I am somebody else
Estamos utilizando la sintaxis de enlace de propiedad con[ngSwitch]. IEn el fragmento de código anterior, cuando el valor de la propiedad selectedCar coincide con el valor [ngSwitchCase] Angular representará físicamente el contenido de esa plantilla, las plantillas restantes no estarán en la pantalla. Si ninguno de los valores de [ngSwitchCase] coincide, Angular representará la plantilla ngSwitchDefault.
[ 44 ]
Basics of Components
No estamos utilizando el símbolo de asterisco (*) para NgSwitch porque estamos usando directamente la etiqueta template HTML 5. ngIf y ngFor también utilizan internamente una etiqueta template HTML 5 para representar contenido, en lugar de escribir explícitamente la etiqueta template cada vez. A diferencia de ngSwitch, usamos el símbolo asterisco (*) como atajo o azúcar sintáctico. Angular reemplazará internamente el símbolo asterisco (*) con la etiqueta template HTML 5..
Directivas de atributo Las directivas de atributo nos permiten cambiar la apariencia o el comportamiento de un elemento. En esta sección, exploraremos las directivas de atributo incorporadas, ngStyle, y ngClass.
ngStyle La directiva ngStyle se usa cuando necesitamos aplicar estilos en línea múltiples dinámicamente a un elemento. En template:
{{framework}}
En la clase Component: getInlineStyles(framework) { let styles = { 'color': framework.length > 3 ? 'red' : 'green', 'text-decoration': framework.length > 3 ? 'underline' : 'none' }; return styles; }
En lugar de escribir sentencias largas en [ngStyle] en la plantilla, estamos llamando a un método dentro de la clase Component, que devuelve múltiples estilos en línea. Si necesitamos aplicar un único estilo en línea dinámicamente, podemos usar el enlace de estilo usando la siguiente sintaxis, [style.style-property]en lugar de la directiva ngStyle:
3 ? 'red': 'green'" > {{framework}}
[ 45 ]
Basics of Components let es
una nueva palabra clave de ES2015, que nos permitirá declarar una variable local de alcance a nivel del bloque. Antes de ES2015 no hay alcance de nivel de bloque en JavaScript.
ngClass La directiva ngClass se usa cuando necesitamos aplicar múltiples clases dinámicamente. El código para Styles es el siguiente: .red { color: red; text-decoration: underline; } .bolder { font-weight: bold; }
En la clase Component: geClasses(framework) { let classes = { red: framework.length > 3, bolder: framework.length > 4 }; return classes; }
En template:
{{framework}}
Cualquiera que sean las clases, se aplicarán a la plantilla. Si necesitamos aplicar una única clase dinámicamente, podemos usar el enlace de clase usando la siguiente sintaxis; [class.class-name]en lugar de la directiva ngClass:
{{framework}}
[ 46 ]
Basics of Components
Construyendo el componente master-detail Hemos aprendido muchas cosas nuevas de Angular, como el enlace de datos, el enlace de eventos, las directivas estructurales y las directivas de atributos en este capítulo. Pongamos en práctica todo construyendo una aplicación de página maestra de detalles. Comencemos por crear otro ejemplo de la sección anterior y darle el nombre de masterdetails. Cambie la propiedad del nombre a master-details en el archivo package.json. No necesitamos el paquete de formularios en este ejemplo. Finalmente, ejecute el comando npm install
seguido del comando npm start.
El código para index.html es el siguiente: Books List <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="node_modules/core-js/client/shim.js"> <script src="node_modules/zone.js/dist/zone.js"> <script src="node_modules/systemjs/dist/system.src.js"> <script src="systemjs.config.js"> <script> System.import('src').catch(function (err) { console.error(err); }); Loading...
[ 47 ]
Basics of Components
Agregamos tres elementos adicionales aindex.html: Bootstrap: frameworks CSS de CDN (podemos usar cualquier CSS) Roboto font: Podemos usar cualquier fuente que nos guste Custom style sheet: Donde escribimos nuestros estilos de aplicación Código para styles.css El código para stylesheet (styles.css) es muy largo. El lector puede agregarlo desde el código fuente provisto.
El código para src/app.module.ts es el siguiente: import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule { }
El código para src/app.component.tses el siguiente: import { Component } from '@angular/core'; @Component({ selector: 'books-list', templateUrl: ./app.component.html' }) export class AppComponent { }
Hay una cosa nueva en nuestro AppComponent, es decir, templateUrl. En lugar de usar una plantilla en línea, estamos usando la plantilla almacenada en un archivo HTML. El código para src/app.component.htmles el siguiente:
Books List
Left
[ 48 ]
Basics of Components
Right
Creamos una estructura básica de diseño usando bootstrap en el archivo HTML precedente. Vamos a escribir algunos códigos en este ejemplo. Vamos a mostrar información básica del libro en el lado izquierdo de la página. El usuario podrá hacer clic en cualquiera de los elementos del libro y obtener información sobre ese libro, y la información se mostrará en el lado derecho de la página. El siguiente paso es agregar la clase Libro a nuestro ejemplo para definir su estructura. El código para src/book.ts es el siguiente: export class Book { isbn: number; title: string; authors: string; published: string; description: string; coverImage: string; }
Además, permítanos agregar algunos datos de muestra. Normalmente, estos datos provienen de servicios REST en aplicaciones del mundo real, aprenderemos cómo consumirlos y usarlos en futuros capítulos. Pero para este ejemplo, vamos a usar datos ficticios dentro de una clase. El código para src/mock-books.ts es el siguiente: import { Book } from './book'; export const BOOKS: Book[] = [ { isbn: 9781786462084, title: 'Laravel 5.x Cookbook', authors: 'Alfred Nutile', published: 'September 2016', description: 'A recipe-based book to help you efficiently create amazing PHP-based applications with Laravel 5.x', coverImage: 'https://d255esdrn735hr.cloudfront.net/sites/ default/files/imagecache/ppv4_main_book_cover/ B05517_MockupCover_Cookbook_0.jpg' }, { isbn: 9781784396527, title: 'Sitecore Cookbook for Developers', authors: 'Yogesh Patel', published: 'April 2016',
[ 49 ]
Basics of Components description: 'Over 70 incredibly effective and practical recipes to get you up and running with Sitecore development', coverImage: 'https://d255esdrn735hr.cloudfront.net/sites/' default/files/imagecache/ppv4_main_book_cover/6527cov_.jpg' }, { isbn: 9781783286935, title: 'Sass and Compass Designers Cookbook', authors: 'Bass Jobsen', published: 'April 2016', description: 'Over 120 practical and easy-to-understand recipes that explain how to use Sass and Compass to write efficient, maintainable, and reusable CSS code for your web development projects', coverImage: 'https://d1ldz4te4covpm.cloudfront.net/sites/ default/files/imagecache/ppv4_main_book_cover/I6935.jpg' } ];
El código para src/app.component.tses el siguiente: import { Component } from '@angular/core'; import { Book } from './book'; import { BOOKS } from './mock-books'; n @Component({ selector: 'books-list', templateUrl: 'src/app.component.html' }) export class Book[] AppComponent { booksList: = BOOKS; }
Estamos almacenando todos los datos de mock-books dentro de la propiedad books-list de la clase AppComponent. Como sabemos, se accede a cualquier propiedad pública de la clase Component dentro de la plantilla, por lo que usaremos *ngFor Para mostrar esta lista dentro de la plantilla (app.component.html):
{{book.title}}
{{book.authors}}
[ 50 ]
Basics of Components
El fragmento de código anterior debe agregarse en el lugar del texto que queda a la izquierda. Aquí estamos usando *ngFor para iterar sobre booksList. Usando la sintaxis de interpolación de libros de la variable de entrada de template , estamos visualizando la imagen, el título del libro y los autores del libro. Así es como se ve nuestro ejemplo en un navegador en esta etapa:
Ahora, siempre que hagamos clic en cualquiera de los elementos de la lista, en el lado derecho se mostrarán todos los detalles del libro. Necesitamos agregar un evento click a cada elemento de la lista, asociarlo con un método en la clase Component, y establecer los detalles de selectedBook en la propiedad usando ese método para que podamos usar la propiedad de selectedBook
para mostrar todos los detalles del libro en el el lado derecho de la plantilla. Ahora agregue el evento click a cada elemento de la lista:
Agregue el método y la propiedad getBookDetails()para almacenar el libro seleccionado. Para el método getBookDetails , estamos aprobando el ISBN de un libro, con el cual podemos filtrar los detalles del libro de booksList: export class AppComponent { booksList: Book[] = BOOKS; selectedBook: Book; getBookDetails (isbn: number) { var selectedBook = this.booksList .filter(book => book.isbn === isbn); this.selectedBook = selectedBook[0];
[ 51 ]
Basics of Components } }
Estamos almacenando la información de nuestro libro seleccionado en la propiedad selectedBook de la clase Component, y podemos usar la misma propiedad en la plantilla para mostrar los detalles:
{{selectedBook.title}}
{{selectedBook.authors}}
{{selectedBook.published}}
{{selectedBook.description}}
Por ahora, nuestra aplicación está casi completa. Si vamos y hacemos clic en cualquiera de los elementos de la lista, en el lado derecho se mostrarán todos los detalles del libro:
Todo esta bien, excepto que hay un problema: cada vez que se carga la aplicación, no tenemos un libro seleccionado. Si vamos a herramientas de desarrollador en la pestaña Console en el navegador, veremos un montón de errores; esto está sucediendo porque estamos tratando de mostrar la información del libro seleccionado cuyo valor no está definido. Deberíamos presentar la plantilla solo cuando haya un valor en la propiedad selectedBook que simplemente podemos verificar agregando una directiva *ngIf para verificar el valor de la propiedad de selectedBook: ,
[ 52 ]
Basics of Components
Ahora, no vemos ningún error porque la directiva *ngIf verifica el valor de la propiedad selectedBook. Si no se inicializa con los detalles del libro, ni siquiera representará el contenido dentro de él. El código de la plantilla no intentará acceder al valor de la propiedad selectedBook porque el código ni siquiera existirá. Con esto, terminaremos este capítulo aquí.
Resumen Comenzamos este capítulo al cubrir una introducción a los componentes. A continuación, discutimos cómo escribir componentes utilizando nuevas funciones en Angular, como el enlace de datos (unidireccional, bidireccional), el enlace de eventos y nuevas sintaxis de plantillas utilizando muchos ejemplos. Luego discutimos diferentes tipos de directivas integradas en Angular. Finalmente, completamos este capítulo al crear una aplicación de detalles maestros usando todas las características que aprendimos a lo largo de este capítulo. Al final de este capítulo, el lector debe comprender bien los nuevos conceptos de Angular y debe poder escribir componentes. En el siguiente capítulo, discutiremos los componentes, los servicios y la inyección de dependencias con más profundidad.
[ 53 ]
Componentes, Servicios e Inyección de Dependencia En este capítulo, aprenderemos cómo implementar algunos escenarios de aplicaciones del mundo real. Vamos a ver cómo implementar múltiples componentes en la página de detalles maestros, cómo los componentes se comunican entre sí, cómo compartir los datos entre estos componentes usando servicios y cómo funciona la inyección de dependencia. Después de pasar por este capítulo, el lector comprenderá los siguientes conceptos: Crear y usar múltiples componentes Comunicación entre los componentes Usar servicios para compartir los datos Cómo funciona la inyección de dependencia
Introducción En el capítulo anterior, aprendimos cómo crear una aplicación de lista de libros, que es una página de detalles maestros. Contiene solo un componente y se vuelve complejo; estos componentes necesitan comunicarse entre ellos. Comencemos por crear otra aplicación del último ejemplo en el capítulo anterior, asígnele el nombre multiple-components, atambién cambie la propiedad del nombre por multiple-components en el archivo package.json.
Components, Services, and Dependency Injection
Ahora ejecute el comando npm install esto descargará todos los paquetes requeridos. Una vez que haya terminado, ejecute el comando npm start. Veremos el siguiente resultado en el navegador:
Nuestra aplicación tiene un único componente llamado AppComponent, y su código de plantilla se encuentra en el archivo app.component.html. Un único componente está haciendo demasiadas cosas en nuestra aplicación. En el futuro, podríamos necesitar mostrar información del libro en otros componentes. Si tenemos que volver a escribir el mismo código duplicando, violaremos el principio DRY (Don't Repeat Yourself) El componente debe ser un bloque de construcción UI atómico, reutilizable, y debe ser lo más pequeño posible y reutilizable.
Trabajando con múltiples componentes
Las aplicaciones del mundo real serán complejas y tendrán múltiples componentes. Vamos a reescribir nuestra aplicación para usar múltiples componentes y entender cómo estos componentes se comunican entre sí. Si miramos nuestro código de plantilla en el archivo app.component.html, tenemos algunos fragmentos de código en la parte inferior, dentro de las etiquetas , que muestra la información del libro seleccionado. Necesitamos refactorizar este código en un componente separado (BookDetailsComponent) para que sea reutilizable en varios lugares según sea necesario. Vamos a crear una carpeta, book-details debajo de la carpeta src, luego cree un archivo, book-details.component.tsdebajo de la carpeta book-details. WVamos a escribir todo el código dentro de las etiquetas en la plantilla BookDetailsComponent.
[ 55 ]
Components, Services, and Dependency Injection
El código para src/book-details/book-details.component.tses: import { Component } from '@angular/core'; import { Book } from '../book'; @Component({ selector: 'book-details', templateUrl: './book-details.component.html' }) export class BookDetailsComponent { book: Book; }
El código para src/book-details/book-details.component.htmles el siguiente:
{{book.title}}
{{book.authors}}
{{book.published}}
{{book.description}}
Tenemos nuestro nuevo componente book-details. Hay un par de cosas que notar; estamos utilizando book como nombre de propiedad en la clase Component en lugar de selectedBook, la plantilla también usa el mismo nombre de propiedad. También agregamos un botón Delete al código de la plantilla, que no hace nada por el momento. Nuestro componente refactorizado puede mostrar la información del libro dada. Para hacer eso podemos usar el selector book-details declarativamente en cualquier lugar:
[ 56 ]
Components, Services, and Dependency Injection
Para usar este BookDetailsComponent, primero debemos agregarlo a la matriz de declaraciones en nuestro módulo de aplicación: import { BookDetailsComponent } from './book-details/book-details.component';
Es hora de usar BookDetailsComponent dentro de la plantilla AppComponent. Aquí está el código app.component.html usando BookDetailsComponentdeclarativamente, usando selector, dentro de etiquetas . El código para src/app.component.htmles el siguiente:
Se supone que la etiqueta muestra toda la información del libro. Para hacer eso, la clase Component necesita un objeto book . Sin embargo, la información del libro está en la propiedad selectedBook de la clase AppComponent. Necesitamos vincular la propiedad selectedBook de la clase AppComponent a la propiedad book de la clase BookDetailsComponent utilizando el enlace de propiedad en la etiqueta .
Propiedades Input Las propiedades de la clase Component no son accesibles directamente para el enlace de propiedad en selector; necesitamos declararlos como propiedades de entrada para que el enlace de propiedad funcione. Podemos declarar una propiedad como una propiedad de entrada usando el decorador @Input() o agregando la propiedad a la propiedad inputs: [] array en el decorador @Component(); podemos usar cualquiera de estas formas.
Declaremos nuestra propiedad book de la clase BookDetailsComponentcomo una propiedad de entrada usando el decorador @Input(). Está disponible en el paquete @angular/core: @Input() book: Book;
[ 57 ]
Components, Services, and Dependency Injection
El código para src/book-details/book-details.component.tses el siguiente: import { Component, Input } from '@angular/core'; import { Book } from '../book'; @Component({ selector: 'book-details', templateUrl: './book-details.component.html', }) export class BookDetailsComponent { @Input() book: Book; }
Ahora la propiedad book de la clase BookDetailsComponentestá disponible para el enlace de propiedades. El código para src/app.component.htmles el siguiente:
Después de agregar el fragmento de código anterior, si volvemos a un navegador, podemos ver el resultado completo. Haga clic en la lista de libros en el lado izquierdo y obtenemos la información completa del libro en el lado derecho. Funciona como se esperaba. Ahora, hemos creado con éxito dos componentes, y estamos pasando datos del componente principal al componente hijo. Así es como los componentes se comunican entre sí. El componente primario interactúa con el componente secundario vinculando la propiedad selectedBook en el componente principal a la propiedad book en un componente secundario. Creamos un BookDetailsComponentreutilizable, que es un componente de presentación que se puede usar en cualquier lugar, al pasar la información del libro a la propiedad book utilizando el enlace de propiedad. En esta sección, aprendimos cómo el componente principal se comunica con un componente secundario. En la siguiente sección, aprenderemos cómo un componente secundario puede comunicarse nuevamente con el componente principal.
Propiedades Aliasing input Si no queremos utilizar el nombre de propiedad srcinal para el enlace de propiedad de entrada, podemos usar aliasing. El decorador @Input () acepta un nombre de alias opcional para la propiedad: @Input('myBook') book: Book;
[ 58 ]
Components, Services, and Dependency Injection
Hemos Aliased el nombre de la propiedad de entrada book, con el nombre myBook; ahora podemos usar myBook como un nombre de propiedad en el selector en lugar del nombre de la propiedad book:
Propiedades Output Agreguemos el siguiente método deleteBook() a la clase AppComponent. Este método borra la información del libro de booksList con un número de ISBN dado: deleteBook (isbn: number) { this.selectedBook = null; this.booksList = this.booksList .filter(book=>book.isbn !== isbn); }
Si revisamos nuestro BookDetailsComponent, tenemos un botón Eliminar en la plantilla. Haga clic en el botón Delete y no ocurre nada porque no hemos conectado ningún evento a ese botón. Agregue un método deleteBook() vacío sin ninguna implementación a la clase BookDetailsComponent: export class BookDetailsComponent { @Input() book: Book; deleteBook () { } }
Agregue un evento (click) al botón Delete en la plantilla y conecte el método deleteBook():
Ahora, si hacemos clic en el botón Eliminar todavía invocamos el método deleteBook() no hace nada porque no necesitamos implementar la funcionalidad de eliminación. De hecho, BookDetailsComponentno sabe cómo eliminar un libro; no tiene ningún conocimiento de booksList desde donde se supone que debe eliminar la información del libro. Es solo un componente de presentación.
[ 59 ]
Components, Services, and Dependency Injection
La fuente de datos booksList y la lógica para eliminar la información del libro de la fuente de datos están en nuestro elemento principal, AppComponent. Cada vez que hacemos clic en el botón Delete en BookDetailsComponent (componente secundario), tenemos que comunicarnos con AppComponent (componente principal) para invocar el método deleteBook() method en él. Necesitamos definir un evento en BookDetailsComponent (componente hijo) que puede desencadenar el método deleteBook()en la clase AppComponent (componente principal). En BookDetailsComponent (componente hijo), cree un evento personalizado llamado onDelete. La propiedad onDelete es una propiedad de salida declarada utilizando el decorador @Output() para que el enlace de evento funcione en selector. Se pueden crear eventos personalizados utilizando la clase EventEmitter. El decorador @Output() y la clase EventEmitter están disponibles en el paquete @angular/core. El código para src/book-details/book-details.component.tses el siguiente: import { Component, Input, Output, EventEmitter } from '@angular/core'; import { Book } from '../book'; @Component({ selector: 'book-details', templateUrl: './book-details.component.html' }) export class BookDetailsComponent { @Input() book: Book; @Output() onDelete = new EventEmitter(); deleteBook() { } }
Cada vez que se hace clic en el botón Eliminar, necesitamos activar un evento onDelete, la clase EventEmitter proporciona un método de emisión para activar los eventos. Estamos invocando el método deleteBook() cuando hacemos clic en el botón Eliminar. Vamos a activar un evento onDelete en el método deleteBook() usando el método emit(): deleteBook () { this.onDelete.emit(this.book.isbn); }
[ 60 ]
Components, Services, and Dependency Injection
Para emit(), estamos pasando el número de ISBN del libro actual. Ahora podemos usar el evento onDelete() para activar el método de la clase principal AppComponent deleteBook() y pasar el número ISBN. Agregue un evento onDelete en el selector en el archivo de plantilla app.component.html: ,
Ahora el botón Eliminar funciona. Comprendamos lo que hicimos paso a paso: Un evento onDelete se declara como propiedad de salida utilizando el decorador @Output() La propiedad onDelete se inicializa como una instancia de la clase EventEmitter Los objetos EventEmitter se usan para crear y activar eventos personalizados La propiedad onDelete es un evento, por lo que estamos usando la sintaxis de enlace de eventos para enlazar al método deleteBook() en el componente padre En el método deleteBook() del componente secundario, estamos utilizando el objeto EventEmitter y el método emit() en la propiedad onDelete, estamos pasando el número de ISBN del libro actual como parámetro Cuando se desencadena un evento onDelete del niño, invocará el método deleteBook() en AppComponent stamos pasando el número de ISBN utilizando el objeto $event al método deleteBook() en AppComponent Al crear el objeto EventEmitter, estamos usando un tipo de número porque estamos pasando un número a través de nuestro evento, podemos usar cualquier tipo en el componente.
Propiedades Aliasing output Si no queremos utilizar el nombre del evento srcinal para el enlace del evento, podemos usar el aliasing. El decorador @Ouput() acepta un nombre de alias opcional para la propiedad: @Output('deleteMyBook') onDelete = new EventEmitter();
[ 61 ]
Components, Services, and Dependency Injection
Vamos a Aliased el nombre de propiedad de salida onDelete con el nombre, deleteMyBook; ahora podemos usar deleteMyBook como un nombre de evento en el selector en lugar de onDelete:
Ahora, hemos implementado con éxito la comunicación entre el componente principal y el componente hijo en ambas direcciones:
[ 62 ]
Components, Services, and Dependency Injection
Las flechas en el diagrama representan la dirección del flujo de datos.
Compartir datos usando servicios En nuestro ejemplo anterior, tenemos nuestros datos de muestra de libros en el archivo mock-books.ts, estamos accediendo a los datos en él directamente en AppComponent. En aplicaciones del mundo real, accederemos a los datos de fuentes de datos externas a través de servicios rest. Necesitamos acceder a los mismos datos y sus operaciones en múltiples componentes. Necesitamos un único punto de acceso a datos reutilizable, y esto se puede implementar como un servicio en Angular. Un servicio en Angular escrito usando TypeScript es simplemente una clase, que actúa como un punto de acceso a datos reutilizable. Reorganicemos nuestra lógica de acceso a datos en un servicio para obtener los datos, filtrar los datos y eliminarlos; estábamos haciendo todas estas operaciones anteriormente en el componente. Una vez que los movemos a un servicio, se puede acceder a ellos desde cualquier lugar dentro de los componentes. Primero, comience con la creación de otro ejemplo del último ejemplo, nómbrelo services, cambie la propiedad de nombre a services en el archivo package.json para reflejar el nombre de ejemplo apropiado. El código para src/book-store.service.tses el siguiente: import { Injectable } from '@angular/core'; import { Book } from './book'; import { BOOKS } from './mock-books'; @Injectable() export class BookStoreService { booksList: Book[] = BOOKS; getBooks () { return this.booksList; } getBook (isbn: number) { var selectedBook = this.booksList .filter(book => book.isbn === isbn); return selectedBook[0]; } deleteBook (isbn: number) { this.booksList = this.booksList .filter(book => book.isbn !== isbn);
[ 63 ]
Components, Services, and Dependency Injection return this.booksList; } }
BookStoreService contiene lógica para recuperar la lista de libros, filtrar un solo libro y
eliminar un libro. No hay nada especial en esta clase, simplemente es una clase de TypeScript con métodos que operan en la propiedad booksList, que es nuestra fuente de datos. En aplicaciones del mundo real, estos métodos pueden comunicarse con servicios rest externos utilizando mecanismos como XHR y JSONP. La lógica subyacente se puede cambiar encuando cualquier momento sin que consumen un servicio, siempre y no cambiemos lasafectar firmaslos decomponentes método. Hay una cosa notable, el decorador @Injectable(). El decorador @Injectable() es utilizado por TypeScript para emitir metadatos sobre nuestro servicio, metadatos que Angular necesita para inyectar otras dependencias en este servicio. Nuestro BookStoreService no tiene ninguna dependencia en este momento, pero estamos agregando el decorador, ya que es una buena práctica para mantener la coherencia en nuestro código y podría ser útil en el futuro. Ahora tenemos que refactorizar nuestro AppComponent para usar los métodos de BookStoreService. Primero, necesitamos crear un objeto BookStoreService. A diferencia de cualquier otra clase, podemos crear un objeto para BookStoreService usando un nuevo operador y su constructor dentro de la clase AppComponent: var bookStoreService = new BookStoreService();
El fragmento de código anterior crea el objeto BookStoreService, pero también crea dependencia, o acoplamiento ajustado, entre AppComponent y BookStoreService. Si la definición del constructor bookStoreService cambia, debemos actualizar AppComponent y todos los demás componentes donde creamos un objeto para este servicio. bookStoreService puede depender de otros servicios; también necesitamos administrar esas dependencias. Cambiamos las definiciones en las aplicaciones del mundo real, gestionar todas estas dependencias entre los servicios, las directivas y los componentes es difícil, y nuestro código se vuelve rápidamente inmanejable y las pruebas unitarias se vuelven difíciles. Aquí es donde la inyección de dependencia entra en juego. En lugar de crear objetos para dependencias, se pueden pasar al constructor de objetos dependiente: export class AppComponent { constructor (private bookStoreService: BookStoreService) { } }
[ 64 ]
Components, Services, and Dependency Injection
Alguien necesita crear el objeto para BookStoreService y pasarle un constructor a AppComponent. Angular viene con su propio sistema de inyección de dependencia. En lugar de crear el objeto para BookStoreService utilizando un nuevo operador, le indicaremos a Angular que cree una instancia de servicio y lo inyecte en el componente, este es un proceso de dos pasos: Pase el objeto de servicio al componente constructor como parámetro Especifique el servicio en el cual necesita un objeto en la matriz de proveedores del decorador @Component({ providers: [] })en el componente: import {BookStoreService} from './contacts/contacts.service'; @Component({ providers: [BookStoreService] }) export class AppComponent { constructor(private bookStoreService:BookStoreService) { } }
En el fragmento de código anterior, cuando Angular mira el parámetro constructor, irá al array providers en el decorador para un proveedor coincidente, y crea una instancia del servicio mencionado utilizando el proveedor. Aprenderemos en detalle acerca de por qué sucede esto y por qué necesitamos especificar el servicio en el arrays providers. El fragmento de código anterior hace un poco de magia a causa de TypeScript. Para cualquier parámetro público o privado mencionado en el constructor, TypeScript crea automáticamente una propiedad en la clase y la asigna con el valor del parámetro constructor dentro del constructor. TypeScript interpreta el código anterior de esta manera: class AppComponent { bookStoreService: BookStoreService; constructor(private bookStoreService: BookStoreService) { this.bookStoreService = bookStoreService; } }
La siguiente es la implementación completamente reescrita de AppComponent utilizando BookStoreService. El código para src/app.component.tses el siguiente: import { Component, OnInit } from '@angular/core'; import { Book } from './book';
Aquí hay algunos puntos importantes acerca de los servicios: Los servicios declarados dentro del array providers de un componente principal están disponibles para componentes secundarios listos para usar Los componentes secundarios no necesitan declarar los servicios nuevamente en su array providers Si un componente secundario declara nuevamente un servicio en su array providers, que ya está declarado en su array proveedores de componentes principales, Angular creará una nueva instancia de servicio para el componente secundario y no usará el servicio del componente principal Una instancia de servicio creada en un componente secundario sólo se puede acceder a sí misma y a sus componentes secundarios
[ 66 ]
Components, Services, and Dependency Injection
En lugar de declarar servicios en el array providers de un componente, también podemos declararlos en el array proveedores del módulo usando un decorador @NgModule({ providers: [] }) Los servicios declarados a nivel de módulo están disponibles en todo el módulo y sus submódulos Una cosa importante en la clase AppComponent es que en lugar de llamar al método getBooksList() directamente en el constructor, lo estamos llamando un método especial llamado ngOnInit(). Como se mencionó anteriormente, el método ngOnInit() es un método de enlace de
ciclo de vida de los componentes invocados justo después de que se crea el componente. El trabajo de constructor es solo para construir e inicializar el objeto, no para recuperar los datos, esto es solucionado por el método ngOnInit().
Inyección de dependencia En la sección anterior, aprendimos qué es la inyección de dependencia y su necesidad. Nos saltamos algunas cosas como: ¿Cómo se crea e inyecta un objeto de servicio en el constructor? Por qué tenemos que especificar los objetos de servicio en el array providers? Diferentes mecanismos para crear un proveedor.
Usando un proveedor de clase Como se mencionó en la sección anterior, obtener la instancia de un objeto de servicio es un proceso de dos pasos. En el paso uno, pasamos el objeto de servicio como un parámetro al constructor: export class AppComponent { constructor (private bookStoreService: BookStoreService) { } }
En este momento, Angular no sabe cómo crear una instancia de BookStoreService, el proceso de creación de la instancia se especifica en el array providers en el decorador de la clase Component. El siguiente fragmento de código es para nuestro servicio del ejemplo anterior: @Component({ providers: [BookStoreService] }) export class AppComponent {
En el fragmento de código anterior, en el array providers de decorador, simplemente proporcionamos el mismo nombre de servicio, providers: [BookStoreService], que se pasa como un parámetro de constructor. ¿Cómo crea el array providers un objeto para BookStoreService? providers: [BookStoreService]
Bueno, el código anterior en la matriz providers es la sintaxis abreviada. Así es como Angular lo interpreta: [{ provide: BookStoreService, useClass: BookStoreService }]
El fragmento de código anterior es una versión expandida de un array providers; es un objeto literal con dos propiedades: La primera propiedad, provide es un token que sirve como clave para registrar un valor de dependencia con un objeto inyector y localizar el proveedor del objeto inyector. La segunda propiedad, useClass es una estrategia utilizada para crear el objeto de definición de proveedor real, que es el valor de dependencia Hay muchas formas de crear valores de dependencia; useClass es uno de ellos En nuestro caso, tanto la clave (token) como el valor (objeto de definición del proveedor) son las mismas, la sintaxis abreviada solo puede usarse en este escenario.
Usar un proveedor de clase con dependencias La mayoría de las veces, nuestro servicio depende de otros servicios e inyectamos esos servicios en nuestro constructor de servicios. Sin embargo, debemos informarle a Angular cómo crear instancias de nuestras dependencias. Aquí tenemos un servicio llamado ConsoleLoggerService que registra datos en la consola: @Injectable() export class ConsoleLoggerService { log (message: string) { console.log(message); } }
[ 68 ]
Components, Services, and Dependency Injection
Nuestro BookStoreService está utilizando un servicio ConsoleLoggerService spara registrar los datos, y se inyecta en el constructor BookStoreService: @Injectable() export class BookStoreService { constructor (private loggerService: ConsoleLoggerService) {} getBook (isbn: number) { this.loggerService.log('fetching book information'); } }
Usamos nuestro BookStoreService en el componente y lo mencionamos en el array providers, pero ahora BookStoreService depende de ConsoleLoggerServiceque es una clase. También podemos simplemente especificarlo en el arrayproviders, y funciona. En las siguientes secciones, aprenderemos a tratar con dependencias que no son de clase, como interfaces y cadenas.
Usar proveedores de clases alternativas El BookStoreService ractual recupera todos los datos de una fuente de datos ficticia. En el futuro, podemos decidir utilizar GraphQL o una implementación diferente para nuestra fuente de datos. Por ejemplo, supongamos que implementamos un nuevo servicio llamado BookStoreGraphQLService y este servicio también proporciona la misma API que BookStoreService, simplemente podemos intercambiar nuestro proveedor de BookStoreService con BookStoreGraphQLService: providers: [{ provide: BookStoreService, useClass: BookStoreGraphQLService }]
Ahora, para todos los componentes donde se inyecta la clave BookStoreService usarán la instancia BookStoreGraphQLService.
[ 69 ]
Components, Services, and Dependency Injection
Usar proveedores de clase aliased Aquí tenemos un escenario diferente. En el futuro, implementaremos un nuevo BookStoreGraphQLService. Decidimos que todos los componentes nuevos usarían esta implementación y los componentes antiguos tambien usaran la implementación BookStoreService existente. Podemos registrar el nuevo servicio en la matriz de proveedores y usarlo como de costumbre: providers: [BookStoreGraphQLService, BookStoreService]
Aunque tenemos de servicio diferentes que funcionan muy bien, algún servicio día podríamos decidirdos queaplicaciones todos los componentes antiguos también deberían usar el nuevo BookStoreGraphQLService. Una forma es ir a todos los componentes y servicios donde se usa la clave BookStoreService y reemplazarla con la clave BookStoreGraphQLService, que no es una buena opción. En lugar de modificar en todos esos lugares, podemos especificar que la clave BookStoreService use el objeto del proveedor BookStoreGraphQLServiceutilizando la estrategia useClass: providers: [ BookStoreGraphQLService, { provide: BookStoreService, useClass: BookStoreGraphQLService } ]
Los componentes antiguos también ahora usan el nuevo BookStoreGraphQLService. Sin embargo, hay un pequeño problema, si miramos cómo Angular interpreta el código anterior: providers: [ { provide: BookStoreGraphQLService, useClass: BookStoreGraphQLService }, { provide: BookStoreService, useClass: BookStoreGraphQLService } ]
[ 70 ]
Components, Services, and Dependency Injection
La estrategia Angular useClass siempre crea una nueva instancia de una clase de servicio provider determinada, por lo que aquí tendremos dos instancias de BookStoreGraphQLServiceen lugar de una, lo cual es innecesario. Podemos indicarle a Angular que use la instancia BookStoreGraphQLServiceexistente para diferentes tokens (proveer) usando la estrategia useExisting: providers: [ BookStoreGraphQLService, { provide: BookStoreService, useExisting: BookStoreGraphQLService ]
}
Existen dos tipos más de estrategias de creación de instancias de proveedores; proveedores de fábrica y de valor, para crear la instancia de un objeto de servicio de suministro, que veremos en capítulos futuros.
Resumen Comenzamos este capítulo discutiendo la aplicación de la lista de libros que construimos en los capítulos anteriores. Luego se discutió cómo dividir el componente individual en múltiples componentes y cómo los componentes se comunican entre sí utilizando las propiedades de entrada y salida. Luego se discutió cómo construir un punto de acceso a datos común para componentes que usan servicios para compartir los datos entre ellos. Finalmente, discutimos diferentes estrategias utilizadas para la creación de una instancia de objetos de servicio de proveedor. Al final de este capítulo, el lector debe tener una buena comprensión de cómo construir cualquier aplicación de interfaz de usuario usando múltiples componentes y cómo compartir los datos entre ellos. En el próximo capítulo, discutiremos cómo crear aplicaciones usando RxJS y observables.
[ 71 ]
Trabajando con Observables
En este capítulo, vamos a ver el paradigma de programación reactiva adoptado por Angular y nos centraremos en cómo fluyen los datos a través de una aplicación. Usamos Observables para implementar conceptos de programación reactiva. ES7 tiene una propuesta para incluir Observables en el lenguaje JavaScript. Hoy podemos usarlos con la biblioteca ReactiveExtensions for JavaScript (RxJS). Este capítulo cubrirá solo los conceptos esenciales de RxJS, y hay un buen número de recursos disponibles para el aprendizaje de RxJS mencionados al final. Después de pasar por este capítulo, vamos a entender los siguientes conceptos:
Programación reactiva Conceptos básicos de RxJS ¿Qué son observables y operadores? Escribir componentes y servicios usando Observables
Conceptos básicos de RxJS y Observables Antes de comenzar con los conceptos básicos de RxJS y Observables, primero debemos comprender qué es la programación reactiva y por qué es importante.
Programación reactiva En la programación imperativa tradicional, un estado variable se modificará cuando asignamos explícitamente un valor nuevo o actualizado. En este caso, la variable perderá su valor anterior; aquí los datos se propagan usando un mecanismo de extracción, cualquier parte de una aplicación que dependa de esta variable u objeto tiene que extraer el valor explícitamente cuando hay cambios, no se propagan automáticamente.
Working with Observables
Los programas reactivos funcionan de manera opuesta. En lugar de asignar explícitamente nuevos valores, se presionan implícitamente y los cambios se propagan automáticamente a todas las partes dependientes de la aplicación. Aprenderemos cómo escribir programas reactivos utilizando Observables en las próximas secciones. Para obtener más información sobre la programación reactiva, consulte los siguientes enlaces: https://en.wikipedia.org/wiki/Reactive_programming https://gist.github.com/staltz/868e7e9bc2a7b8c1f754
Aprendamos los conceptos básicos de RxJS, Observables y operadores.
Observer El Observer son devoluciones de colección que saben cómo escuchar los valores emitidos por un Observable: interface Observer { closed?: boolean; next: (value: T) => void; error: (err: any) => void; complete: () => void; }
El objeto Observer tiene tres métodos de devolución de llamada: next(), error(), y complete(). Estos métodos se explican en detalle, de la siguiente manera: Cada vez que un Observable emite el valor, se invoca la devolución de llamada next() Si no hay más valores emitidos por Observable, se invoca la devolución de llamada complete()
La devolución de llamada error() se invocará si se produce un error, entonces el observador dejará de escuchar los valores
[ 73 ]
Working with Observables
Observable El Observable es una colección de valores o eventos que llegan a través del tiempo; puede modelar eventos, solicitudes de servidor asíncronas o animaciones en la interfaz de usuario. La clase Observable tiene muchos métodos para crear colecciones Observable: Observable.create() Observable.of() Observable.from() Observable.fromArray() Observable.fromEvent() Observable.fromPromise() Observable.interval() Observable.timer()
El Observable es el elemento básico de RxJS; es importante para nosotros entender cómo usarlo en Angular y cómo Angular lo usa internamente. Usemos el método Observable.create() para crearlo manualmente. El siguiente ejemplo demuestra algunos de los conceptos clave de los Observable: El código para example01.html es el siguiente: <meta charset="UTF-8"> Manually creating an Observable <script type="text/babel"> const observable = Rx.Observable.create((observer) => { observer.next(1); observer.next(2); setTimeout(() => { observer.next(3); observer.next(4); observer.complete(); }, 1000); observer.next(5); });
[ 74 ]
Working with Observables console.log('Before subscribe'); observable.subscribe({ next: val => console.log(`Got value ${val}`), error: err => console.log(`Something went wrong ${err}`), complete: () => console.log('I am done') }); console.log('After subscribe'); <script src="https://unpkg.com/babel-standalone@6/babel.min.js"> <script src="https://unpkg.com/@reactivex/rxjs/dist/global/Rx.js">
Si abrimos el archivo example01.html en el navegador, podemos ver los siguientes mensajes de salida en la consola del navegador: Before subscribe Got value 1 Got value 2 Got value 5 After subscribe Got value 3 Got value 4 I am done
El método Observable.create() crea un nuevo Observable usando un Observer. El
El Observable emitirá valores solo cuando un Observer se suscriba utilizando el método de subscribe() podemos aclarar este comportamiento de visualización en los mensajes de salida. En el ejemplo anterior, vimos primero el mensaje Before subscribe aunque el objeto Observable ya está creado, emitirá los valores solo después de invocar el método subscribe()
Aquí hay un ejemplo de cómo trabajar con eventos DOM usando un Observable.
[ 75 ]
Working with Observables
El código para example02.html es el siguiente: const mouseMoves = Rx.Observable.fromEvent(document, 'mousemove'); mouseMoves .subscribe(event => console.log(event.clientX, event.clientY));
El ejemplo anterior registrará todo el movimiento del mouse en la consola del navegador.
Subscription El objeto subscription representa la ejecución de un Observable, y se utiliza para cancelar la ejecución. El código para example03.html es el siguiente: const interval = Rx.Observable.interval(1000); const subscription = interval.subscribe(val => console.log(val)); setTimeout(() => { subscription.unsubscribe(); }, 10000);
En el ejemplo anterior, el Observable emite un valor cada segundo y estamos iniciando sesión en la consola del navegador. El Observable deja de emitir los valores después de diez segundos, porque estamos cancelando la suscripción utilizando el método unsubscribe() en el objeto Subscription devuelto por el método subscribe().
Operators Un operador es una función pura que crea un nuevo Observable basado en el Observable actual, y nos permite realizar varios tipos de operaciones como filtrado, mapeo y retraso de valores. RxJS es muy rico en términos de operadores, y a lo largo del capítulo aprenderemos diferentes tipos de operadores.
[ 76 ]
Working with Observables
Aquí hay un ejemplo usando los operadores map() y filter(). El código para example04.html es el siguiente: const interval = Rx.Observable.interval(1000) .map(x => x * 2) .filter(x => x%2 === 0); interval.subscribe(val => console.log(val));
En el ejemplo anterior, usamos el operador map() para multiplicar los valores, luego usamos el operador filter() para filtrar valores pares.
Observables en Angular Angular utiliza Observables internamente en muchos conceptos, como formularios, HTTP y router. En este capítulo, solo veremos cómo usar Observables con eventos y cómo usar operadores.
Valores observables de stream y mapping Aquí hay un ejemplo de manejo de un clic de botón y entrada de cuadro de texto usando un Observable. El código para example05/src/app.component.tses el siguiente: import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/fromEvent'; import 'rxjs/add/operator/map'; @Component({ selector: 'app-root', template: `
Vamos a entender lo que está sucediendo en el Component anterior: Estamos accediendo a un botón y a un cuadro de texto que están en template usando @ViewChild en Component Estamos accediendo a los elementos DOM subyacentes utilizando la propiedad nativeElement Estamos creando un Observable, uno para el clic del botón y otro para el evento de cambio de texto Cuando se hace clic en el botón, estamos mostrando el mensaje 'Hello Angular, RxJS!' Cuando se cambia el texto del cuadro de texto, estamos mostrando el mismo texto en el mensaje
Fusionando Observables streams En el ejemplo anterior, tenemos dos bloques de suscripción redundantes que hacen lo mismo, podemos refactorizarlos usando el operador merge(). Podemos incluir el operador merge() usando import 'rxjs/add/operator/merge': ngOnInit() { const btnOb$ = Observable .fromEvent(this.btn.nativeElement, 'click') .map(event => 'Hello Angular, RxJS!'); const textOb$ = Observable .fromEvent(this.text.nativeElement, 'change') .map(event => event.target.value);
Observable
[ 78 ]
Working with Observables .merge(btnOb$, textOb$) .subscribe(res => this.message = res); }
Estamos utilizando el operador merge() para combinar ambos streams y subscribing al flujo de salida, y emitirá simultáneamente todos los valores de cada entrada dada Observable. En nuestro caso, o el usuario hace clic en el botón o ingresa texto en un cuadro de texto y vamos a mostrar el siguiente mensaje:
Usando el método
Observable.interval()
Vamos a construir un ejemplo más para mostrar un reloj y comprender algunos conceptos más. El código para example06/src/app.component.tses el siguiente: import import import import
{ Component, OnInit } from '@angular/core'; { Observable } from 'rxjs/Observable'; 'rxjs/add/observable/interval'; 'rxjs/add/operator/map';
@Component({ selector: 'app-root', template: `
[ 79 ]
Working with Observables
{{time}}
` }) export class AppComponent implements OnInit { time: string; ngOnInit() { const timer$ = Observable.interval(1000) .map(event => new Date());
El ejemplo anterior actualiza el temporizador en la vista cada segundo para mostrar el reloj. En lugar de suscribirse al Observable timer$, permítanos mostrarlo directamente en las vistas: import import import import
{ Component } from '@angular/core'; { Observable } from 'rxjs/Observable'; 'rxjs/add/observable/interval'; 'rxjs/add/operator/map';
@Component({ selector: 'app-root', template: `
{{timer$}}
` }) export class AppComponent { timer$ = Observable.interval(1000) .map(event => new Date()); }
[ 80 ]
Working with Observables
El fragmento de código anterior mostrará [object Object] en la pantalla del navegador. Debido a que Observable timer$ es un objeto, no un valor, pero el Observable timer$ emite la fecha y la hora. Solo podemos acceder a este valor en el método Subscribe (). subscribe(). Angular proporciona AsyncPipe para acceder a los valores emitidos por un Observable directamente en la vista.
Usando AsyncPipe Ahora obtenemos el mismo resultado que anteriormente, pero sin suscribirnos directamente al Observable. Vamos a formatear nuestra fecha utilizando DatePipe sólo para mostrar el tiempo: template: `
{{timer$ | async}}
`
Now we get the same output as previously but without directly subscribing to the template: `
TIME: {{timer$ | async | date: 'mediumTime'}}
[ 81 ]
Working with Observables
Crear un componente de búsqueda de libros Para comprender a profundidad los Observables, vamos a ver un ejemplo más. En el Capítulo 2, Conceptos básicos de componentes, creamos una aplicación de detalles maestros de libros. Permítanos agregarle funcionalidad de búsqueda. Necesitamos la siguiente funcionalidad en el formulario de búsqueda: Cuando un usuario comienza a escribir en el cuadro de búsqueda, debemos mostrar la sugerencia del título del libro El usuario debería poder seleccionar el título de las sugerencias El usuario debería en poder buscarde y ver una lista de libros en función de la entrada ingresada el cuadro búsqueda. Todo el código fuente requerido para la configuración está disponible chaper04/books-searchen el código fuente provisto. El siguiente es BookSearchComponent donde vamos a implementar la funcionalidad de búsqueda utilizando los operadores Observables y RxJS. El código para src/books/book-search/book-search.component.tses el siguiente: import import import import
{ Component, OnInit, ViewChild } from '@angular/core'; { Observable } from 'rxjs/Observable'; 'rxjs/add/observable/fromEvent'; 'rxjs/add/operator/map';
Working with Observables ` }) export class BookSearchComponent implements OnInit { @ViewChild('searchInput') searchInput; bookTitles: Array; ngOnInit() { Observable.fromEvent(this.searchInput.nativeElement,'keyup') .map((event: KeyboardEvent) => (event.target).value) .subscribe(title => console.log(title)); }
}
En el Component anterior, , estamos capturando todas las entradas del usuario ingresadas en el cuadro de búsqueda usando un Observable y mostrándolo en la consola. Para buscar títulos de libros y libros basados en la entrada del usuario, debemos implementar esa funcionalidad en un servicio, siguiendo a BookStoreService implementando eso. Tiene dos métodos, uno para buscar libros y otro para buscar títulos de libros. El código para src/books/book-store.service.tses el siguiente: import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; import { Book } from './book'; import MOCK_BOOKS from './mock-books';
@Injectable()
[ 83 ]
Working with Observables export class BookStoreService { booksList: Book[] = MOCK_BOOKS; getBooks(title: string): Observable { return Observable.of(this.filterBooks(title)); } getBookTitles(title: string): Observable { return Observable.of(this.filterBooks(title) .map(book => book.title)); } filterBooks(title: string): Book[] { return title ? this.booksList.filter((book) => new RegExp(title, 'gi').test(book.title)) : []; } }
Podemos actualizar nuestro componente de búsqueda para usar BookStoreService para las sugerencias de títulos del libro cuando el usuario comienza a ingresar datos. El código para src/books/book-search/book-search.component.tses el siguiente: ngOnInit() { Observable.fromEvent(this.searchInput.nativeElement,'keyup') .map((event: KeyboardEvent) => (event.target).value) .subscribe(title => this.bookStoreService .getBookTitles(title) .subscribe(bookTitles => this.bookTitles = bookTitles)); }
[ 84 ]
Working with Observables
En el método subscribe(), estamos llamando al método getBookTitles() y al texto que que se ingresa en el cuadro de búsqueda, que nuevamente arroja los resultados del Observable título del libro. Todo se ve bien; estamos obteniendo los resultados, pero hay algo que no está bien en el fragmento de código precedente. Estamos utilizando subscribe() dentro de otro método subscribe() de nuevo, es similar a las devoluciones de llamada anidadas. No deberíamos escribir el código de esta manera usando RxJS. Para lidiar con este tipo de problema, RxJS ofrece muchos operadores. En nuestro caso, podemos usar el operador mergeMap(); toma el valor fuente de la entrada Observable y produce una salida plana Observable basada en la aplicación de una función que proporcionamos. El código para src/books/book-search/book-search.component.tses el siguiente: ngOnInit() { Observable.fromEvent(this.searchInput.nativeElement,'keyup') .map((event: KeyboardEvent) => (event.target).value) .mergeMap(title => this.bookStoreService.getBookTitles(title)) .subscribe(bookTitles => this.bookTitles = bookTitles); }
Necesitamos refactorizar este código para obtener un mejor rendimiento. En este momento, tan pronto como el usuario comienza a escribir, estamos haciendo llamadas de servicios. La aplicación debe esperar a que el usuario ingrese algunos caracteres y solo luego realice la llamada de servicios, tampoco necesitamos llamar al servicio nuevamente si el siguiente término de búsqueda es el mismo que el anterior. Esto se puede lograr utilizando los operadores debounceTime() y distinctUntilChanged() El código para src/books/book-search/book-search.component.tses el siguiente: ngOnInit() { Observable.fromEvent(this.searchInput.nativeElement,'keyup') .debounceTime(400) .distinctUntilChanged() .map((event: KeyboardEvent) => (event.target).value) .switchMap(title => this.bookStoreService.getBookTitles(title)) .subscribe(bookTitles => this.bookTitles = bookTitles); }
El operador debounceTime(400) espera 400 ms después de cada pulsación de tecla antes de considerar el término de búsqueda, el operador distinctUntilChanged()lo ignora si el siguiente término de búsqueda es el mismo que el anterior. Ahora estamos usando el operador switchMap() en lugar del operador mergeMap(); cambia a un nuevo Observable cada vez que cambia el término de búsqueda.
[ 85 ]
Working with Observables
Hacemos más cambios en nuestro código para enviar el término de búsqueda al componente principal cuando un usuario hace clic en el botón de búsqueda. Lo haremos usando el decorador @Output(), como aprendimos en el último capítulo. El código para src/books/book-search/book-search.component.tses el siguiente: import import import import import import import import import import
{ Component, OnInit, ViewChild } from '@angular/core'; { Output, EventEmitter } from '@angular/core'; { BookStoreService } from '../book-store.service'; { Observable } from 'rxjs/Observable'; 'rxjs/add/observable/fromEvent'; 'rxjs/add/operator/map'; 'rxjs/add/operator/filter'; 'rxjs/add/operator/switchMap'; 'rxjs/add/operator/debounceTime'; 'rxjs/add/operator/distinctUntilChanged';
Working with Observables this.searchInputTerm = res; this.bookTitles = []; }); } searchBooks() { this.bookTitles = []; this.search.emit(this.searchInputTerm); } }
El código para src/books/book-search/book-search.component.htmles el siguiente:
Books Search
0 ? 'block' : 'none'">
{{bookTitle}}
Aquí está el alojamiento AppComponent BookSearchComponent y BooksListComponent que es el componente raíz de nuestra aplicación. ,
El código para src/app.component.tses el siguiente:: import { Component } from '@angular/core'; import { BookStoreService, Book } from './books/index'; @Component({ selector: 'app-root', template: `
`, providers: [BookStoreService] })
[ 87 ]
Working with Observables export class AppComponent { filteredBooks: Book[]; constructor(private bookStoreService: BookStoreService) { } searchBook(title: string) { this.bookStoreService .getBooks(title) .subscribe(books => this.filteredBooks = books); } }
A continuación se muestra el BooksListComponent utilizado en AppComponent para mostrar una lista de libros basada en la entrada de búsqueda del usuario. El código para src/books/books-list/books-list.component.tses el siguiente: import { Component, Input } from '@angular/core'; import { Book } from '../book'; @Component({ selector: 'books-list', styles: [` .book-item { margin-bottom: 1rem; } .cover-image-container { width: 100%; } .cover-image-container img { width: 100%; vertical-align: 0; border: 0; } `], template: `
Aquí está el resultado final de nuestra aplicación:
Todo el código fuente del ejemplo anterior está disponible bajo chaper4 en el código fuente proporcionado.
Como se mencionó al comienzo de esta sección, tenemos diferentes conceptos como el módulo de enrutador, el módulo de formularios y el módulo HTTP que se implementan mediante Observables. Continuaremos aprendiendo cómo usar Observables en próximos capítulos. Se pueden encontrar más recursos para aprender RxJS en http://reactiv ex.io/r xjs /
[ 89 ]
Working with Observables
Resumen Comenzamos este capítulo con qué es la programación reactiva y cómo implementarla utilizando el concepto de Observables. A continuación, analizamos los conceptos básicos de RxJS, como Observables y operadores, y cómo usarlos para escribir componentes angulares y servicios en diferentes escenarios. Al final de este capítulo, el lector debe tener una buena comprensión de los diferentes conceptos de RxJS, como qué son los Observables y los operadores, y cómo usarlos en varios escenarios. En el próximo capítulo, discutiremos cómo construir formularios usando Angular.
[ 90 ]
Manejo de formularios En este capítulo, vamos a aprender a usar la nueva API de formularios en Angular para crear interfaces de usuario para capturar, validar y enviar entradas de usuario. Después de pasar por este capítulo, el lector comprenderá los siguientes conceptos: Formularios impulsados por plantillas en Angular Formularios reactivos en Angular Validar entradas de formulario
¿Por qué son difíciles los formularios? Los formularios son la clave de cualquier aplicación web; nos ayudan a capturar la información de los usuarios. Aquí hay un par de cosas que hacemos con formularios: Captura de entradas del usuario Validar la entrada del usuario Responder a eventos Mostrar los mensajes de información Mostrar los mensajes de error Las validaciones hacen que los formularios sean más difíciles de tratar porque no sabemos de qué manera el usuario ingresa los datos. Una lógica de control podría depender de otra entrada de control. A veces, necesitamos activar la lógica de validación en el servidor en función de la entrada del usuario (verificar la unicidad del nombre de usuario o la dirección de correo electrónico). Necesitamos mantener el estado general del formulario incluso si abarca varias plantillas, como los magos. Angular proporciona un enfoque más simple para capturar la entrada del usuario, así como para hacer frente a las validaciones.
Handling Forms
API de formularios en Angular En el DOM, tenemos controles de entrada, y necesitamos información sobre los controles, como su valor, si los datos ingresados son válidos de acuerdo con las reglas de validación, cómo el usuario ha interactuado con el control, si cambiaron su valor o si lo tocaron, y cómo queremos que nos notifiquen sobre sus eventos (clic, blur y otros eventos DOM) cuando ocurren. Angular tiene los siguientes dos enfoques para tratar con formularios: Formularios impulsados por plantillas Formularios reactivos Cada técnica tiene diferentes opiniones sobre cómo manejar formularios; los veremos en detalle en las próximas secciones.
FormControl, FormGroup, y FormArray Las clases FormControl, FormGroup, y FromArray son la clave para ambas técnicas. Primero comprendamos estas clases, y luego podremos explorar cada método en detalle.
FormControl El control es la unidad más pequeña en cualquier formulario; representa un elemento de entrada de formulario único (cuadro de texto, desplegable, botón de opción, casilla de verificación, etc.). El control es el componente fundamental del API de formularios en Angular; un objeto de control encapsula el valor del campo de entrada y su estado. Se representa utilizando la clase FormControl. Crear un control de formulario
El siguiente fragmento de código crea un solo control llamado firstName: let firstName = new FormControl();
El siguiente fragmento de código crea un único control llamadofirstName y lo inicializa con un valor predeterminado vacío: let firstName = new FormControl('');
El siguiente fragmento de código crea un único control llamado firstName, y se inicializa con el valor predeterminado 'Shravan': let firstName = new FormControl('Shravan');
[ 92 ]
Handling Forms
Accediendo al valor de un control de entrada
Al usar la propiedad de valor del objeto de control de formulario, podemos obtener el valor de la entrada: let firstNameValue = firstName.value;
Establecer el valor del control de entrada
No podemos usar la propiedad de valor para establecer el valor del control de formulario; es solo un getter. Deberíamos usar el método setValue() para establecer el valor mediante programación: firstName.setValue('Shravan');
Restablecer el valor de un control de entrada
El método reset() en el control de formulario establece el valor en nulo: firstName.reset();
Estados de control de entrada
Cada control de entrada en un formulario angular y el formulario en sí mismo mantiene diferentes estados dependiendo de la entrada del usuario y la interacción con él: //form control error list object let errors = firstName.errors // form control value is valid, it has no errors let isValid = firstName.valid // form control value is invalid, it has errors let isInValid = firstName.invalid //Control has been visited let isTouched = firstName.touched //Control has not been visited let isUnTouched = firstName.untouched //Form control's value has changed let valueChanged = firstName.dirty //Form control's value has not changed let valueNotChanged = firstName.pristine
[ 93 ]
Handling Forms
Cada vez que cambia el estado de un control de entrada, Angular actualizará el elemento con las siguientes clases: Estado
Class if true Class if false
El control ha sido visitado
ng-touched ng-untouched
El valor del control ha cambiado
ng-dirty
El valor del control es válido ng-valid
ng-pristine ng-invalid
Los estados y las clases anteriores no son aplicables solo al objeto FormControl, también se aplicarán a FormGroup, FormArray, y al formulario completo.
FormGroup Incluso un formulario simple contiene más de un control que podría ser dependiente el uno del otro. Informados de trabajar con cada control e iterar sobre ellos para conocer el valor y el estado de cada control y formulario, queremos saber el estado de múltiples controles a la vez. A veces tiene más sentido pensar en una serie de controles de formulario como grupo. Tenemos otra clase, FormGroup que es útil. Es una colección de controles de formulario y mantiene el estado general del formulario. Por ejemplo, necesitamos una dirección de usuario que contenga la calle, ciudad, estado, país y código postal. Podemos crear cinco objetos individuales de FormControl y trabajarlos uno a la vez, pero todos juntos representan una dirección donde podemos usar la clase FormGroup: //create a form group let address = new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), country: new FormControl(''), zip: new FormControl('') }); //return an object literal of form group value let formModel = address.value; //{street: "", city: "", state: "", country: "", zip: ""} //check overall state form state let errors = address.errors; //null let isValid = address.valid; //true let isInValid = address.invalid; //false let isTouched = address.touched; //false
[ 94 ]
Handling Forms let isUnTouched = address.untouched; //true let valueChanged = address.dirty; //false let valueNotChanged = address.pristine; //true //set the value of the form group address.setValue({ street: '1-3 Strand', city: 'London', state: '', country: 'UK', zip: 'WC2N 5BW' });
Podemos usar el método setValue() para establecer el valor de FormGroup mediante programación, pero debemos aprobar todos los controles que se declaran inicialmente con FormGroup. De lo contrario, el método setValue() arroja un error. Si necesitamos actualizar FormGroup parcialmente desde un superconjunto o subconjunto, podemos usar el método patchValue(): address.patchValue({ street: '1-3 Strand', city: 'London' });
El método patchValue() acepta superconjuntos y subconjuntos de grupo sin lanzar un error. Form () en sí se representa utilizando la clase FormGroup.
FormArray La clase FormArray es similar a la clase FormGroup, también es una colección de controles de formulario y mantiene el estado general del formulario. Podemos usar FormArray para crear un formulario variable o de longitud desconocida: //create a form array let registration = new FormArray([ new FormControl('Shravan'), new FormControl('Kasagoni'), new FormControl('[email protected]')
[ 95 ]
Handling Forms registration.push(new FormControl('UK')); registration.patchValue(['London','W5' //access form array value console.log(registration.value); console.log(registration.value[0
Ahora, hemos aprendido las clases básicas para el módulo de formularios angulares. Vamos a sumergirnos en los diferentes enfoques proporcionados por Angular.
Formularios impulsados por plantillas El enfoque de formularios basados en plantilla es similar al trabajo con formularios en Angular 1.x. Como sugieren los nombres, escribiremos toda la lógica, como crear controles de formulario, formularios y definir validaciones dentro de la plantilla de manera declarativa.
Crear un formulario de registro Para comenzar con formularios basados en plantillas en Angular, comencemos con la creación de un proyecto denominado forms y el uso de la siguiente estructura de directorios y archivos: forms €• index.html €• package.json €• src
Necesitamos agregar el código a package.json, tsconfig.json, systemjs-angularloader.js, system.config.js, y index.html del último ejemplo en el Capítulo 4, Trabajando con Observables.
[ 96 ]
Handling Forms
Antes de ejecutar la aplicación, asegurémonos de tener "@angular/forms": "^4.0.0" agregado a la sección de dependencias en el archivo package.json y agregue la siguiente línea '@angular/forms': ng:forms/bundles/forms.umd.js' , para mapear el objeto en el archivo systemjs.config.js. El código para styles.css es el siguiente: /** The stylesheet is very long, reader can add it from sample code under chapter5/forms example. **/
El código para src/app.module.ts es el siguiente: import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { RegistrationFormComponent } from './registrationform/registration-form.component'; @NgModule({ imports: [BrowserModule, FormsModule], declarations: [AppComponent, RegistrationFormComponent], bootstrap: [AppComponent] }) export class AppModule { }
Agregamos FormsModule del paquete '@angular/forms' para import arrays, todas las clases relacionadas con los formularios impulsados por plantillas se encuentran en este módulo. El RegistrationFormComponentse agrega al array declarations, y este componente es parte de nuestro AppModule, podemos acceder a él en cualquier parte de nuestro AppModule. El código para src/app.component.tses el siguiente: import { Component } from '@angular/core'; @Component({ selector: 'forms-app', template: '' }) export class AppComponent { }
[ 97 ]
Handling Forms
No tenemos mucho código en AppComponent, solo es un marcador de posición para mostrar RegistrationFormComponent. En la plantilla de AppComponent, estamos usando la etiqueta dentro de las etiquetas en index.html. Todos los próximos ejemplos usarán el AppComponent solo como un marcador de posición para mostrar otros componentes, no hay una ventaja adicional aquí, pero comprenderemos por qué lo estamos haciendo en capítulos futuros. El código para src/registration-form/registration-form.component.tses el siguiente: import { Component } from '@angular/core'; @Component({ selector: 'registration-form', templateUrl: './registration-form.component.html' }) export class RegistrationFormComponent { }
El código parasrc/registration-form/registration-form.component.htmles el siguiente:
Registration Form
[ 98 ]
Handling Forms
Parece que hay mucho código en la plantilla, pero solo tenemos dos cuadros de texto; las otras piezas es solo HTML y clases CSS de Bootstrap para fines de diseño. Todavía no hay un código relacionado con formularios angulares. Nuestra aplicación está lista. Ahora ejecute el comando npm install, una vez que finalice ejecute el comando npm start. Esto iniciará nuestra aplicación en el navegador. Podemos ver el siguiente resultado en el navegador:
Tenemos dos controles de entrada en HTML en la página. Necesitamos hacer que formen controles agregando algún código relacionado con formularios angulares. Cuando trabajamos con formularios basados en plantillas, nunca crearemos directamente el objeto FormControl, FormGroup por nuestra cuenta. En cambio, utilizaremos las directivas ngModel, ngModelGroup, ngForm en los controles de entrada, todo manejado internamente por angular.
Usando la directiva ngModel Para trabajar con controles de entrada individuales, debemos usar la directiva ngModel. Vamos a agregarlo a nuestros controles de entrada (Nombre y Apellido) en la plantilla registration-form.component.html:
Agregamos ngModel a nuestros controles de entrada. No habrá ningún cambio en la salida del navegador, pero bajo la cubierta Angular crea dos objetos FormControl para First name y Last name.
[ 99 ]
Handling Forms
Accediendo a un valor de control de entrada usando ngModel La directiva ngModel en los controles de entrada representa el objeto del modelo. Para acceder a ella necesitamos exportar a una variable de referencia de plantilla:
Ahora, podemos utilizar estas variables de referencia de plantilla #firstNameRef y #lastNameRef para acceder a los objetos del modelo (FormControl) de los controles de entrada First name y Last name en cualquier lugar de la plantilla. Usemos las variables de referencia de plantilla y la sintaxis de interpolación para mostrar el texto escrito en los controles de entrada: {{firstNameRef}} {{lastNameRef}}
Una vez que guardamos la plantilla, el navegador se actualizará con el siguiente resultado:
La interpolación muestra objetos porque, como se mencionó anteriormente, las variables de referencia de la plantilla se exportan con el objeto del modelo(FormControl) de los controles de entrada. Debemos usar propiedades para acceder a sus valores y Estados: {{firstNameRef.value}}
{{lastNameRef.value}}
[ 100 ]
Handling Forms
Después de guardar el código, una vez que el navegador se actualiza, comienza a escribir el mismo texto en cualquiera de los campos de entrada. Inmediatamente podemos ver esos valores que se muestran en la pantalla del navegador:
Usar ngModel para enlazar un valor de cadena Si necesitamos vincular un control de entrada a un valor inicial, podemos simplemente especificar ngModel="". Este es un enlace de cadena simple. No estamos utilizando el enlace de propiedad ni el enlace de evento. El ejemplo de esto se muestra en el siguiente fragmento de código: {{firstNameRef.value}} {{lastNameRef.value}}
Una vez que guardamos la plantilla, el navegador se actualizará con el siguiente resultado:
[ 101 ]
Handling Forms
Usar ngModel para enlazar una propiedad del componente El código para src/registration-form/registration-form.component.tses el siguiente: import { Component } from '@angular/core'; interface User { firstName: string; lastName: string; } @Component({ selector: 'registration-form', templateUrl: './registration-form.component.html' }) export class RegistrationFormComponent { user: User = { firstName: 'Shravan', lastName: 'Kasagoni' } }
Tenemos un objeto de usuario inicializado dentro de la clase RegistrationFormComponent. Usemos este objeto de usuario para inicializar los controles de entrada en la plantilla. El código para src/registration-form/registration-form.component.htmles el siguiente: {{firstNameRef.value}} {{user.firstName}}
{{lastNameRef.value}} {{user.lastName}}
Si miramos el resultado en el navegador, los controles de entrada y su interpolación han mostrado las cadenas user.firstName y user.lastName directamente en lugar de sus valores. Como estamos usando ngModel="", es simplemente un enlace de cadena simple, no un enlace de propiedad. Vamos actualizar nuestro código para usar el enlace de propiedad y establecer los valores iniciales:
[ 102 ]
Handling Forms
Después de actualizar los controles de entrada con enlace de propiedad, el componente comenzará a mostrar los valores de propiedad user.firstName and user.lastName. Después de actualizar los controles de entrada con enlace de propiedad, el componente comenzará a mostrar los valores de propiedad user.firstName y user.lastName. Un comportamiento interesante que podemos observar, una vez que comencemos a cambiar valores en los controles de entrada, su enlace de interpolación se actualizará con lo que los escribamos, pero no las propiedades del componente. Debido a que el enlace de propiedad es unidireccional, no actualizará la propiedad con cambios de datos en el control de entrada. Para actualizar la propiedad enlazada con el valor actualizado del control de entrada, podemos usar un enlace de datos bidireccional como este: [(ngModel)]="user.firstName". Sin embargo, no se recomienda utilizar el enlace de datos bidireccional en formularios dirigidos por plantilla hasta que sea necesario porque tenemos que mantener el estado tanto en la plantilla como en el componente que es innecesario, y puede causar problemas desconocidos. ¿Cómo podemos devolver los valores de los controles de entrada al componente? Deberíamos usar la variable de referencia de la plantilla de formulario y enviar el valor de la misma al componente. Vamos a eliminar todo el código agregado a la plantilla y el componente, ponerlo nuevamente en el estado inicial de nuestro ejemplo y agregarle una etiqueta
y un botón submit dentro de ella; una vez que el navegador se actualiza con la última versión, hay muchos errores en la consola:
El mensaje de error dice bastante claramente:
Si ngModel se utiliza dentro de una etiqueta de formulario, se debe establecer el atributo de nombre o el control de formulario se debe definir como 'independiente' en ngModelOptions. Cuando usemos ngModel en el control de entrada dentro de una etiqueta
, we debemos declarar un atributo name en ella. ngModel registra los controles de entrada usando su atributo de nombre en el formulario. Agreguemos una propiedad de nombre a ambos controles de entrada:
[ 104 ]
Handling Forms
Uso de la directiva ngForm La directiva ngForm en la etiqueta form representa el objeto del modelo (FormGroup), para acceder a ella debemos exportarla a una variable de referencia de plantilla:
{{formRef | json}}
El código entre las etiquetas
se elimina para su legibilidad. Podemos usar las variables de referencia de la plantilla #formRef para acceder a los objetos del FormGroup , la variable #formRef, y la pipe JSON para mostrar modelo de formulario su estructura completa(del modelo )de formulario:
No necesitamos toda la estructura; simplemente necesitamos el valor del formulario. Usemos la propiedad value en la clase FormGroup porque internamente ngForm es un FormGroup:
{{formRef.value | json}}
[ 105 ]
Handling Forms
Ahora podemos ver el objeto JSON que se muestra en la pantalla del navegador con los valores de los controles de entrada:
Curiosamente, no agregamos la directiva ngForm en la etiqueta
como hemos añadido la directiva ngModel en los controles de entrada. Esto se debe a que cada vez que Angular encuentra una etiqueta
en la plantilla, activará y adjuntará implícitamente la directiva ngForm a la etiqueta
, no es necesario que agreguemos la directiva ngForm explícitamente en la etiquetaform. Si no queremos que la directiva ngForm se adjunte automáticamente a la etiqueta
, podemos inhabilitar esta funcionalidad agregando la directiva ngNoForm como un atributo a la etiqueta
.
Envío de un formulario usando el método ngSubmit Para enviar nuestro formulario, debemos usar un evento ngSubmit y adjuntarlo a un método en el componente. Agreguemos un método para mostrar el valor que se le pasó en la consola del navegador: export class RegistrationFormComponent { onSubmit(formValue) { console.log(formValue); } }
[ 106 ]
Handling Forms
Invoquemos el método onSubmit() siempre que se active un evento ngSubmit:
Ahora, una vez que ingresamos algunos datos y hacemos clic en el botón Submit, se desencadenará el evento(ngSubmit), que invoca el método onSubmit() en el componente. Para el método onSubmit(), estamos pasando nuestro valor de formulario usando formRef.value y mostrándolo en la consola del navegador:
Estamos extendiendo nuestro ejemplo para usar un par de campos más y los controles más utilizados (botones de opción, casilla de verificación y desplegable) en los formularios. Comencemos por agregar campos de email, password, y confirmPassword:
[ 107 ]
Handling Forms
Ahora agreguemos los campos street, city, state, zip, y country:
[ 108 ]
Handling Forms ngModel="" #countryRef="ngModel">
Añadamos campos de gender y service :
value="Male" class="blue">Male
value="Female" class="blue">Female
value="Other" class="blue">Other
I agree to the Terms of Service
[ 109 ]
Handling Forms
Una vez que guardamos la plantilla, el navegador se actualiza con el último relleno en la entrada y al hacer clic en el botón Submit, podemos ver todos los valores seleccionados como un objeto en la consola. En aplicaciones del mundo real, podríamos enviar estos datos al servidor usando HTTP o podríamos realizar algunas operaciones más con estos datos:
Usando la directiva ngModelGroup Como se menciona en la sección FormGroup, a veces tiene más sentido pensar en una serie de controles de formulario como grupo y trabajar con ellos. En nuestro ejemplo, tenemos street, city, state, zip, y country y estamos tratando todos estos campos como controles individuales, pero todo lo que representan es la dirección, por lo que los tratamos a todos como el grupo de direcciones.
[ 110 ]
Handling Forms
En la plantilla dirigida para agrupar los controles, podemos usar la directiva ngModelGroup. Coloquemos todos los campos de street, city, state, zip, y country dentro de una etiqueta div y adjúntela a la directiva ngModelGroup:
Una vez más, completamos la entrada y hacemos clic en Submit. Podemos ver todos los valores seleccionados como un objeto impreso en la consola y las propiedades de street, city, state, zip,
y country ahora están debajo del objeto address, en lugar de la raíz:
El valor y el estado de ngModelGroup dependen de todos los controles dentro de él; podemos usar su variable de referencia de plantilla, #addressRef para acceder al valor, estado y otras propiedades dentro de la plantilla. Internamente, la directiva ngModelGroup es FormGroup.
[ 111 ]
Handling Forms
Agregar validaciones al formulario de registro Antes de enviar cualquier formulario, debemos validar si la entrada ingresada por el usuario es correcta o no. Para aplicar la validación en los formularios, Angular proporciona las siguientes directivas de validación, también podemos construir nuestros validadores personalizados: Marca un control para tener un valor no vacío Aplica validación de longitud mínima maxlength: Aplica validación de longitud máxima required:
minlength:
pattern:
Valida un valor de control para que coincida con una expresión regular
En función de la validez de entrada, las directivas de validación cambian el estado de ngModel, ngModelGroup, y ngForm, podemos acceder a ellas utilizando sus variables de referencia de plantilla. Usemos las directivas de validación angular para aplicar la validación en nuestros controles de formulario. Para demostrar las directivas de validación, vamos a usar solo el campo de entrada firstName Podemos aplicar la validación al resto de los campos de forma similar. Comencemos con la creación de un proyecto llamado form-validations de los ejemplos de forms anteriores, cambie el nombre en el archivo package.json a form-validations:
{{firstNameRef?.errors | json}}
{{firstNameRef.valid}}
{{formRef.valid}}
Aplicamos la directiva required en nuestro campo firstName, el usuario debe ingresar alguna entrada antes de enviarla:
[ 112 ]
Handling Forms
Estamos utilizando la propiedad errors en el objeto de control de formulario para obtener la lista de errores. El valor de la propiedad errors es un objeto u objeto de objetos según el número de validaciones que apliquemos en el control de formulario. Para cada directiva de validación, la propiedad errors tendrá un objeto que contiene información al respecto. En nuestro ejemplo, aplicamos directivasrequired en el control de formularios, y su propiedad errors es { "required": true }. Nos dice que este campo es obligatorio, podemos usar la información en la propiedad errors para mostrar mensajes de error y comentarios al usuario en el formulario. El control de formulario, propiedad valid devuelve falso porque sus reglas de validación no están satisfechas y la propiedad valid devuelve false porque su estado se calcula en función del estado del control dentro de él. Una vez que todos los controles dentro del formulario se vuelven válidos, su estado también se vuelve válido.
[ 113 ]
Handling Forms
Usemos la propiedad errors para mostrar un mensaje de error al usuario:
The first name is required.
La salida del mensaje de error se puede ver en la siguiente captura de pantalla:
Estamos utilizando las propiedades errors y touched t para mostrar el mensaje de error tan pronto como el usuario abandona el cuadro de texto First name al ingresar la entrada. También estamos utilizando las mismas propiedades en el cuadro de texto First name para hacer que el borde de color rojo utilice el enlace de class. Podemos aplicar tantas validaciones como deseemos en los controles de entrada. Usemos la directiva minlength para implementar una validación, para forzar al usuario a ingresar un mínimo de tres caracteres en el cuadro de texto First name:
The first name is required.
You should enter minimum {{firstNameRef?.errors?.minlength.requiredLength}} characters into first name, but you entered only
[ 114 ]
Handling Forms {{firstNameRef?.errors?.minlength.actualLength}} characters.
Si el usuario ingresa menos o igual a dos caracteres en el cuadro de texto First name, se mostrará el siguiente mensaje de error:
Vamos a hacer una última cosa en nuestro formulario. Aunque hay errores, el usuario puede enviarlos, lo que no debería ser el caso, por lo que desactivaremos el botón Submit si el formulario no es válido:
[ 115 ]
Handling Forms
Aquí hay un ejemplo de un formulario completamente implementado con todas las validaciones. El código de muestra está bajo el ejemplo capítulo 5/ form-validations:
Pros y contras de formularios basados en plantilla Es muy fácil crear formularios grandes de manera declarativa utilizando formularios basados en plantillas. Sin embargo, la desventaja es toda la lógica, y las reglas de validación están en HTML. Es difícil probar la lógica de validación. Tenemos que hacer pruebas de extremo a extremo para verificar la funcionalidad usando herramientas como protractor.
[ 116 ]
Handling Forms
Formularios reactivos Los fornularios reactivos (también conocidas como formularios impulsados por modelos) son el nuevo enfoque presentado en Angular. A diferencia de los formularios basados en plantillas, en formularios reactivos, escribiremos toda la lógica de formularios, como crear controles, formularios y definir reglas de validación dentro de nuestras clases de componentes utilizando la API de formularios en lugar de HTML. En un enfoque de formularios reactivos, utilizaremos directamente las clases FormControl, FormGroup, FormArray, y FormBuilder para crear controles de entrada, Component
formularios y aplicar reglas de validación dentro de la clase . Vamos a crear nuestro ejemplo anterior utilizando el enfoque de formularios reactivos.
Crear un formulario de registro usando formularios reactivos Para comenzar con formularios reactivos en Angular, comencemos por configurar un proyecto llamado reactive-forms y usando la siguiente estructura de directorios y archivos. Copia el ejemplo del código anterior: reactive-forms €• index.html €• package.json €• src ƒ €• app.component.ts ƒ €• app.module.ts ƒ €• main.ts ƒ ‚• registration-reactive-form ƒ €• registration-reactive-form.component.html ƒ ‚• registration-reactive-form.component.ts €• styles.css €• systemjs-angular-loader.js €• systemjs.config.js ‚• tsconfig.json
[ 117 ]
Handling Forms
El código para src/app.module.ts es el siguiente: import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { RegistrationReactiveFormComponent } from './registration- reactive-form /registration-reactive-form.component'; @NgModule({ imports: [BrowserModule, ReactiveFormsModule], declarations: [AppComponent, RegistrationReactiveFormComponent], bootstrap: [AppComponent] }) export class AppModule { }
Debemos importar e incluir ReactiveFormsModule al array de importaciones porque todas las clases de API del formulario, FormControl, FormGroup, FormArray, FormBuilder, y Validators están disponibles en ese módulo. El código para src/app.component.tses el siguiente: import { Component } from '@angular/core'; @Component({ selector: 'forms-app', template: `` }) export class AppComponent { }
Uso de FormGroup, FormControl, y Validators Usemos las clases FormGroup, FormControl, y Validators para construir nuestro formulario de registro dentro de la clase Component. El código para src/registration-reactive-form/registrationreactive-form.component.tses el siguiente: import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl, Validators } from '@angular/forms';
[ 118 ]
Handling Forms @Component({ selector: 'registration-reactive-form', templateUrl: './registration-reactive-form.component.html' }) export class RegistrationReactiveFormComponent implements OnInit { EMAIL_REGEX = "[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*"; registrationForm: FormGroup; ngOnInit() { this.registrationForm = new FormGroup({ firstName: new FormControl('Shravan', Validators.required), lastName: new FormControl(''), email: new FormControl('', [Validators.required, Validators.pattern(this.EMAIL_REGEX)]) }); } onSubmit(formValue) { console.log(formValue); console.log(this.registrationForm.value) } }
El código es bastante autoexplicativo. Al comienzo del capítulo, discutimos las clases FormControl y FormGroup. En nuestro componente, aplicamos la validación en los controles de formulario utilizando la clase Validators que proporciona las mismas directivas de validación (required, minlength, maxlength, pattern) que discutimos en los formularios basados en plantilla como método. Si necesitamos utilizar múltiples directivas de validación en un control de formulario único, podemos pasarlas en una matriz.
Usando [formGroup], formControlName, y formGroupName Ahora tenemos que vincular FormGroup a la etiqueta
utilizando el enlace [formGroup], y FormControl a la etiqueta utilizando la directiva formControlName:
[ 119 ]
Handling Forms *ngIf="registrationForm.get('email').touched && registrationForm.get('email').hasError('required')"> The email is required.
Estamos vinculando el objeto registrationForm (una instancia de la clase FormGroup) a [formGroup]. La entrada de correo electrónico se adjunta a la propiedad email (una instancia de una clase FormControl) utilizando la directiva formControlName.
En formularios reactivos, no necesitamos la propiedad de nombre en los controles de entrada porque se crean en el componente y solo se vinculan a los controles en la plantilla. Tampoco tnecesitamosuna variable de referencia de plantilla; podemos acceder directamente a los controles de formulario utilizando el método get()en la clase FormGroup como registrationForm.get ('email'), esto accederá a todos los métodos y propiedades en la clase FormControl.
Estamos accediendo a las validaciones en los controles de formulario utilizando el método hasError() en lugar de la propiedad errors, cualquier enfoque funciona bien. La salida sería más o menos la misma. Para agrupar los controles, debemos anidar el grupo de formularios dentro de otro grupo de formularios: this.registrationForm
= new FormGroup({ firstName: new FormControl('Shravan', Validators.required), lastName: new FormControl(''), email: new FormControl('', [Validators.required, Validators.pattern(this.EMAIL_REGEX)]), address: new FormGroup({ street: new FormControl(''), country: new FormControl('', Validators.required) }) });
Necesitamos usar la directiva formGroupName para vincular los controles del grupo en HTML:
Street
[ 120 ]
Handling Forms
Country
The country is required.
El código restante en la plantilla se elimina para facilitar la lectura. El fragmento de código precedente debe estar dentro de las etiquetas
form>
Usando FormBuilder La clase FormBuilder proporciona una API más simple para tratar con grupos de control: import { Component, OnInit } from '@angular/core'; import { FormGroup, Validators, FormBuilder } from '@angular/forms'; @Component({ selector: 'registration-reactive-form', templateUrl: './registration-reactive-form.component.html' }) export class RegistrationReactiveFormComponent implements OnInit { EMAIL_REGEX = "^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$"; registrationForm: FormGroup; constructor(public formBuilder: FormBuilder) { } ngOnInit() { this.registrationForm = this.formBuilder.group({ firstName: ['Shravan', Validators.required], lastName: '',
El group()de clase FormBuilder devuelve el objeto FormGroup en sí. Dentro del método group, solo estamos pasando el valor inicial y los Validators para el control de formularios en lugar de crear un objeto FormControl de forma manual cada vez. No necesitamos hacer ningún cambio en la plantilla, simplemente funciona, esta es una API simplificada.
CustomValidators En Angular, un validador es una función simple que acepta AbstractControl como parámetro de entrada y devuelve un literal de objeto donde la clave es un código de error y el valor es verdadero si falla. Queremos usar este CustomValidators en múltiples componentes, vamos a crear una clase llamada CustomValidators y agregar nuestras funciones de validación personalizadas dentro de ella. El código para src/custom-validators.tses el siguiente: import { AbstractControl } from '@angular/forms'; export class CustomValidators { static passwordStrength (control: AbstractControl) { if (CustomValidators.isEmptyValue(control.value)){ return null; } if (!control.value.match(/^(?= .*[0-9])(?=.*[!@#\$%\^&\*])(?=.*[a-z]) (?=.*[A-Z])[a-zA-Z0-9!@#\$%\^&\*]{8,}$/)) { return {'weakPassword': true}; } retrun null;
[ 122 ]
Handling Forms } static isEmptyValue (value) { return value == null || typeof value === 'string' && value.length === 0; } }
Creamos un método estático, passwordStrength() que acepta el control como parámetro y compara su valor con una expresión regular para verificar la fortaleza de la contraseña y devuelve un objeto de error si el valor de control no cumple con los criterios de expresión regular: import { CustomValidators } from '../CustomValidators'; ngOnInit () { this.registrationForm = this.formBuilder.group({ password: ['', [Validators.required, CustomValidators.passwordStrength]] }); }
Dentro de la plantilla, deberíamos tener la misma propiedad(weakPassword) en el objeto de error devuelto por CustomValidators utilizando el método hasError('weakPassword'):
The password is required.
The password must be minimum 8 characters, must contain at least 1 lowercase alphabet, 1 uppercase alphabet, 1 numeric character, 1 special character.
[ 123 ]
Handling Forms
Aprendimos cómo aplicar la validación personalizada en un solo control, pero a veces nuestra lógica depende de múltiples valores de control en el sentido de que debemos usar los validadores a nivel de grupo. Vamos a crear un validador más que compare tanto la contraseña como los valores de contraseña y devuelva un objeto de error si ambas contraseñas no coinciden: import { AbstractControl } from '@angular/forms'; export class CustomValidators { static passwordMatcher(control: AbstractControl) { const password = control.get('password').value; const confirmPassword = control.get('confirmPassword').value; if (CustomValidators.isEmptyValue(password) || CustomValidators.isEmptyValue(confirmPassword)) { return null; } return password === confirmPassword ? null : { 'mismatch': true }; } static isEmptyValue(value) { return value == null || typeof value === 'string' && value.length === 0; } }
Podemos usar el validador passwordMatcher() en el nivel de grupo de formulario en el componente y su objeto de error en la plantilla: ngOnInit () { this.registrationForm = this.formBuilder.group({ password: ['', [Validators.required, CustomValidators.passwordStrength]], confirmPassword: ['', Validators.required], }, {validator: CustomValidators.passwordMatcher}); }
[ 124 ]
Handling Forms
In the template:
The confirm password is required.
The confirm password should match password.
Pros y contras de formularios reactivos Como se mencionó anteriormente, el enfoque de formularios reactivos es nuevo en Angular. Es muy fácil definir formularios complejos en código. Mientras escribimos toda la lógica de validación en los componentes, la prueba unitaria de la lógica de nuestro formulario es bastante fácil sin ninguna dependencia de DOM, simplemente instanciando las clases.
Resumen Comenzamos este capítulo con una discusión sobre por qué es más difícil desarrollar formularios, y luego discutimos diferentes tipos de enfoques en Angular que facilitan el desarrollo. Aprendimos a construir formularios basados en plantillas y formularios reactivos y los pros y las contras de ambos métodos. También aprendimos cómo usar validaciones integradas y cómo escribir CustomValidators. Al final de este capítulo, el lector debe tener una buena comprensión de cómo crear formularios usando diferentes APIs en Angular.
[ 125 ]
Creación de una aplicación de tienda de libros En este capítulo, aprenderemos cómo implementar algunos escenarios de aplicaciones del mundo real mediante el desarrollo de una aplicación de tienda de libros. Después de pasar por este capítulo, el lector comprenderá los siguientes conceptos: Comunicarse con el servicio REST usando un cliente HTTP Navegando entre los componentes usando el enrutamiento Animaciones NgRX módulos de características
Aplicación de tienda de libros Vamos a aprender a desarrollar una aplicación Book Store utilizando varios conceptos angulares. La aplicación Book Store consta de diferentes componentes relacionados con las características proporcionadas por una verdadera librería, donde podemos ver la lista disponible de libros y la información de cada libro, agregar libros nuevos y eliminar libros antiguos. Antes de que comencemos a desarrollar nuestra aplicación Book Store, aprenderá a usar un cliente HTTP en Angular.
HTTP Cualquier aplicación angular que necesite comunicarse con el backend utilizando los servicios REST necesita un cliente HTTP. Angular viene con su propia biblioteca HTTP; está disponible en el paquete @angular/http.
Building a Book Store Application
Antes de que comencemos a aprender sobre la biblioteca HTTP, necesitamos una aplicación donde podamos usarla. Vamos a utilizar Angular CLI para crear nuestro proyecto; antes de comenzar, asegúrese de tener Angular CLI instalado en su máquina. Ejecute el siguiente comando para instalar Angular CLI: $ npm install -g @angular/cli@latest
Ejecute el siguiente comando para crear un proyecto angular utilizando CLI: $ ng new http-client-basics
El comando anterior creará la aplicación angular con todas las bibliotecas y herramientas necesarias. Ahora navegue a nuestra carpeta de proyectos e inicie la aplicación usando los siguientes comandos: $ cd http-client-basics $ npm start
Ahora el proyecto se está ejecutando en: http://localhost:4200. Navegue a la URL en el navegador y podemos ver la salida. Obtenga más información sobre Angular CLI en: https://cli.angular.io.
Necesitamos un poco más de configuración antes de comenzar a escribir el código para usar el cliente HTTP. Nuestro cliente HTTP necesita conectarse a un servicio REST para obtener los datos; para el propósito de este ejemplo, vamos a usar el paquete npm JSON server para crear una API REST falsa. Podemos reemplazar esto con cualquier API REST real. Siga estos pasos para usar el servidor JSON: 1. Instale el paquete npm JSON server en nuestro directorio raíz de aplicación. $ npm install json-server --save-dev
2. Agregue el siguiente comando a la sección de scripts en el archivo package.json para ejecutar el JSON server. "json-server": "json-server --watch db.json --port 4567"
[ 127 ]
Building a Book Store Application
Copie el archivo db.json en nuestro directorio raíz de la aplicación. El archivo contiene información del libro, que utilizamos en el Capítulo 3, Componentes, Servicios e Inyección de Dependencia (puede copiar este archivo desde el código fuente en el capítulo 6 / httpclient-basics). Ahora ejecute el siguiente comando para invocar el JSON server: $ npm run json-server
El comando anterior iniciará nuestra API en la URL http://localhost:4567. Podemos navegar a esta URL en el navegador para verificar la funcionalidad. Obtenga más información sobre JSON server en: https://github.com/typicode/jsonserver.
[ 128 ]
Building a Book Store Application
Tenemos nuestra API lista; Escribamos algunos códigos para comunicarnos con la API. Como se mencionó al principio, necesitamos el paquete @angular/http para trabajar con el cliente HTTP; Angular CLI ya descargó el paquete npm cuando creamos el proyecto. Importe HttpModule en nuestro módulo de aplicación (src/app/app.module.ts). import { HttpModule } from '@angular/http';
Agregue el HttpModule al array imports en el decorador @NgModule(): @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, FormsModule, HttpModule ], providers: [], bootstrap: [AppComponent] })
[ 129 ]
Building a Book Store Application
Agregue la clase Book bajo la carpeta de la aplicación, que representa la estructura del objeto libro. El código para src/app/book.ts es el siguiente: export class Book { id: number; isbn: number; title: string; authors: string; published: string; description: string; }
coverImage: string;
Añadamos un código a nuestra plantilla de componentes de aplicación (app.component.html).
{{booksList | json}}
En la plantilla anterior, estamos llamando al método getBooksData() en la clase Component cada vez que se hace clic en el botón, y también mostramos la matriz booksList en formato JSON utilizando un pipe json. Deberíamos definir el método getBooksData(), y el array booksList en la clase Component (app.component.ts): import { Component } from '@angular/core'; import { Book } from './book'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { booksList: Book[] = []; getBooksData() { console.log(this.booksList); } }
[ 130 ]
Building a Book Store Application
Hacer solicitudes GET El cliente HTTP está disponible como servicio Http en el paquete @angular/http; importarlo en nuestro componente e inyectarlo a través de la inyección de dependencia en un constructor: import { Http } from '@angular/http';
constructor(private http: Http) { }
Tenemos nuestro cliente HTTP; ahora invoque la API para obtener los datos cada vez que el usuario haga clic en el botón Get Books Data: getBooksData() { this.http.get('http://localhost:4567/books') .subscribe(res => this.booksList = res.json() as Book[ }
Estamos llamando a nuestra API usando el método GET. El servicio Http de Angular es un Observable, y debemos suscribirnos para recibir la respuesta. Una vez que el código anterior se agrega al método getBooksData(), si hacemos clic en el botón, recibiremos toda la información de los libros de la API en formato JSON .
[ 131 ]
Building a Book Store Application
Solo mostramos todas nuestras respuestas en la plantilla, lo cual no es muy útil. Vamos a cambiarlo a un formato presentable para el usuario. El código para src/app/app.component.htmles el siguiente:
{{book.title}}
{{book.isbn}}
{{book.title}}
{{book.authors}}
{{book.published}}
{{book.description}}
[ 132 ]
Building a Book Store Application
Actualizamos nuestra plantilla para mostrar la lista de libros en el lado izquierdo de la página. Cada vez que el usuario hace clic en el nombre del libro, llamamos a nuestra API utilizando el cliente HTTP para obtener información específica del libro, y la respuesta se muestra en el lado derecho de la página. Tenemos todos los libros relacionados con la información. Aún así, llamaremos a la API para obtener la información específica del libro usando la ID solo para el propósito de este ejemplo. Una API realpara debería devolver la información requerida, y actualizamos nuestro componente llamar a la APIsolo y obtener la información específica del libro: El código para src/app/app.component.tses el siguiente: import { Component, OnInit } from '@angular/core'; import { Http } from '@angular/http'; import { Book } from './book'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { booksList: Book[] = []; book: Book; baseUrl: string = 'http://localhost:4567'; constructor(private http: Http) { } ngOnInit() { this.getBooksData(); } getBooksData() { const url = `${this.baseUrl}/books`; this.http.get(url) .subscribe(res => this.booksList = res.json() as Book[ } getBookInfo(id: number) { const url = `${this.baseUrl}/books/${id}`; this.http.get(url) .subscribe(res => this.book = res.json() as Book);
[ 133 ]
Building a Book Store Application } }
Aquí está el resultado:
Vamos a refactorizar nuestro código antes de pasar a la siguiente sección. En nuestro ejemplo, AppComponent se está comunicando directamente con la API utilizando el servicio Http. Esta es la responsabilidad de un servicio angular. Mueva toda la lógica relacionada con la comunicación API a un servicio Book Store: El código para src/app/book-store.service.tses el siguiente: import import import import import
{ Injectable } from '@angular/core'; { Http } from '@angular/http'; { Observable } from 'rxjs/Observable'; 'rxjs/add/operator/map'; { Book } from './book';
Building a Book Store Application const url = `${this.baseUrl}/books`; return this.http.get(url) .map(response => response.json() as Book[ } getBook(id: number): Observable { const url = `${this.baseUrl}/books/${id}`; return this.http.get(url) .map(response => response.json() as Book); } }
Necesitamos importar y agregar BookStoreService al array providers en el AppModule antes de que podamos comenzar a usarlo: El código para src/app/app.module.tses el siguiente: import { BookStoreService } from './book-store.service'; @NgModule({ ... providers: [BookStoreService], ... }) export class AppModule { }
Aquí está el AppComponent refactorizado que usa el BookStoreService para obtener los datos de la API: El código para src/app/app.component.tses el siguiente: import { BookStoreService } from './book-store.service'; export class AppComponent implements OnInit { booksList: Book[] = []; book: Book; constructor(private bookStoreService: BookStoreService) { } ngOnInit() { this.getBooksData(); } getBooksData() { this.bookStoreService.getBooksList() .subscribe(books => this.booksList = books);
[ 135 ]
Building a Book Store Application } getBookInfo(id: number) { this.bookStoreService.getBook(id) .subscribe(book => this.book = book); } }
IEn la siguiente sección, aprenderá sobre el routing y utilizaremos una aplicación de ejemplo de la Tienda de libros. Al final del Capítulo 3, Componentes, Servicios e Inyección de Dependencia, creamos una aplicación de detalles maestros. La aplicación de ejemplo se recrea usando Material Design Lite para el estilo, el cliente HTTP para obtener los datos, y está disponible en la carpeta Chapter6/start en el código fuente provisto. Podemos utilizar la aplicación en la carpeta Chapter6/start como punto de partida para seguir lo que queda de este capítulo. Vamos a crear la carpeta llamada book-store y copiar todos los archivos y carpetas del directorio Chapter6/start. Ejecute los siguientes comandos en la raíz de la carpeta book-store antes de comenzar con el enrutamiento: $ npm install $ npm run json-server $ npm start
Navegue a http://localhost:4200en el navegador para ver la aplicación Book Store.
[ 136 ]
Building a Book Store Application
Obtenga más información sobre Material Design Lite en: https://getmdl.io.
Routing En los capítulos anteriores, aprendió diferentes conceptos en Angular para construir aplicaciones. Todos nuestros ejemplos contienen un máximo de dos componentes. Cualquier aplicación en el mundo real contiene muchos componentes; deberíamos poder navegar entre las diferentes páginas/componentes en la aplicación, pasar los datos de un componente a otro y actualizar múltiples componentes en el mismo árbol de componentes. Angular viene con su propio enrutador, que está disponible en el paquete @angular/ router.
[ 137 ]
Building a Book Store Application
Definiendo rutas Para comenzar con router, debemos seguir estos pasos: Establecer la base href Importar el RouterModule en AppModule Definir el array routes con el objeto Routes Agregue los routes al array de importación utilizando el método RouterModule.forRoot() index.html
El navegador utiliza el valor href para prefijar URL relativas al hacer referencia a CSS, JS y archivos de imagen. Aquí hay un ejemplo de href:
El código para src/app/app.module.tses el siguiente: import import import import import
{ { { { {
BrowserModule } from '@angular/platform-browser'; NgModule } from '@angular/core'; ReactiveFormsModule } from '@angular/forms'; HttpModule } from '@angular/http'; RouterModule, Routes } from '@angular/router';
Building a Book Store Application declarations: [ AppComponent, AboutComponent, BooksListComponent, BookDetailsComponent, NewBookComponent, Safe ], imports: [ BrowserModule, ReactiveFormsModule, HttpModule, RouterModule.forRoot(routes) ], providers: [BookStoreService], bootstrap: [AppComponent] }) export class AppModule { }
Anteriormente en AppModule, definimos nuestro array routes usando el objeto Routes. Cada ruta especifica el estado del enrutador actual. El objeto Routes tiene muchas propiedades, y estamos usando algunas de ellas para definir rutas para la aplicación Book Store. La explicación para los diferentes tipos de rutas que especificamos son las siguientes: {path: '', redirectTo: 'books', pathMatch: 'full'}
Si miramos nuestra primer route, la propiedad path está vacía; especificamos la propiedad redirectTo. Cada vez que iniciemos la aplicación comenzamos con /, redireccionará a la ruta del books y mostrará su componente correspondiente: {path: 'books', component: BooksListComponent}
Nuestro segundo path es muy simple; siempre que la ruta sea books mostrará el BooksListComponent: {path: 'books/:id', component: BookDetailsComponent}
Nuestro cuarto path es un poco diferente; tiene dos segmentos El primer segmento es books, y es una simple coincidencia de cadenas. El segundo segmento es :id y especifica el parámetro para la ruta. Anteriormente, en nuestros routes para diferentes paths, especificamos BooksListComponent, NewBookComponent, y AboutComponent, pero no hemos creado estos componentes en nuestra aplicación. También necesitamos un marcador de posición en nuestra aplicación para mostrar estos componentes.
Directiva RouterOutlet La directiva RouterOutlet como un marcador de posición donde Angular puede mostrar dinámicamente los componentes en función del estado del enrutador actual.
[ 139 ]
Building a Book Store Application
En nuestra aplicación, hasta ahora estamos mostrando todo en AppComponent. Utilizaremos AppComponent como marcador de posición para mostrar los elementos comunes y otros componentes basados en las rutas. Tenemos un encabezado y un menú del lado izquierdo, y ambos son comunes en toda la aplicación. Los mantendremos tal como están en AppComponent, y el espacio restante mostrará los otros componentes utilizando la directiva RouterOutlet. El código para src/app/app.component.tses el siguiente: import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { }
Eliminamos toda la lógica de AppComponent porque va a actuar como marcador de posición. Si miramos la plantilla aquí, eliminamos todo el código debajo de la etiqueta main> y agregamos , debajo de la etiqueta para mostrar los otros componentes: El código para src/app/app.component.htmles el siguiente:
El código restante anterior se quita para más legibilidad; podemos encontrar el código completo en el código fuente provisto. Vamos a crear el BooksListComponent para mostrar la lista de libros: El código para src/app/books/books-list/books-list.component.tses el siguiente: import { Component, OnInit } from '@angular/core'; import { Book } from '../book'; import { BookStoreService } from '../book-store.service'; @Component({ selector: 'books-list', templateUrl: './books-list.component.html', styleUrls: ['./books-list.component.scss'] }) export class BooksListComponent implements OnInit {
[ 140 ]
Building a Book Store Application booksList: Book[]; constructor(private storeService: BookStoreService) { } ngOnInit() { this.getBooksList(); } getBooksList() { this.storeService.getBooks() }
.subscribe(books => this.booksList = books);
}
El componente anterior recupera la lista de libros de BookStoreService. Tan pronto como se cargue la aplicación, el enrutador redireccionará a BooksListComponent y mostrará la lista de libros. Cuando hagamos clic en el enlace VIEW BOOK, navegaremos a BookDetailsComponentpara mostrar la información de un libro en particular:
[ 141 ]
Building a Book Store Application
Nombre RouterOutlet Podemos utilizar outlets con nombres para cargar múltiples componentes uno al lado del otro en lugar de anidarlos. Los outlets con nombre se crean especificando el atributo de nombre en la directiva RouterOutlet. Podemos tener un outlets principal (outlets sin nombre), como muchos outlets nombrados:
Especificamos el outlet de destino mientras definimos la ruta en sí o mientras navegamos hacia la ruta de manera imperativa o declarativa.
Navegación El enrutador angular proporciona dos formas de navegar de un componente a otro. La forma declarativa utilizando la directiva RouterLink es la siguiente: Add Book
Podemos especificar la ruta de la directiva routerLink como una cadena, y también podemos generar la ruta dinámicamente vinculándola a un array usando el enlace de propiedad: View Book
Anteriormente, se usaron dos fragmentos de código en la plantilla de BooksListComponentpara la navegación. También podemos navegar de un componente a otros componentes de imperativa los métodos y navigateByUrl() en forma el objeto Router; usando los utilizaremos en elnavigate() siguiente componente (BookDetailsComponent) para volver a BooksListComponent.
Parámetros de ruta Podemos pasar valores cuando navegamos de un componente a otro. En nuestro ejemplo, estamos pasando el valor id de BooksListComponent a BookDetailsComponent. Podemos acceder a los parámetros de ruta utilizando la propiedad Params del objeto ActivatedRoute:
[ 142 ]
Building a Book Store Application
El código para src/app/books/book-details/book-details.component.tses el siguiente: import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { Location } from '@angular/common'; import 'rxjs/add/operator/switchMap'; import { BookStoreService } from '../book-store.service'; import { Book } from '../book'; @Component({ selector: 'book-details', templateUrl: './book-details.component.html', styleUrls: ['./book-details.component.scss'] }) export class BookDetailsComponent implements OnInit { book: Book; constructor(private private private private }
Tan pronto como se inicialice el componente, usaremos ActivatedRoute para acceder a los parámetros de ruta usando la propiedad Params, que es un Observable. Estamos usando el operador switchMap() en los Params Observable para recibir los últimos parámetros, y luego invocamos el BookStoreService y pasamos el id como parámetro al método getBook() usando +params['id'].
Cuando estamos en el componente, si los parámetros de ruta cambian, el enrutador no necesita reactivar el componente completo porque Params es un Observable, y recibirá los nuevos valores y los emitirá. El operador switchMap() siempre se suscribe al último Observable, y siempre usará los valores más recientes y ejecutará el código. En nuestro caso, obtiene los datos del servicio utilizando el parámetro id. Tenemos un método deleteBook() en el componente que invoca el método deleteBook() en BookStoreService. Tan pronto recibamos la respuesta del servicio, usamos el método navigate() del objeto Router para volver a BooksListComponent. También estamos utilizando el método back() del objeto Location para volver a la ruta anterior; El objeto Location utiliza el historial del navegador para navegar hacia atrás y hacia adelante.
[ 144 ]
Building a Book Store Application
Aquí está la implementación de NewBookComponent usando formularios reactivos: El código para src/app/books/new-book/new-book.component.tses el siguiente: import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { Location } from '@angular/common'; import { Book } from '../book'; import { BookStoreService } from '../book-store.service'; @Component({ selector: 'new-book', templateUrl: './new-book.component.html', styleUrls: ['./new-book.component.scss'] }) export class NewBookComponent implements OnInit { newBookForm: FormGroup; constructor(private private private private }
El código para src/app/books/new-book/new-book.component.htmles el siguiente:
Add Book
ISBN
Book Title
Authors
Published
Description
[ 146 ]
Building a Book Store Application Cover Image
Aquí está el resultado:
[ 147 ]
Building a Book Store Application
Animar componentes enrutados El movimiento agrega más vida a la interfaz de usuario cuando se implementa con cuidado. Las animaciones nos permiten agregar diferentes tipos de movimientos a las aplicaciones para hacer que la IU sea más atractiva. Angular implementó un sistema de animación en la parte superior, Web Animations API, y nos permite crear animaciones que se ejecutan en el rendimiento nativo, como las animaciones de CSS puro. Los navegadores que no son compatibles con la API de Web Animations aún necesitan el polyfill web-animations.min.js. Para obtener más información sobre la API de Web Animations, visite https://w3c.github.io/web-animations . El archivo polyfill web-animations.min.js se puede descargar en https://github.com/web-animations/web-animations -js. En esta sección, vamos a aprender cómo animar mientras navegas entre los componentes. Primero, veamos cómo agregar el AnimationsModule a AppModule. El código para src/app/app.module.tses el siguiente: import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; ... @NgModule({ ... imports: [ ... BrowserAnimationsModule, RouterModule.forRoot(routes) ] ... }) export class AppModule { }
Ahora definiremos animaciones. El código para src/app/animations.tses el siguiente: import { animate, state, style, transition, trigger, AnimationTriggerMetadata } from '@angular/animations'; export const slideInOutAnimation: AnimationTriggerMetadata = trigger('routeAnimation', [ state('*',
[ 148 ]
Building a Book Store Application style({ opacity: 1, transform: 'translateX(0)' }) ), transition(':enter', [ style({ opacity: 0, transform: 'translateX(-100%)' }), animate('0.2s ease-in') ]), transition(':leave', [ animate('0.4s ease-out', style({ opacity: 0, transform: 'translateX(100%)' })) ])
Usamos los siguientes métodos para definir animaciones: Esto crea un disparador de animación con una lista de estados y transición Esto declara un estado de animación dentro del disparador dado; estamos usando el * en nuestro código, y coincide con cualquier estado de animación style(): Esto toma un par de clave/valor de pares de propiedad/valor de CSS transition(): Esto declara pasos de animación trigger(): state():
Estamos creando animaciones para el componente al entrar y salir del estado de la ruta. Al ingresar, nuestro componente se anima de izquierda a derecha, mientras se va, se anima de derecha a izquierda. Después de definir animaciones, agregue animaciones al componente: El código para src/app/books/books-list.component.tses el siguiente: import { Component, HostBinding, OnInit } from '@angular/core'; import { slideInOutAnimation } from '../../animations'; @Component({ ... animations: [slideInOutAnimation] }) export class BookDetailsComponent implements OnInit { ...
[ 149 ]
Building a Book Store Application @HostBinding('@routeAnimation') routeAnimation = true; @HostBinding('style.display') display = 'block'; @HostBinding('style.position') position = 'absolute'; ... }
Importamos la animación definida en el paso anterior y la agregamos al array animations en el decorador @Component() y accedemos al desencadenador de animación y estilos utilizando el decorador @HostBinding(). ,
Podemos seguir los pasos anteriores para agregar la animación a cualquier componente de nuestro ejemplo.
Módulos de características usando @NgModule () A medida que el número de componentes aumenta en la aplicación, se vuelve complejo, y debemos segregar nuestros componentes en diferentes módulos en función de su funcionalidad para gestionar la complejidad. Vamos a entender cómo usar @NgModule() para estructurar nuestros componentes de aplicaciones en módulos de características. Tenemos solo un módulo en nuestra aplicación; vamos a refactorizarlo para crear un módulo más. Tenemos muchos libros relacionados con la funcionalidad en nuestra aplicación, así que creemos un módulo separado para esto. En los módulos, necesitamos crear un módulo separado para las rutas, manteniendo nuestra clase de módulo de características limpio. Aquí está el módulo de enrutamiento de libros, que incluye todas las rutas relacionadas con libros. El código para src/app/books/books-routing.module.tses el siguiente: import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { BooksListComponent } from './books-list/books-list.component'; import { BookDetailsComponent } from './book-details/book-details.component'; import { NewBookComponent } from './new-book/new-book.component'; const routes: Routes = [ {path: 'books', component: BooksListComponent}, {path: 'books/new', component: NewBookComponent}, {path: 'books/:id', component: BookDetailsComponent} ];
[ 150 ]
Building a Book Store Application @NgModule({ imports: [ RouterModule.forChild(routes) ], exports: [ RouterModule ] }) export class BooksRoutingModule { }
En el módulo de enrutamiento de libros, al agregar las rutas al array de importaciones, usamos forChild() porque este será un componente secundario del módulo de aplicación principal. Vamos a crear el módulo de características de libros y agregar todos los componentes, servicios y y rutas relacionados. El código para src/app/books/books.module.tses el siguiente: import import import import
{ { { {
NgModule } from '@angular/core'; CommonModule } from '@angular/common'; ReactiveFormsModule } from '@angular/forms'; HttpModule } from '@angular/http';
import { BooksListComponent } from './books-list/books-list.component'; import { BookDetailsComponent } from './book-details/book-details.component'; import { NewBookComponent } from './new-book/new-book.component'; import { BookStoreService } from './book-store.service'; import { BooksRoutingModule } from './books-routing.module'; @NgModule({ declarations: [ BooksListComponent, BookDetailsComponent, NewBookComponent ], imports: [ CommonModule, ReactiveFormsModule, HttpModule, BooksRoutingModule ], providers: [BookStoreService] })
[ 151 ]
Building a Book Store Application export class BooksModule { }
Por ahora, tenemos un módulo de características independiente para los libros, y tenemos que añadir el AppModule principal antes de que nos permite definir un módulo de ruta separada para AppModule. El código para src/app/app-routing.module.tses el siguiente: import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { DashboardComponent } from './dashboard.component'; import { AboutComponent } from './about.component'; const routes: Routes = [ {path: '', redirectTo: 'dashboard', pathMatch: 'full'}, {path: 'dashboard', component: DashboardComponent}, {path: 'about', component: AboutComponent} ]; @NgModule({ imports: [ RouterModule.forRoot(routes) ], exports: [ RouterModule ] }) export class AppRoutingModule { }
Ahora tenemos que incluir AppRoutingModule y el módulo de características de libros en AppModule: El código para src/app/app.module.tses el siguiente: import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import import import import
{ { { {
AppComponent } from './app.component'; DashboardComponent } from './dashboard.component'; AboutComponent } from './about.component'; Safe } from './safe';
import { AppRoutingModule } from './app-routing.module'; import { BooksModule } from './books/books.module'; @NgModule({
[ 152 ]
Building a Book Store Application declarations: [ AppComponent, DashboardComponent, AboutComponent, Safe ], imports: [ BrowserModule, BooksModule, AppRoutingModule ], bootstrap: [AppComponent] }) export class AppModule { }
Si miramos el AppModule, parece diminuto y limpio ahora. Dependiendo del tamaño de la aplicación, creamos tantos módulos como necesitamos. Con las características, podemos usar las funciones como carga lenta y precarga para mejorar también el rendimiento de la aplicación. El código fuente para el ejemplo refactorizado está disponible en la carpeta Chapter6/book-storeextended.
Resumen Comenzamos este capítulo discutiendo cómo comunicarnos con los servicios REST usando un cliente HTTP, desarrollamos un servicio. ejemplo básico. refactorizamos todo el básicos código relacionado con elycliente HTTP a un Luego, Luego, aprendió sobre los conceptos de enrutamiento angular y luego implementamos todas las funciones en una aplicación de Book Store. Miramos cómo agregar animación a los componentes enrutados; finalmente, aprendió cómo refactorizar nuestra aplicación en módulos de caracteristicas. Al final de este capítulo, el lector debe tener una buena comprensión de cómo crear cualquier aplicación de interfaz de usuario con varias características angulares, como componentes, formularios, HTTP y enrutamiento. En el próximo capítulo, discutiremos cómo probar la aplicación Book Store que creamos en este capítulo.
[ 153 ]
Pruebas En este capítulo, aprenderá cómo probar aplicaciones angulares utilizando diferentes tipos de técnicas y herramientas de prueba. Veremos algunos ejemplos básicos y ejemplos del mundo real. Después de pasar por este capítulo, el lector comprenderá los siguientes conceptos: Pruebas unitarias y pruebas de extremo a extremo Cómo escribir pruebas unitarias aisladas e integradas Cómo probar componentes y servicios de unidad
Pruebas Las pruebas son uno de los aspectos importantes del desarrollo de aplicaciones, que garantiza que la aplicación funcione bien antes de implementarla para el uso del usuario final; ayuda a encontrar los errores de manera temprana y también asegura que no rompamos la funcionalidad existente a medida que agreguemos nuevas funciones a la aplicación. Es importante hacerlo parte del proceso de desarrollo en sí mismo. Existen diferentes tipos de procesos de desarrollo de software que se centran en las pruebas. El desarrollo basado en pruebas (TDD) es un tipo de técnica que enfatiza la escritura de las pruebas primero y luego la funcionalidad real; no nos sumergiremos más en TDD, que está más allá del alcance de este libro. Este capítulo se centra en las siguientes dos metodologías de pruebas principales utilizadas por los desarrolladores durante el desarrollo: Pruebas unitarias Prueba de extremo a extremo
Testing
Pruebas unitarias Las pruebas unitarias se centran en probar las partes individuales de las aplicaciones; por ejemplo, en la aplicación angular, tenemos la funcionalidad de prueba unitaria dentro de los componentes, servicios, directivas y pipes.
Prueba de extremo a extremo Las pruebas de extremo a extremo se centran en probar toda la aplicación, y estas pruebas se ejecutan en una aplicación que se ejecuta en un navegador real, interactuando con ella como lo haría un usuario en el mundo real. En este capítulo, cubrimos las pruebas unitarias, las pruebas de extremo a extremo están más allá del alcance de este libro. Antes de comenzar a escribir la prueba, veamos las herramientas requeridas para la prueba unitaria.
Herramientas Las siguientes son las herramientas requeridas para la prueba: Jasmine: Jasmine es un framework de desarrollo impulsado por el comportamiento para probar código JavaScript, puedes encontrar más información sobre Jasmine en https://jasmine.github.io Karma: Karma es un corredor de pruebas que usamos para ejecutar nuestra prueba unitaria durante el desarrollo; Puede encontrar más información sobre Karma en: http://karma-runner.github.io
Protractor: Protractor es un framework de prueba de extremo a extremo para aplicaciones angulares. Puede encontrar más información sobre Protractor en: http://protractortest.org
Archivos de configuración Los siguientes son los archivos de configuración de Karma: karma.conf.js: Este es el archivo de configuración de Karma que especifica qué plugins usar, qué aplicación y qué archivos de prueba cargar, qué navegador (s) usar y cómo informar los resultados de las pruebas. karma-test-shim.js: Esta es la cuña que hace que Karma trabaje con el entorno de prueba angular y lanza al mismo Karma; incluye algo de la configuración de SystemJS para cargar las herramientas de prueba Angular.
[ 155 ]
Testing
Conceptos básicos de Jasmine Antes de comenzar a escribir las pruebas unitarias, veamos algunas funciones de Jasmine que usamos para escribir cada prueba unitaria. describe(): La función de descripción es una función
global de Jasmine. Se utiliza para agrupar tipos similares de pruebas / especificaciones en un conjunto. Las funciones de descripción se pueden anidar. La sintaxis es la siguiente: describe('suite name', () => { //unit tests - it functions... });
it(): Es una función de Jasmine utilizada para escribir las pruebas unitarias
reales. La sintaxis es la siguiente: it('test name', () => { //unit test code });
Matchers: son las funciones incorporadas de Jasmine junto con la función expect() para comparar el valor real con el valor esperado. Estas son las funciones de emparejamiento proporcionadas por Jasmine: toBe() toEqual() toMatch() toBeDefined() toBeUndefined() toBeNull() toBeNaN() toBeTruthy() toBeFalsy() toHaveBeenCalled() toHaveBeenCalledWith() toHaveBeenCalledTimes() toContain() toBeLessThan() toBeLessThanOrEqual()
[ 156 ]
Testing toBeGreaterThan() toBeGreaterThanOrEqual() toBeCloseTo() toThrow() toThrowError() expect(): Es otra función de Jasmine que
toma un valor llamado valor real, y se usa junto con funciones de emparejamiento para afirmar el valor esperado. beforeEach(): beforeEach() es una función incorporada de Jasmine que ejecuta el código dentro de cada prueba en la función describe(). afterEach(): afterEach()es una función incorporada de Jasmine que ejecuta el código dentro de ella después de cada prueba en la función describe(). beforeAll(): beforeAll() es una función incorporada de Jasmine que ejecuta el código dentro de ella solo una vez antes de todas las pruebas en la función describe(). afterAll(): afterAll() es una función incorporada de Jasmine que ejecuta el código dentro de ella solo una vez después de que todas las pruebas completan la ejecución en la función describe().
Pruebas unitarias Podemos escribir dos tipos de pruebas unitarias para aplicaciones angulares: Pruebas unitarias aisladas Pruebas unitarias integradas Las pruebas unitarias aisladas ejemplifican la clase directamente dentro de las pruebas sin ninguna dependencia de Angular. Se utilizan para probar solo la lógica del componente (no la plantilla), y son adecuados para probar servicios, pipes y directivas. Las pruebas unitarias integradas se escriben usando clases de utilidad de prueba angular; se usan para probar escenarios más complejos que dependen de características angulares, como módulos y plantillas.
Pruebas unitarias aisladas En esta sección, aprenderá a usar algunas pruebas unitarias básicas aisladas. Vamos a usar el proyecto unit-testing-setup del código fuente provisto; esto es solo una aplicación a hello world en Angular. Instalaremos los paquetes npm Karma, Jasmine y configuraremos Karma para ejecutar nuestra prueba utilizando el framework Jasmine.
[ 157 ]
Testing
Vamos a crear un proyecto llamado 01-isolated-unit-testsdel proyecto unit-testingsetup. Primero, necesitamos instalar los paquetes npm y ejecutar los siguientes comandos en el proyecto: npm install jasmine-core jasmine --save-dev npm install karma karma-cli --save-dev npm install karma-jasmine karma-chrome-launcher
Ahora agregue el siguiente código a la sección de scripts en el archivo package.json para ejecutar Karma directamente usando el comando npm run: "karma": "karma start karma.conf.js", "pretest:once": "npm run build", "pretest": "npm run build", "test:once": "npm run karma -- --single-run", "test": "concurrently \"npm run build:watch\" \"npm run karma\""
Necesitamos incluir los archivos karma.conf.js y karma-test-shim.js en la raíz de nuestro proyecto. El siguiente es un archivo de configuración de Karma de ejemplo, que proporciona las instrucciones al corredor de prueba de Karma para el framework que queremos usar, los plugins necesarios para ejecutar las pruebas, qué archivos incluir en la prueba y qué excluir: module.exports = function (config) { var appSrcBase = 'src/'; var appAssets = '/base/app/'; config.set({ basePath: '', frameworks: ['jasmine'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher') ], client: { builtPaths: [appSrcBase] }, files: [], proxies: {}, exclude: [], preprocessors: {}, reporters: ['progress'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false, concurrency: Infinity
[ 158 ]
Testing }); }
Nuestros archivos srcinales karma.conf.js y karma-test-shim.js en el proyecto son muy largos; puedes encontrarlos en el código fuente proporcionado.
Escribir pruebas unitarias básicas aisladas Antes de la comenzar escribirunitaria pruebasalunitarias, nuestra configuración de prueba. Agregue siguientea prueba proyectoverifiquemos (src/app/app.component.spec.ts ): describe('my first unit test', () => { it('true is true', () => expect(true).toBe(true)); });
Ahora ve a la línea de comando, ejecuta el siguiente comando: npm run test:once
El comando anterior ejecutará la prueba que agregamos en el paso anterior. Si nuestra configuración está bien al final, obtendremos el mensaje Executed 1 of 1 SUCCESS y Karma dará por terminada la ejecución.
Antes de proceder, quiero discutir un poco sobre el nombre del archivo de prueba. Está utilizando el mismo nombre que el nombre del componente con el sufijo .spec. En el framework Jasmine, las pruebas se denominan specs; es una convención general sumar todos los archivos de prueba con .spec y usar el mismo nombre de archivo (componentes, servicios, directivas, pipe y rutas), que estamos probando. Una vez más, vaya a la línea de comando, y ejecute el siguiente comando: npm run test
El comando anterior ejecutará Karma en modo de vigilancia. Cada vez que hacemos un cambio en el código fuente o el código de prueba, Karma ejecutará automáticamente todas las pruebas unitarias nuevamente.
[ 159 ]
Testing
Vamos a escribir algunas pruebas unitarias para nuestro AppComponent: El código para src/app/app.component.spec.tses el siguiente: import { AppComponent } from './app.component'; describe('AppComponent', () => { it('name is initialized with Angular', () => { let component = new AppComponent(); expect(component.name).toBe('Angular'); }); it('name to be Angular UI', () => { let component = new AppComponent(); expect(component.name).toBe('Angular'); component.name = 'Angular UI'; expect(component.name).toBe('Angular UI'); }); });
Tenemos dos pruebas unitarias, una está verificando el valor inicial de la propiedad name en la clase AppComponent, y otra está verificando los cambios en la propiedad name. En ambas pruebas, estamos instanciando la clase AppComponent, que no es necesaria, que podemos usar el método beforeEach() en Jasmine framework para ejecutar el mismo código antes de cada prueba: El código para src/app/app.component.spec.tses el siguiente: import { AppComponent } from './app.component'; describe('AppComponent', () => { let component: AppComponent; beforeEach(() => { component = new AppComponent(); }); it('name is initialized with Angular', () => { expect(component.name).toBe('Angular'); }); it('name to be Angular UI', () => { expect(component.name).toBe('Angular');
Entendimos cómo escribir pruebas unitarias básicas, pero nuestra clase AppComponent no tiene ninguna funcionalidad real que podamos probar. Usemos el ejemplo de librería que desarrollamos en el capítulo anterior para que podamos entender cómo escribir algunas pruebas unitarias útiles. Puede usar la aplicación book-store-start en el código fuente del Chapter7\bookstore-start para comenzar. Esta aplicación se crea utilizando Angular CLI, por lo que ya tiene toda la configuración necesaria. Vamos a crear una aplicación book-store desde la aplicación book-store-start.
Prueba de Servicios En nuestra aplicación Book Store, tenemos BookStoreService, que se comunica con el servicio REST externo utilizando el servicio Angular HTTP para realizar diferentes operaciones en la lista de libros: El código para src/app/books/book-store.service.spec.tses el siguiente: import { BookStoreService } from './book-store.service'; describe('BookStoreService', () => { let bookStoreService: BookStoreService; beforeEach(() => { bookStoreService = new BookStoreService(); }); });
El fragmento de código precedente está incompleto. El constructor BookStoreService espera un objeto de servicio HTTP de angular como parámetro, y esto es necesario debido a los métodos en nuestro servicio que utilizan métodos HTTP, como get(), post(), y delete() para diferentes operaciones. Sin embargo, no debemos llamar al servicio REST real utilizando HTTP porque queremos probar nuestro comportamiento de servicio, no el servicio REST externo. En estos escenarios, debemos simular el objeto requerido, y esto se puede hacer simplemente utilizando el método jasmine.createSpyObj().
[ 161 ]
Testing
Dependencias Mocking Vamos a simular el servicio HTTP de angular utilizando el método jasmine.createSpyObj(): El código para src/app/books/book-store.service.spec.tses el siguiente: import { BookStoreService } from './book-store.service'; describe('BookStoreService', () => { let bookStoreService: BookStoreService, mockHttp; beforeEach(() => { mockHttp = jasmine.createSpyObj('mockHttp', ['get', 'post', 'delete' bookStoreService = new BookStoreService(mockHttp); }); });
El método jasmine.createSpyObj()toma el nombre del objeto simulado como el primer parámetro y los métodos del objeto simulado en el segundo parámetro como un array. Aquí está la prueba para el método deleteBook() en BookStoreService: it('deleteBook should remove the book', () => { const book: Book = { id: 12, isbn: 9781849692380, title: 'test title', authors: 'test author', published: 'test date', description: 'test description', coverImage: 'test image' }; mockHttp.delete.and.returnValue(Observable.of(book)); const response = bookStoreService.deleteBook(12); response.subscribe(value => { expect(value).toBe(book); });
El método deleteBook() devuelve el libro que eliminamos como un Observable y nos burlamos(mocking) de ese valor de retorno utilizando el método returnValue(). Estamos utilizando el método subscribe() para recibir los valores y comparar el valor de respuesta con el libro.
[ 162 ]
Testing
Vamos a escribir algunas pruebas unitarias más y verificar los parámetros pasados al método delete de HTTP. El código para src/app/books/book-store.service.spec.tses el siguiente: import import import import
{ BookStoreService } from './book-store.service'; { Observable } from 'rxjs/Observable'; 'rxjs/add/observable/of'; { Book } from './book';
Podemos probar los métodos restantes de manera similar. Veamos cómo probar los componentes usando pruebas unitarias aisladas.
Prueba de Componentes En nuestra aplicación Book Store, tenemos múltiples componentes. Veamos cómo probar BooksListComponent. La prueba del componente es muy similar a la forma en que probamos el servicio. El código para src/app/books/books-list/books-list.component.spec.tses el siguiente: import { BooksListComponent } from './books-list.component'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; describe('BooksListComponent', () => { let booksListComponent: BooksListComponent, mockBookStoreService; beforeEach(() => { mockBookStoreService = jasmine.createSpyObj( 'mockBookStoreService', ['getBooks' booksListComponent = new BooksListComponent(mockBookStoreService); }); it('initial books list should be empty', () => { expect(booksListComponent.booksList.length).toBe(0); }); describe('ngOnInit', () => { it('should fetch books list', () => { const books = [{}, {}]; expect(booksListComponent.booksList.length).toBe(0); mockBookStoreService.getBooks .and.returnValue(Observable.of(books)); booksListComponent.ngOnInit(); expect(booksListComponent.booksList.length).toBe(2); }); }); });
[ 164 ]
Testing
Nuestro BooksListComponent depende de BookStoreService, por lo que debemos simular esto. Tenemos una propiedad booksList, que está inicialmente vacía después de invocar el método ngOnInit(). La propiedad booksList puede cambiar; necesitamos probar este comportamiento En la aplicación, ngOnInit() se invoca como parte del ciclo de vida del componente; aquí, necesitamos invocarlo explícitamente.
Pruebas unitarias integradas Probar simples y lógica de métodos es suficiente la mayoría de las veces, perocon las tambiénenlaces queremos comprender cómo funciona nuestra lógica de componentes junto plantillas, los componentes secundarios y las rutas. Para hacer esto, las pruebas unitarias aisladas son suficientes. Probando un componente con una plantilla simple también podría ser complejo. Para esto, Angular proporciona utilidades de prueba en el módulo @angular/core/ testing. Estas clases de utilidad de prueba nos ayudan a probar nuestra aplicación cerca del entorno de tiempo de ejecución angular.
Prueba de Componentes Aquí está nuestro AboutComponent, que solo muestra los valores de las propiedades en la plantilla; este es el lugar correcto para comenzar a escribir algunas pruebas unitarias integradas: El código para src/app/about.component.tses el siguiente: import { Component } from '@angular/core'; @Component({ selector: 'about-page', template: `
{{heading}}
{{content}}
`, }) export class AboutComponent { heading = 'This is About Page'; content = ''; }
[ 165 ]
Testing
El código para src/app/about.component.integrated.spec.tses el siguiente: import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; import { AboutComponent } from './about.component'; describe('AboutComponent', () => { let component: AboutComponent; let fixture: ComponentFixture; let debugElement: DebugElement; let element: HTMLElement; beforeEach(() => { TestBed.configureTestingModule({ declarations: [AboutComponent] }); fixture = TestBed.createComponent(AboutComponent); component = fixture.componentInstance; debugElement = fixture.debugElement.query(By.css('h4')); element = debugElement.nativeElement; }); it('should display "This is About Page"', () => { fixture.detectChanges(); expect(element.textContent).toContain(component.heading); }); });
La prueba unitaria integrada anterior solo está verificando el valor del encabezado en AboutComponent. Es muy largo en comparación con las pruebas unitarias aisladas de manera similar, ya que estamos probando el entorno de tiempo de ejecución angular proximal. Vamos a entender la prueba línea por línea. Primero, importamos todas las clases de utilidad de prueba angular requeridas, luego el componente que necesitamos para la prueba unitaria: Esto crea el entorno para probar aplicaciones angulares (creando módulos angulares y componentes para pruebas). ComponentFixture: Este es un accesorio para probar los componentes; esto proporciona las propiedades y los métodos para acceder a la instancia del componente, los elementos DOM dentro de la plantilla del componente y ejecutar la detección de cambio de forma manual. TestBed:
DebugElement: Esto proporciona acceso al elemento raíz del componente. HTMLElement: Esto representa el elemento HTML DOM nativo.
[ 166 ]
Testing
Ahora podemos escribir las pruebas de unidades integradas usando los métodos de Jasmine describe(), beforeEach(), y it(). Primero, estamos configurando nuestro módulo de prueba utilizando el método configureTestingModule()en la clase TestBed, que es similar a @NgModule()y toma un objeto como un parámetro con las siguientes propiedades: providers, declarations, import, y schemas. Entonces, estamos creando el componente que devuelve un accesorio para acceder a la instancia del componente. Una vez que tenemos acceso a la instancia del componente; consultamos utilizando un elemento raíz utilizando el método query () de la clase DebugElement.
query(), necesitamos pasar un predicado; se pasa por un Para el el método selector de CSS usando método By.css() .
El método By.css coincide con los elementos por el selector CSS dado.
El método query () de la clase DebugElement devuelve el primer elemento que coincida con el selector, y podemos obtener todos los elementos utilizando el método queryAll(). La clase Por el módulo @angular/platform-browserproporciona dos métodos más para acceder a los elementos: By.all():
Esto coincide con todos los elementos Esto coincide con los elementos que tienen presente la directiva dada
By.directive():
Se accede al elemento DOM nativo utilizando la propieda nativeElement de DebugElement, con la cual podemos acceder a los elementos prueba y secundarios dentro de él. Finalmente, estamos comparando el valor del encabezado del componente con el texto en la plantilla. Sin embargo, hay algo interesante en nuestro método de prueba detectChanges(); Angular no ejecutará la detección de cambios automáticamente en el entorno de prueba, necesitamos usar el método detectChanges() cada vez que modifiquemos los datos. Agreguemos un par de pruebas más para diferentes escenarios. El código para src/app/about.component.integrated.spec.tses el siguiente: beforeEach(() => { TestBed.configureTestingModule({ declarations: [AboutComponent] });
describe('content', () => { beforeEach(() => { debugElement = fixture .debugElement.query(By.css('.message')); element = debugElement.nativeElement; }); it('should be empty', () => { fixture.detectChanges(); expect(element.textContent).toBe(component.content); }); it('should be "new message"', () => { component.content = 'new message'; fixture.detectChanges(); expect(element.textContent).toBe(component.content); }); });
[ 168 ]
Testing
En la segunda prueba, estamos verificando el valor del encabezado modificado, y en las pruebas tercera y cuarta, estamos probando los valores de propiedad de contenido. El AboutComponent tiene una plantilla en línea. ISi tiene una plantilla externa o hojas de estilo externas, las pruebas unitarias mencionadas anteriormente no funcionarán. Angular descarga estos archivos de forma asincrónica, pero nuestras pruebas unitarias se ejecutan de forma síncrona. Podemos usar el método async() en el módulo @angular/core/testing para manejar operaciones asíncronas en nuestras pruebas; aquí está la prueba unitaria de ejemplo de AboutComponent con una plantilla externa:: beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [AboutComponent] }); })); beforeEach(() => { fixture = TestBed.createComponent(AboutComponent); component = fixture.componentInstance; debugElement = fixture.debugElement.query(By.css('h4')); element = debugElement.nativeElement; });
Solo tenemos que ajustar la creación del módulo de prueba en el método async() en un beforeEach() por separado y el resto del código en diferentes bloques, dependiendo de la prueba unitaria. Necesitamos encadenar el método configureTestingModule().compileComponents()para compilarutilizando las plantillas y los archivos si estamosusa usando SystemJS. En nuestra aplicación, estamos Angular CLI que CSS internamente webpack para nosotros.
Prueba de componentes con dependencias Hasta ahora, hemos probado un componente simple con dos propiedades, pero los componentes se vuelven complejos con dependencias tales como servicios, otros componentes, componentes secundarios, rutas y formularios. Echemos un vistazo a BooksListComponent, que depende del BookStoreService, el método BookStoreService devuelve el observable como resultado, y nuestra plantilla utiliza directivas de enrutador. Ya sabemos cómo tratar con los servicios dependientes utilizando los métodos spy()de Jasmine. En la sección anterior, miramos cómo escribir pruebas unitarias integradas. Combinemos estos dos conceptos para probar el BooksListComponent:
[ 169 ]
Testing
El código para src/app/books/books-list/bookslist.component.integrated.spec.tses el siguiente: import { ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixtureAutoDetect } from '@angular/core/testing'; import { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; import { Observable } from 'rxjs/Observable'; import { BooksListComponent } from './books-list.component'; import { BookStoreService } from '../book-store.service'; import { Book } from '../book'; describe('BooksListComponent', () => { let fixture: ComponentFixture, component: BooksListComponent, debugElement: DebugElement, element: HTMLElement, mockBookStoreService; const booksList: Book[] = [{ id: 1, isbn: 9781783980628, title: 'Getting Started with Grunt', authors: 'Jaime Pillora', published: 'February 2014', description: 'JavaScript and Grunt.', coverImage: 'https://test.com/img1.png' }]; beforeEach(async(() => { mockBookStoreService = jasmine .createSpyObj('mockBookStoreService', ['getBooks' mockBookStoreService.getBooks .and.returnValue(Observable.of(booksList)); TestBed.configureTestingModule({ declarations: [ BooksListComponent ], providers: [ { provide: ComponentFixtureAutoDetect, useValue: true }, { provide: BookStoreService, useValue: mockBookStoreService }
Tenemos la configuración inicial para BooksListComponent. Nos burlamos(mocked ) del método getBooks() del método BookStoreService para devolver un Observable ficticio de libros. También configuramos ComponentFixtureAutoDetecten true, y esto ejecutará automáticamente la detección de cambio inicial en cada prueba. Aquí está nuestro primer valor de comprobación de prueba devuelto por el mockBookStoreService: it('should display books list', () => { debugElement = fixture.debugElement .query(By.css('.book-card')); element = debugElement.nativeElement.firstElementChild; expect(element.style.backgroundImage) .toContain(booksList[0].coverImage); });
Aquí está nuestra segunda prueba que prueba los valores de los componentes sin detección de cambios: it('should not display updated books list', () => { component.booksList = [{ id: 2, isbn: 9781786462084, title: 'Laravel 5.x Cookbook', authors: 'Alfred Nutile', published: 'September 2016', description: 'Laravel 5.x', coverImage: 'https://test.com/img2.png' }]; debugElement = fixture.debugElement .query(By.css('.book-card')); element = debugElement.nativeElement.firstElementChild; expect(element.style.backgroundImage) .toContain(booksList[0].coverImage); expect(element.style.backgroundImage) .not.toContain(component.booksList[0].coverImage); });
[ 171 ]
Testing
En la prueba mencionada anteriormente sin llamar al método detectChanges(), el componente aún utiliza los valores anteriores después de asignar booksList con el nuevo conjunto de libros. Aquí está nuestra tercera prueba para verificar los valores actualizados con la detección de cambios: it('should display updated books list', () => { component.booksList = [{ id: 2, isbn: 9781786462084, title: 'Laravel 5.x Cookbook', authors: 'Alfred Nutile', published: 'September 2016', description: 'Laravel 5.x', coverImage: 'https://test.com/img2.png' }]; fixture.detectChanges(); debugElement = fixture.debugElement .query(By.css('.book-card')); element = debugElement.nativeElement.firstElementChild; expect(element.style.backgroundImage) .toContain(component.booksList[0].coverImage); });
Resumen Comenzamos este capítulo discutiendo diferentes mecanismos de prueba y aprendimos por qué es necesario realizar pruebas. Examinamos las diferentes estrategias de pruebas untarias para probar el código Angular. Aprendió cómo escribir pruebas unitarias aisladas y pruebas unitarias integradas para varias partes (servicios, componentes, etc.) de la aplicación angular. Al final de este capítulo, un usuario debe tener una buena comprensión de cómo escribir las pruebas unitarias para aplicaciones angulares.
[ 172 ]
Angular Material En este capítulo, aprenderemos cómo desarrollar aplicaciónes visualmente atractivas utilizando componentes de Angular Material. Examinaremos diferentes controles UI proporcionados por Angular Material y cómo usarlos en varios escenarios. Después de pasar por este capítulo, el lector comprendera los siguientes conceptos: Diseño de material Cómo usar los componentes de diseño de Material
Introducción Angular Material es un conjunto de componentes UI de alta calidad desarrollado por el equipo de Angular, especificaciónes de de diseño Google Material. Estos componentes UI nosbasado ayudanena las construir una interfaz usuario única y atractiva que abarca varios dispositivos.
Empezando En este capítulo, aprenderá cómo usar los componentes UI proporcionados por Angular Material para compilar las aplicaciones. En lugar de mirar los controles individuales, vamos a desarrollar una aplicación completa usando estos componentes. En el En el Capítulo 6, Creación de una aplicación Book Store, creamos una aplicación de tienda de libros utilizando Material Design Lite, donde escribimos muchos códigos repetitivos para que nuestra aplicación se viera bien. Vamos a desarrollar la misma aplicación usando Angular Material; aprenda a cómo lograr una funcionalidad similar con menos código para hacer una aplicación más atractiva.
Angular Material
Material Design Lite también se basa solo en la especificación de diseño de Google Material; no se basa en ningún frameworks de JavaScript.
Configuración del proyecto Los siguientes son los pasos para incluir Angular Material en nuestra aplicación Book Store. Podemos usar la aplicación book-store-start bajo el código fuente del Chapter8 para comenzar con la configuración. Vamos primero a instalar Angular Material. El siguiente comando instalará Angular Material: npm install @angular/material --save
Ahora incluiremos animaciones angulares en AppModule. Algunos de los componentes de Angular Material dependen del módulo Angular animations para transiciones avanzadas. Vamos a instalarlo e incluirlo en nuestro proyecto. npm install @angular/animations --save
Importe en nuestro AppModule y agréguelo al array de importacion @NgModule(): import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @NgModule({ ... imports: [ BrowserModule, HttpModule, BrowserAnimationsModule ], ... }) export class AppModule { }
Ahora incluiremos un tema en el archivo index.html.
[ 174 ]
Angular Material
Deberíamos incluir un tema para todos los estilos de componentes de Material, bajo la carpeta node_modules/@angular/material/prebuilt-themes , tenemos los siguientes cuatro temas listos para usar: deeppurple-amber indigo-pink pink-bluegrey purple-green
Podemos incluir cualquiera de los temas anteriores, o podemos incluir nuestro tema personalizado también. Para nuestra aplicación, vamos a utilizar el tema indigo-pink, así que vamos a agregarlo a nuestro archivo index.html:
Agreguemos HammerJS para soporte de gestos. Algunos de los componentes de Angular Material como MdTooltip y MdSlider dependen de HammerJS para gestos. Necesitamos instalarlo e incluirlo en nuestro AppModule: npm install hammerjs --save
Importarlo en nuestro AppModule: import 'hammerjs';
La configuración de Angular Material para nuestra aplicación ya está hecha. Vamos a incluir también la fuente Roboto y los íconos de diseño de Material en nuestro index.html. Estos son opcionales, y podemos usar cualquier fuente o un conjunto diferente de íconos:
[ 175 ]
Angular Material
Uso de componentes de Angular Material Para comenzar con Angular Material primero, vamos a desarrollar una página de masterdetail. Para alojar esta página y otras páginas, necesitamos un diseño en nuestra aplicación. Vamos a usar CSS flexbox para diseñar nuestros diseños. En lugar de escribir mucho CSS a mano, el equipo Angular desarrolló un módulo denominado @angular/flex-layout, el módulo de diseño flexible proporcionó directivas para usar flexbox de forma declarativa en plantillas angulares. Necesitamos instalarlo e incluirlo en nuestro AppModule: npm install @angular/flex-layout --save
Agrégalo a AppModule: import { FlexLayoutModule } from '@angular/flex-layout'; @NgModule({ ... imports: [ BrowserModule, HttpModule, BrowserAnimationsModule, FlexLayoutModule ], ... })
Angular FlexLayoutModule se puede usar independientemente de Angular Material.
Para conocer flexbox, visite los siguientes enlaces: https://css-tricks.com/snippets/css/a-guide-to-flexbox/ https://github.com/angular/flex-layout/
Al momento de escribir este capítulo, Angular Material todavía está en beta 3, y las API podrían cambiar en el futuro. El código fuente provisto con el libro se actualizará para acomodar los últimos cambios en el framework.
[ 176 ]
Angular Material
Página Master-detail Vamos a crear la página master-detail para mostrar la lista de libros y la información del libro seleccionado de la lista de libros. El código para src/app/books/master-detail/master-detail.component.tses el siguiente: import { Component, OnInit } from '@angular/core'; import { Book } from '../book'; import { BookStoreService } from '../book-store.service'; @Component({ selector: 'bl-master-detail', styleUrls: ['./master-detail.component.scss'], templateUrl: './master-detail.component.html' }) export class MasterDetailComponent implements OnInit { booksList: Book[] = []; selectedBook: Book; constructor(private bookStoreService: BookStoreService) { } ngOnInit() { this.bookStoreService .getBooks() .subscribe(response => this.booksList = response); } }
El componente es exactamente igual al capítulo anterior. Todo el código relacionado con Material está en la plantilla. Como vamos a construir la página master-detail, necesitamos el contenedor de la izquierda para mostrar la lista de libros, el contenedor de la derecha para mostrar la información del libro seleccionado: Podemos usar el para el contenedor del lado izquierdo El y el contenido asociado viven dentro de un
Podemos usar un div para contenido asociado dentro de para mostrar el contenedor del lado derecho El muestra el sidenav lado a lado con el contenedor del lado derecho. En , necesitamos mostrar la lista de libros, podemos usar o :
[ 177 ]
Angular Material
El código para src/app/books/master-detail/master-detail.component.htmles el siguiente:
Books List Master Detail Page
{{book.title}}
{{book.authors}}
{{selectedBook.title}}
{{selectedBook.authors}}
{{selectedBook.published}}
ISBN: {{selectedBook.isbn}}
{{selectedBook.description}}
Una última cosa que tenemos que hacer para que nuestro componente funcione es, estamos usando componentes UI de Angular Material como , , y . Nuestra aplicación no tiene conocimiento de estos componentes, por lo que debemos importar e incluir sus respectivos módulos en el AppModule.
[ 178 ]
Angular Material
Vamos a agregar módulos de Material para separar el módulo e incluir ese módulo a AppModule, y esto mantiene nuestro AppModule más pequeño y más limpio: El código para src/app/app-material.module.tses el siguiente: import { NgModule } from '@angular/core'; import { MdSidenavModule, MdListModule } from '@angular/material'; const MATERIAL_MODULES = [ MdSidenavModule, MdListModule ]; @NgModule({ imports: MATERIAL_MODULES, exports: MATERIAL_MODULES }) export class AppMaterialModule { }
Cada vez que usemos un nuevo componente de Material, su módulo respectivo debería agregarse a AppMaterialModule: El código para src/app/app.module.tses el siguiente: import { AppMaterialModule } from './app-material.module'; @NgModule({ ... imports: [ BrowserModule, HttpModule, BrowserAnimationsModule, FlexLayoutModule, AppMaterialModule ] ... })
[ 179 ]
Angular Material
El siguiente es el resultado de nuestro MasterDetailComponent:
No podemos ver el resultado anterior todavía; para eso, necesitamos usar el selector de componentes en nuestra plantilla AppComponent. También necesitamos agregar MasterDetailComponenta nuestra matriz de declaraciones AppModule. En AppComponent, necesitamos un encabezado para mostrar el título de la aplicación y otras opciones; también queremos mostrar la navegación de la aplicación. Para el encabezado, podemos usar ; para la navegación, una vez más, use , . En AppComponent, vamos a usar para mostrar varios componentes según la selección de pestañas del usuario: El código para src/app/app.component.tses el siguiente: import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']
[ 180 ]
Angular Material }) export class AppComponent { links = [{ name: 'Books' }]; }
The code for src/app/app.component.htmlis as follows:
Book Store
{{link.name}}
LIST
En la plantilla anterior, el sidenav está oculto por defecto usando . El over mode muestra el sidenav en la parte superior de todos los elementos, y el resto de la pantalla se superpone. Estamos mostrando un ícono de menú en usando dentro de un botón que alterna el sidenav usando el método toggle() de . Lo invocamos utilizando la variable de referencia de la plantilla #sidenav.
[ 181 ]
Angular Material
También deberíamos incluir MdToolbarModule, MdButtonModule, MdIconModule, y MdTabsModule en AppMaterialModule. Inicie la aplicación utilizando el comando npm start, y podemos ver el siguiente resultado:
[ 182 ]
Angular Material
Hemos construido con éxito nuestros dos primeros componentes utilizando Angular Material. Vamos a crear una vista diferente para mostrar la lista de libros usando .
Página de lista de libros En esta página de lista de libros, vamos a mostrar libros en la interfaz de vista de lista; para eso, vamos a flexbox, y dentro usaremos para mostrar los libros: El código para src/app/books/list/list.component.tses el siguiente: import { Component, OnInit } from '@angular/core'; import { Book } from '../book'; import { BookStoreService } from '../book-store.service'; @Component({ selector: 'bl-list', styleUrls: ['./list.component.scss'], templateUrl: './list.component.html' }) export class ListComponent implements OnInit { booksList: Book[] = []; constructor(private bookStoreService: BookStoreService) { } ngOnInit() {
El componente mencionado anteriormente tiene lógica para obtener los libros y eliminar el libro utilizando el servicio. El código para src/app/books/list/list.component.htmles el siguiente:
0">
Books List Page
La tiene otras secciones como , , y para mostrar información adicional. Deberíamos incluir MdcardModule en AppMaterialModule y ListComponent en el array de declaraciones AppModule.
[ 184 ]
Angular Material
Ahora deberíamos usar el selector en la plantilla AppComponent dentro de para mostrar el componente:
Aquí está el resultado que podemos ver en el navegador:
Si hacemos clic en cualquiera de los botones DELETE, eliminará el libro y actualizará la página, y no mostrará ningún mensaje al usuario. Usemos el componente MdSnackBar para mostrar el mensaje cuando la eliminación de un libro es exitosa.
[ 185 ]
Angular Material
Para usar el componente MdSnackBar, debe ser inyectado en el constructor: import { MdSnackBar } from '@angular/material'; constructor( private bookStoreService: BookStoreService, private snackBar: MdSnackBar ) { }
Podemos usar el método openFromComponent() del método MdSnackBar o el método open() para mostrarlo:
Aquí está el resultado de usar el componente MdSnackBar:
[ 186 ]
Angular Material
IEn nuestros ejemplos, obtenemos la respuesta rápidamente y luego mostramos la salida de inmediato. Sin embargo, en los escenarios del mundo real, habrá una demora en obtener la respuesta, la pantalla estará en blanco y el usuario no entenderá lo que está sucediendo. Podemos usarpara mostrar el progreso hasta que recibamos la respuesta del servidor. Deberíamos incluir en la parte superior de nuestra plantilla; lo mostramos por defecto y lo ocultamos cuando recibimos la respuesta:
En el componente, estamos usando la propiedad spinnerVisibility para controlar la visibilidad del componente : spinnerVisibility = 'block'; getBooks() { this.bookStoreService .getBooks()
En nuestros ejemplos, es posible que no podamos ver la ruleta; para simular el retraso, usamos el operador RxJS delay() en el BookStoreService: El código para src/app/books/book-store.service.tses el siguiente: import 'rxjs/add/operator/delay'; getBooks(): Observable { const url = `${this.baseUrl}`; return this.http.get(url) .delay(5000) .map(response => response.json() as Book[ }
Ahora nuestro método getBooks() espera cinco segundos para devolver la respuesta. Deberíamos
incluir
MdProgressSpinnerModule,
AppMaterialModule.
[ 188 ]
MdSnackBarModule
en
Angular Material
Agregar diálogo de libro En esta sección, aprenderá cómo implementar un formulario utilizando controles de formulario de Angular Material y un cuadro de diálogo usando MdDialog: El código para src/app/books/add-book-dialog/add-book-dialog.component.ts es el siguiente: import { Component } from '@angular/core'; import { MdDialogRef } from '@angular/material'; @Component({ selector: 'add-book-dialog', styleUrls: ['./add-book-dialog.component.scss'], templateUrl: './add-book-dialog.component.html' }) export class AddBookDialogComponent { constructor(private dialogRef: MdDialogRef) {} }
En el componente anterior, estamos inyectando MdDialogRef en el constructor, que se puede usar para referirse al diálogo mismo. El código para src/app/books/add-book-dialog/add-book-dialog.component.html es el siguiente:
Add Book
[ 189 ]
Angular Material
En la plantilla anterior, estamos utilizando la API de formularios angulares para vincular controles de formulario y la directiva mdInput con la entrada de texto envuelta en para estilos de Material. Finalmente, estamos usando (ngSubmit)="dialogRef.close(form.value)" para cerrar el formulario y pasar el valor del formulario. Deberíamos incluir AddBookDialogComponenten el array declarations y FormsModule en el array imports en AppModule. Podemos usarun el botón componente anterior para mostrarpara el cuadro de el diálogo Agregar libro. manera: Agreguemos en la barra de herramientas mostrar diálogo, de la siguiente El código para src/app/app.component.htmles el siguiente: Book Store
En el componente, estamos inyectando MdDialog en el constructor; cada vez que el usuario haga clic en el botón Agregar en la barra de herramientas, invocará el método openAddBookDialog(). En el interior, estamos utilizando el método open() del método MdDialog para mostrar el cuadro de diálogo Agregar libro. Luego, estamos usando afterClosed() en MdDialog para obtener el valor pasado del evento dialogRef.close(form.value)en el formulario de agregar libro. Deberíamos incluir MdDialogModule y MdInputModule en AppMaterialModule y AddBookDialogComponenten los arrays entryComponents y declarations. Los cuadros de diálogo no se pueden resolver dinámicamente, por lo que debemos agregarlos a entryComponents. Aquí está el resultado de AddBookDialogComponent:
[ 192 ]
Angular Material
Formulario de registro de usuario Para conocer los controles restantes de Angular Material, construyamos el formulario de registro de usuario y cree una nueva aplicación desde la etapa actual de la aplicación Book Store, de la siguiente manera: El código para src/app/user-registration/user-registration.component.tses el siguiente: import { Component } from '@angular/core'; @Component({ selector: 'user-registration', templateUrl: './user-registration.component.html', styles: [` .user-registration-form { width: 60% } .gender-radio-group { display: inline-flex;
[ 193 ]
Angular Material flex-direction: row; } .gender-radio-button { margin: 5px; } `] }) export class UserRegistrationComponent { countries: Array
Report "Creación de Aplicaciones Web Modernas Usando Angular"