Anda di halaman 1dari 216

Creación de aplicaciones web

modernas usando Angular

Aprenda a crear aplicaciones web ricas y atractivas con


Angular

Shravan Kumar Kasagoni

BIRMINGHAM - MUMBAI
Creación de aplicaciones web modernas
usando Angular
Copyright © 2017 Packt Publishing
Todos los derechos reservados. Ninguna parte de este libro puede reproducirse,
almacenarse en un sistema de recuperación o transmitirse de ninguna forma ni por ningún
medio, sin el permiso previo por escrito del editor, excepto en el caso de citas breves
incluidas en artículos críticos o reseñas.
Se han hecho todos los esfuerzos posibles en la preparación de este libro para garantizar la
precisión de la información presentada. Sin embargo, la información contenida en este
libro se vende sin garantía, ya sea expresa o implícita. Ni el autor, ni Packt Publishing, ni
sus negociantes y distribuidores serán responsables de los daños causados o
supuestamente causados directa o indirectamente por este libro.

Packt Publishing se ha esforzado por proporcionar información sobre marcas


comerciales de todas las empresas y productos mencionados en este libro mediante
el uso adecuado de las mayúsculas. Sin embargo, Packt Publishing no puede
garantizar la exactitud de esta información.

Primera publicación: mayo de 2017

Production reference: 1240517


Published by Packt Publishing Ltd.
Livery Place
35 Livery Street
Birmingham
B3 2PB, UK.
ISBN 978-1-78588-072-8
www.packtpub.com
Créditos

Autor Editor de copia


Shravan Kumar Kasagoni Safis Editing
Dipti Mankame

Revisores Coordinador del proyecto


Hemant Singh Judie Jose
Phodal Huang

Editor de Adquisición Corrector


Tushar Gupta Safis Editing

Editor de desarrollo de contenido Indexador


Juliana Nair Rekha Nair

Editor técnico Gráficos


Mohd Riyan Khan Kirk D'Penha

Coordinador de produccion
Melwyn Dsa
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 tecnologías web modernas (HTML5, JavaScript y Node.js) y


frameworks (Angular, React.js, Knockout.js, etc.). También ha trabajado 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 de impresión, tiene derecho a un descuento en la copia de libro
electrónico. Póngase en contacto con nosotros en service@packtpub.com para obtener más
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 https://www.amazon.com/dp/1785880721.

Si desea unirse a nuestro equipo de revisores regulares, puede enviarnos un correo


electrónico a customerreviews@packtpub.com. 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 6
¿Qué hay de nuevo en Angular? 7
Configuración del entorno 7
Instalando Node.js y npm 8
Opciones de lenguaje 8
ECMAScript 5 9
ECMAScript 2015 9
TypeScript 9
Instalación de TypeScript 10
Conceptos básicos de TypeScript - tipos 10
String 11
Number 11
Boolean 11
Array 11
Enum 11
Any 12
Void 12
Functions 13
Declaración de función - función nombrada 14
Expresión de función - función anónima 14
Classes 14
Escribiendo tu primera aplicación Angular 16
Configurar la aplicación angular 16
Paso 1 16
Paso 2 18
Paso 4 19
Paso 5 20
Paso 6 21
Paso 7 22
Paso 8 22
SystemJS 23
Uso del componente angular 24
Comprender los paquetes npm 25
Paso 9 26
Paso 10 26
Uso de Angular CLI 28
Empezar conAngular CLI 28
Resumen 29
Capítulo 2: Conceptos básicos de los componentes 31
Empezando 31
Configuración del proyecto 31
Trabajando con datos 34
Visualización de datos 35
sintaxis de interpolación 35
Enlace de propiedad 37
Enlace de atributos 38
Enlace de eventos 38
Enlace de Datos bidireccionales 41
Directivas incorporadas 42
Directivas estructurales 42
ngIf 43
ngFor 43
La comprensión de la sintaxis ngFor 43
ngSwitch 44
Directivas de atributo 45
ngStyle 45
ngClass 46
Construyendo el componente master-detail 47
Resumen 53
Capítulo 3: Componentes, servicios e inyección de dependencias 54
Introducción 54
Trabajando con múltiples componentes 55
Propiedades Input 57
Propiedades Aliasing input 58
Propiedades Output 59
Propiedades Aliasing output 61
Compartir datos usando servicios 63
Inyección de dependencia 67
Usando un proveedor de clase 67
Usar un proveedor de clase con dependencias 68
Usar proveedores de clases alternativas 69
Usar proveedores de clase aliased 70
Resumen 71
Capítulo 4: Trabajando con Observables 72
Conceptos básicos de RxJS y Observables 72
Programación reactiva 72

[ ii ]
Observer 73
Observable 74
Subscription 76
Operators 76
Observables en Angular 77
Valores observables de stream y mapping 77
Fusionando Observables streams 78
Usando el método Observable.interval() 79
Usando AsyncPipe 81
Crear un componente de búsqueda de libros 82
Resumen 90
Capítulo 5: Manejo de formularios 91
¿Por qué son difíciles los formularios? 91
API de formularios en Angular 91
FormControl, FormGroup y FormArray 92
FormControl 92
Crear un control de formulario 92
Accediendo al valor de un control de entrada 93
Establecer el valor del control de entrada 93
Restablecer el valor de un control de entrada 93
Estados de control de entrada 93
FormGroup 94
FormArray 95
Formularios impulsados por plantillas 96
Crear un formulario de registro 96
Usando la directiva ngModel 99
Accediendo a un valor de control de entrada usando ngModel 100
Usar ngModel para enlazar un valor de cadena 101
Usar ngModel para enlazar una propiedad del componente 102
Uso de la directiva ngForm 105
Envío de un formulario usando el método ngSubmit 106
Usando la directiva ngModelGroup 110
Agregar validaciones al formulario de registro 112
Pros y contras de formularios basados en plantilla 116
formularios reactivos 117
Crear un formulario de registro usando formularios reactivos 117
Uso de FormGroup, FormControl y Validators 118
Usando [formGroup], formControlName, y formGroupName 119
Usando FormBuilder 121
CustomValidators 122
Pros y contras de formularios reactivos 125
Resumen 125

[ iii ]
Capítulo 6: Creación de una aplicación de tienda de libros 126
Aplicación de tienda de libros 126
HTTP 126
Hacer solicitudes GET 131
Routing 137
Definiendo rutas 137
Directiva RouterOutlet 139
Nombre RouterOutlet 142
Navegación 142
Parámetros de ruta 142
Animar componentes enrutados 148
Módulos de características usando @NgModule () 150
Resumen 153
Capítulo 7: Pruebas 154
Pruebas 154
Pruebas unitarias 155
Prueba de extremo a extremo 155
Herramientas 155
Archivos de configuración 155
Conceptos básicos de Jasmine 156
Prueba de unidad 157
Pruebas unitarias aisladas 157
Escribir pruebas unitarias básicas aisladas 159
Prueba de Servicios 161
Dependencias Mocking 162
Prueba de Componentes 164
Pruebas unitarias integradas 165
Prueba de Componentes 165
Prueba de componentes con dependencias 169
Resumen 172
Capítulo 8: Angular Material 173
Introducción 173
Empezando 173
Configuración del proyecto 174
Uso de componentes de Angular Material 176
Página Master-detail 176
Página de lista de libros 183
Agregar diálogo de libro 189
Formulario de registro de usuario 193

[ iv ]
Agregar temas 197
Resumen 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: '<h1>Say Hello to Angular</h1>'
})
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
feedback@packtpub.com 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. Coloque el puntero del mouse en la pestaña SOPORTE en la parte superior.
3. Haga clic en Descargas de código y errata.
4. Ingrese el nombre del libro en el cuadro de búsqueda.
5. Seleccione el libro para el que desea descargar los archivos de código.
6. Elija del menú desplegable donde compró este libro.
7. 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/PacktPublishing/Building-Modern-Web-Application-using-Angu
la r .
También tenemos otros paquetes de códigos de nuestro rico catálogo de libros y videos
disponibles en: https://github.com/PacktPublishing/. É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 el texto o el código, le estaríamos agradecidos si pudiera informarnos de esto. Al
hacerlo, puede salvar a otros lectores de la frustración y ayudarnos a mejorar las versiones
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 copyright@packtpub.com 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 questions@packtpub.com y haremos nuestro mejor esfuerzo para resolver
el problema.

[5]
Empezando
1
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 una actualización significativa del lenguaje
JavaScript; el código escrito en ES2015 es mucho más limpio 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 <filename.ts>

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<elementType>:


var fw: Array<string> = ['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<string> = ['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://typescriptlang.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 comenzar a escribir su primera aplicación Angular creando la siguiente estructura de
carpetas y archivos:
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 durante la compilación, o podemos pasar nuestros indicadores
de forma manual cada vez que 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/PacktPublishing/Building-Modern-Web-Applicatio
n - u s i n g - A n g u l a r . ambién tenemos otros paquetes de códigos de nuestro rico catálogo
de libros y videos disponibles en: h t t p s ://g i t h u b . c o m /P a c k t P u b l i s h i n g /. Échales
un vistazo!
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
moduleResolution: 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

[ 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:

dependencies: Esto contiene todos los paquetes necesarios para que la aplicación se ejecute
devDependencies: Esto contiene todos los paquetes necesarios solo para el desarrollo

[ 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-browser usando 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 original.

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: '<h1>Say Hello to Angular</h1>'
})
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: '<h1>Say Hello to Angular</h1>'
})
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: '<h1>Say Hello to Angular</h1>'
})
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';

platformBrowserDynamic().bootstrapModule(HelloWorldAppModule);

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: '<h1>Say Hello to Angular</h1>'
})
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:
<html>
<head>
<title>Angular Hello World</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,
initial-scale=1">

[ 22 ]
Getting Started

<script src="node_modules/core-js/client/shim.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>

<script>
System.config({
paths: {
'ng:': 'node_modules/@angular/'
},
map: {
'@angular/core': 'ng:core/bundles/core.umd.js',
'@angular/common': 'ng:common/bundles/common.umd.js',
'@angular/compiler': 'ng:compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'ng:platform-
browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'ng:platform-browser-
dynamic/bundles/platform-browser-dynamic.umd.js',
'rxjs': 'node_modules/rxjs'
},
packages: {
src: {
main: 'app',
defaultExtension: 'js'
},
rxjs: { defaultExtension: 'js' }
}
});

System.import('src').catch(function (err) {
console.error(err);
});
</script>
</head>
<body>
<hello-world-app>Loading...</hello-world-app>
</body>
</html>

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>

<script>
System.config({});
System.import('src');
</script>

¿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 platformBrowserDynamic del módulo
@angular/platform-browser-dynamic. El módulo @angular/core es un archivo físico
de JavaScript disponible en nuestra raíz de la aplicación en la ruta node_modules/
@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 <hello-world-app>, que hemos
declarado en HelloWorldAppComponent y mostrará su vista:

<body>
<hello-world-app>Loading...</hello-world-app>
</body>

[ 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

core-js: 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>.

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\" \"lite-server\"",
"build": "tsc"
}

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


<command>; Los comandos predefinidos de npm se pueden ejecutar usando npm
<command>.

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 Angular en 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/platform-
browser, @angular/platform-browser-dynamic; cargarán sus módulos dependientes.

La función platformBrowserDynamic() inicia nuestro HelloWorldAppModule, mientras


inicia el HelloWorldAppModule, agregará HelloWorldAppComponent en la propiedad
bootstrap a sus entryComponents.

Una vez que se haya agregado HelloWorldAppComponent a entryComponents, Angular


lo compilará y creará una instancia de ComponentFactory y la almacenará en la instancia
de ComponentFactoryResolver.

Finalmente, la etiqueta <hello-world-app> 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 ng g directive my-new-directive

Pipe ng g pipe my-new-pipe

Servicio ng g service my-new-service

Módulo ng g module my-module

Módulo con ng g module my-module --routing


enrutamiento

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
2
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

├─ index.html
├─ package.json
├─ src
│ ├─ app.component.ts
Basics of Components

│ ├─ app.module.ts
│ └─ main.ts
├─ systemjs-angular-loader.js
├─ systemjs.config.js
└─ tsconfig.json

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:

import { Component } from '@angular/core';

@Component({
selector: 'display-data-app',
template: '<h1>Data Binding in Angular -
Interpolation Syntax</h1>'
})
export class AppComponent {}

[ 32 ]
Basics of Components

El código para index.html es el siguiente:


<html>
<head>
<title>Data Binding in Angular - Interpolation Syntax</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,
initial-scale=1">

<script src="node_modules/core-js/client/shim.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>

<script src="systemjs.config.js"></script>
<script>
System.import('src').catch(function (err) {
console.error(err);
});
</script>
</head>
<body>
<display-data-app>Loading...</display-data-app>
</body>
</html>

Movimos nuestro código de configuración SystemJS a un archivo separado (systemjs.config.js)


desde index.html.

El código para systemjs.config.js es el siguiente:

(function (global) {
System.config({
paths: {
'ng:': 'node_modules/@angular/'
},
map: {
'@angular/core': 'ng:core/bundles/core.umd.js',
'@angular/common': 'ng:common/bundles/common.umd.js',
'@angular/compiler': 'ng:compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'ng:platform-
browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'ng:platform-browser-
dynamic/bundles/platform-browser-dynamic.umd.js',
'rxjs': 'node_modules/rxjs'
},
packages: {
src: {

[ 33 ]
Basics of Components

main: 'main',
defaultExtension: 'js',
meta: {
'./*.js': {
loader: 'systemjs-angular-loader.js'
}
}
},
rxjs: {
defaultExtension: 'js'
}
}
});
})(this);

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: '<h1>{{message}}</h1>'
})
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: `
<h1>{{message}}</h1>
<input type="text" value="{{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 <h1> 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 origen 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.


<element-name [element-property-name] = "component-property-name">

Sintaxis de enlace de propiedad:

element-name: Puede ser cualquier etiqueta HTML o etiqueta personalizada


element-property-name: 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

Aquí hay un ejemplo de enlace de propiedad en Angular: <img [src]="headerImage" />

En el fragmento de código anterior, estamos vinculando la propiedad headerImage de la


clase Component con el atributo src de la etiqueta <img />.

[ 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<img />, 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: `
<h1 [textContent]="message"></h1>
<input type="text" [value]="message"/>

En lugar de utilizar la sintaxis de interpolación, estamos ajustando la propiedad textContent


de la etiqueta <h1> 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 origen 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-:
<h1 bind-textContent ="message"></h1>

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:
<td [attr.colspan]="colSpanValue"></td>

[ 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: `
<h1>{{message}}</h1>
<input type="text" [value]="message"
(keypress)="showMessage()"/>
`

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.ts es el siguiente:


import { Component } from '@angular/core';

@Component({
selector: 'event-binding-app',
template: `
<p>{{message}}</p>
<input type="text" (keypress)="showMessage($event)"/>
`
})
export class AppComponent {
public message: string = 'Angular - Event Binding';

showMessage(onKeyPressEvent) {
this.message = onKeyPressEvent.target.value;
}
}

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.value para 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 <p> 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-:
<input type="text" on-keypress="showMessage($event)"/>

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: `
<p>{{message}}</p>
<input type="text" [(ngModel)]="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 <p>.

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:
<element *ngIf="condition"> content </element>

Si la condición es verdadera, Angular agregará contenido a DOM, si la condición es


falsa, eliminará físicamente ese contenido de DOM:
<div *ngIf="isReady">
<h1>Structural Directives</h1>
<p>They lets us modify DOM structure</p>
</div>

En el fragmento de código anterior, cuando el valor de isReady es verdadero, el contenido


dentro de la etiqueta <div> se representará en la página, siempre que sea falso, ambas
etiquetas dentro de la etiqueta <div> 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:
<ul>
<li *ngFor="let framework of frameworks">
{{framework}}
</li>
</ul>

El fragmento de código anterior mostrará la lista de nombres de framework en una lista


desordenada.
La comprensión de la sintaxis ngFor
<li *ngFor="let framework of frameworks">
{{framework}}
</li>

[ 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:

frameworks: Esta es un array y fuente de datos para la directiva ngFor sobre la


que se repetirá.
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:
<div [ngSwitch]="selectedCar">
<template [ngSwitchCase]="'Bugatti'">I am Bugatti</template>
<template [ngSwitchCase]="'Mustang'">I am Mustang</template>
<template [ngSwitchCase]="'Ferrari'">I am Ferrari</template>
<template ngSwitchDefault>I am somebody else</template>
</div>

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:
<p [ngStyle]="getInlineStyles(framework)">{{framework}}</p>

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:
<p [style.color]="framework.length > 3 ? 'red': 'green'" >
{{framework}}
</p>

[ 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:
<p [ngClass]="geClasses(framework)">{{framework}}</p>

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:

<p [class.red]="isThisRed">{{framework}}</p>

[ 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 master-
details. 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:


<html>
<head>
<title>Books List</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,
initial-scale=1">

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/
4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Roboto:
400,500" rel="stylesheet">
<link rel="stylesheet" href="styles.css"/>

<script src="node_modules/core-js/client/shim.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>

<script src="systemjs.config.js"></script>
<script>
System.import('src').catch(function (err) {
console.error(err);
});
</script>
</head>
<body>
<books-list>Loading...</books-list>
</body>
</html>

[ 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.ts es 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.html es el siguiente:


<h3 class="title">Books List</h3>
<div class="border-divider"></div>
<div class="row">
<div class="col-xs-3">Left</div>

[ 48 ]
Basics of Components

<div class="col-xs-9">Right</div>
</div>

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.ts es 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 AppComponent {
booksList: Book[] = 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):
<ul class="list">
<li class="list-item" *ngFor="let book of booksList">
<div class="cover-image-container">
<span class="cover-image">
<img [src]="book.coverImage" alt="cover image"/>
</span>
</div>
<div class="clear">
<h3 class="book-title">{{book.title}}</h3>
<h4 class="book-author">{{book.authors}}</h4>

[ 50 ]
Basics of Components

</div>
</li>
</ul>

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:


<li class="list-item" *ngFor="let book of booksList"
(click)="getBookDetails(book.isbn)">

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:
<div class="col-xs-9">
<div class="row selected-book">
<div class="col-xs-2">
<img [src]="selectedBook.coverImage" alt="cover image"/>
</div>
<div class="col-xs-8">
<h3 class="title">{{selectedBook.title}}</h3>
<p>{{selectedBook.authors}}</p>
<p>{{selectedBook.published}}</p>
<p>{{selectedBook.description}}</p>
</div>
</div>
</div>

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:
<div *ngIf="selectedBook" class="row selected-book">

[ 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
3
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 <div
class="col-xs-9"></div>, 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.ts debajo de la carpeta book-details. WVamos a
escribir todo el código dentro de las etiquetas <div class="col-xs-9"></div> en la
plantilla BookDetailsComponent.

[ 55 ]
Components, Services, and Dependency Injection

El código para src/book-details/book-details.component.ts es:


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.html es el siguiente:


<div *ngIf="book">
<div class="row selected-book">
<div class="col-xs-2">
<img [src]="book.coverImage" alt="cover image"/>
</div>
<div class="col-xs-8">
<h3 class="title">{{book.title}}</h3>
<p>{{book.authors}}</p>
<p>{{book.published}}</p>
<p>{{book.description}}</p>
</div>
</div>
<div class="row">
<div class="col-xs-10">
<button class="btn btn-danger float-xs-right mt-1">
Delete
</button>
</div>
</div>
</div>

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:
<book-details></book-details>

[ 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';

declarations: [AppComponent, BookDetailsComponent]

Es hora de usar BookDetailsComponent dentro de la plantilla AppComponent. Aquí está el


código app.component.html usando BookDetailsComponent declarativamente, usando
selector, <book-details> dentro de etiquetas <div class="col-xs-9"></div>.

El código para src/app.component.html es el siguiente:


<div class="col-xs-9">
<book-details></book-details>
</div>

Se supone que la etiqueta <book-details></book-details> 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 <book-details>.

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 BookDetailsComponent como 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.ts es 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 BookDetailsComponent está disponible para el


enlace de propiedades.

El código para src/app.component.html es el siguiente:


<div class="col-xs-9">
<book-details [book]="selectedBook"></book-details>
</div>

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 BookDetailsComponent reutilizable, 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 original 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:
<book-details [mybook]="selectedBook"></book-details>

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():
<button class="btn btn-danger float-xs-right mt-1"
(click)="deleteBook()">Delete
</button>

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, BookDetailsComponent no 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.ts es 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<number>();

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
<book-details> en el archivo de plantilla app.component.html:

<div class="col-xs-9">
<book-details [book]="selectedBook"
(onDelete)="deleteBook($event)">
</book-details>
</div>

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 original 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<number>();

[ 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:
<book-details (deleteMyBook)="deleteBook($event)">
</book-details>

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.ts es 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 en cualquier momento sin afectar los componentes que consumen un servicio,
siempre y cuando no cambiemos las firmas de 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.ts es el siguiente:


import { Component, OnInit } from '@angular/core';
import { Book } from './book';

[ 65 ]
Components, Services, and Dependency Injection

import { BookStoreService } from './book-store.service';

@Component({
selector: 'books-list',
templateUrl: 'src/app.component.html',
providers: [BookStoreService]
})
export class AppComponent implements OnInit {

booksList: Book[];
selectedBook: Book;

constructor(private bookStoreService: BookStoreService) { }

ngOnInit() {
this.getBooksList();
}

getBooksList() {
this.booksList = this.bookStoreService.getBooks();
}

getBookDetails(isbn: number) {
this.selectedBook = this.bookStoreService.getBook(isbn);
}

deleteBook(isbn: number) {
this.selectedBook = null;
this.booksList = this.bookStoreService.deleteBook(isbn);
}
}

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 {

[ 67 ]
Components, Services, and Dependency Injection

constructor(private bookStoreService: BookStoreService) {


}
}

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 ConsoleLoggerService que 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 dos aplicaciones de servicio diferentes que funcionan muy bien, algún día
podríamos decidir que todos los componentes antiguos también deberían usar el nuevo servicio
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 BookStoreGraphQLService utilizando 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
BookStoreGraphQLService en lugar de una, lo cual es innecesario. Podemos indicarle a
Angular que use la instancia BookStoreGraphQLService existente 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
4
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 Reactive-
Extensions 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<T> {
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:


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Manually creating an Observable</title>
</head>
<body>
<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>

<script src="https://unpkg.com/babel-standalone@6/babel.min.js">
</script>
<script src="https://unpkg.com/@reactivex/rxjs/dist/global/Rx.js">
</script>

</body>
</html>

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.ts es 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: `
<div class="container">
<input #text class="form-control mt-1"/>
<button #btn class="btn primary mt-1">Show Message!</button>
<p class="mt-1">{{message}}</p>
</div>
`
})
export class AppComponent implements OnInit {
@ViewChild('btn') btn;
@ViewChild('text') text;

[ 77 ]
Working with Observables

message: string;

ngOnInit() {
const btnOb$ = Observable
.fromEvent(this.btn.nativeElement, 'click');
btnOb$
.subscribe(res => this.message = 'Hello Angular, RxJS!');

const textOb$ = Observable


.fromEvent(this.text.nativeElement, 'change')
.map((event: Event) => (<HTMLInputElement>event.target).value);
textOb$.subscribe(res => this.message = res);
}
}

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.ts es el siguiente:


import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/map';

@Component({
selector: 'app-root',
template: `
<div class="container">

[ 79 ]
Working with Observables

<p class="mt-1">{{time}}</p>
</div>
`
})
export class AppComponent implements OnInit {
time: string;

ngOnInit() {
const timer$ = Observable.interval(1000)
.map(event => new Date());

timer$.subscribe(val => this.time = val.toString());


}
}

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 { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/map';

@Component({
selector: 'app-root',
template: `
<div class="container">
<h4 class="mt-1">{{timer$}}</h4>
</div>
`
})
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: `
<div class="container">
<h4 class="mt-1">{{timer$ | async}}</h4>
</div>
`

Now we get the same output as previously but without directly subscribing to the

template: `
<div class="container">
<h4 class="mt-1">
TIME: {{timer$ | async | date: 'mediumTime'}}
</h4>
</div>

[ 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 poder buscar y ver una lista de libros en función de la
entrada ingresada en el cuadro de búsqueda.

Todo el código fuente requerido para la configuración


está disponible chaper04/books-search en 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.ts es el siguiente:


import { Component, OnInit, ViewChild } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/map';

@Component({
moduleId: module.id,
selector: 'book-search',
styleUrls: ['./book-search.component.css'],
template: `
<h3 class="page-title">Books Search</h3>
<div class="search-container">
<div class="books-search-form">
<input type="text" #searchInput
class="search-input" placeholder="Book Title">
<button class="btn btn-primary">Search</button>
</div>
<ul>
<li *ngFor="let bookTitle of bookTitles">
{{bookTitle}}
</li>
</ul>
</div>

[ 82 ]
Working with Observables

`
})
export class BookSearchComponent implements OnInit {
@ViewChild('searchInput') searchInput;
bookTitles: Array<string>;

ngOnInit() {
Observable.fromEvent(this.searchInput.nativeElement, 'keyup')
.map((event: KeyboardEvent) =>
(<HTMLInputElement>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.ts es 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<Book[]> {


return Observable.of(this.filterBooks(title));
}

getBookTitles(title: string): Observable<string[]> {


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.ts es el siguiente:


ngOnInit() {
Observable.fromEvent(this.searchInput.nativeElement, 'keyup')
.map((event: KeyboardEvent) =>
(<HTMLInputElement>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.ts es el siguiente:
ngOnInit() {
Observable.fromEvent(this.searchInput.nativeElement, 'keyup')
.map((event: KeyboardEvent) =>
(<HTMLInputElement>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.ts es el siguiente:


ngOnInit() {
Observable.fromEvent(this.searchInput.nativeElement, 'keyup')
.debounceTime(400)
.distinctUntilChanged()
.map((event: KeyboardEvent) =>
(<HTMLInputElement>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.ts es el siguiente:


import { Component, OnInit, ViewChild } from '@angular/core';
import { Output, EventEmitter } from '@angular/core';
import { BookStoreService } from '../book-store.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';

@Component({
moduleId: module.id,
selector: 'book-search',
templateUrl: './book-search.component.html',
styleUrls: ['./book-search.component.css']
})
export class BookSearchComponent implements OnInit {
@ViewChild('searchInput') searchInput;
@ViewChild('suggestions') suggestions;
bookTitles: Array<string> = [];
searchInputTerm: string = '';

@Output() search = new EventEmitter<string>();

constructor(private bookStoreService: BookStoreService) {


}

ngOnInit() {
Observable.fromEvent(this.searchInput.nativeElement, 'keyup')
.debounceTime(400)
.distinctUntilChanged()
.map((event: KeyboardEvent) =>
(<HTMLInputElement>event.target).value)
.switchMap(title =>
this.bookStoreService.getBookTitles(title))
.subscribe(bookTitles => this.bookTitles = bookTitles);

Observable.fromEvent(this.suggestions.nativeElement, 'click')
.map((event: KeyboardEvent) =>
(<HTMLInputElement>event.srcElement).innerText)
.subscribe(res => {

[ 86 ]
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.html es el siguiente:


<h3 class="page-title">Books Search</h3>
<div class="search-container">
<div class="books-search-form">
<input type="text" #searchInput class="search-input"
placeholder="Book Title" [(ngModel)]="searchInputTerm">
<button class="btn btn-primary" (click)="searchBooks()">
Search
</button>
</div>
<div class="title-suggestion-list--wrapper">
<ul class="title-suggestion-list" #suggestions
[style.display]="bookTitles.length > 0 ? 'block' : 'none'">
<li *ngFor="let bookTitle of bookTitles">{{bookTitle}}</li>
</ul>
</div>
</div>

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.ts es el siguiente::


import { Component } from '@angular/core';
import { BookStoreService, Book } from './books/index';

@Component({
selector: 'app-root',
template: `
<div class="container">
<book-search (search)="searchBook($event)"></book-search>
<books-list [books]="filteredBooks"></books-list>
</div>
`,
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.ts es 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: `
<div class="row mt-1">
<div class="col-sm-12">
<div class="row">
<div class="col-sm-3 book-item"
*ngFor="let book of books">
<div class="cover-image-container">
<img [src]="book.coverImage" alt="cover image"/>
</div>
</div>
</div>

[ 88 ]
Working with Observables

</div>
</div>
`
})
export class BooksListComponent {
@Input() books: Book[] = [];
}

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
5
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 ng-pristine
El valor del control es válido ng-valid 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 (<form></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('shravan@theshravan.net')
]);

[ 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
│ ├─ app.component.ts
│ ├─ app.module.ts
│ ├─ main.ts
│ └─ registration-form
│ ├─ registration-form.component.html
│ └─ registration-form.component.ts
├─ styles.css
├─ systemjs-angular-loader.js
├─ systemjs.config.js
└─ tsconfig.json

Necesitamos agregar el código a package.json, tsconfig.json, systemjs-angular-


loader.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 './registration-
form/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
RegistrationFormComponent se 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.ts es el siguiente:


import { Component } from '@angular/core';

@Component({
selector: 'forms-app',
template: '<registration-form></registration-form>'
})
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
<registration-form que es el selector para RegistrationFormComponent. Además,
asegúrese de actualizar el selector <forms-app> dentro de las etiquetas <body> 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.ts es


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.html es el
siguiente:
<div class="row m-1">
<div class="col-md-8">
<div class="box">
<div class="box-header">
<h2>Registration Form</h2>
</div>
<div class="box-divider"></div>
<div class="box-body">
<div class="row">
<div class="col-sm-6 form-group">
<label>First name</label>
<input type="text" class="form-control">
</div>
<div class="col-sm-6 form-group">
<label>Last name</label>
<input type="text" class="form-control">
</div>
</div>
</div>
</div>
</div>
</div>

[ 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:

<input type="text" class="form-control" ngModel>

<input type="text" class="form-control" ngModel>

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:
<input type="text" class="form-control"
ngModel #firstNameRef="ngModel">

<input type="text" class="form-control"


ngModel #lastNameRef="ngModel">

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:
<input type="text" class="form-control"
ngModel #firstNameRef="ngModel">
{{firstNameRef}}

<input type="text" class="form-control"


ngModel #lastNameRef="ngModel">
{{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="<value>". 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:
<input type="text" class="form-control"
ngModel="Shravan" #firstNameRef="ngModel">
{{firstNameRef.value}}

<input type="text" class="form-control"


ngModel="Kasagoni" #lastNameRef="ngModel">
{{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.ts es 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.html es


el siguiente:

<input type="text" class="form-control"


ngModel="user.firstName"
#firstNameRef="ngModel">

{{firstNameRef.value}} <br/> {{user.firstName}}

<input type="text" class="form-control"


ngModel="user.lastName"
#lastNameRef="ngModel">
{{lastNameRef.value}} <br/> {{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="<value>", 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

<input type="text" class="form-control"


[ngModel]="user.firstName"
#firstNameRef="ngModel">

<input type="text" class="form-control"


[ngModel]="user.lastName"
#lastNameRef="ngModel">
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 los
valores en los controles de entrada, su enlace de interpolación se actualizará con lo que
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 <form>:
<div class="box-body">
<form novalidate>
<div class="row">
<div class="col-sm-6 form-group">
<label>First name</label>
<input type="text" class="form-control"
ngModel #firstNameRef="ngModel">
</div>
<div class="col-sm-6 form-group">
<label>Last name</label>
<input type="text" class="form-control"
ngModel #lastNameRef="ngModel">
</div>
</div>
<button type="submit"
class="btn btn-secondary">Submit</button>
</form>
</div>

[ 103 ]
Handling Forms

Agregamos una etiqueta <form> 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 <form>, 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:
<input type="text" class="form-control" name="firstName"
ngModel #firstNameRef="ngModel">

<input type="text" class="form-control" name="lastName"


ngModel #lastNameRef="ngModel">

[ 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:
<form novalidate #formRef="ngForm"></form>
<pre>{{formRef | json}}</pre>

El código entre las etiquetas <form></form> se elimina para su legibilidad. Podemos


usar las variables de referencia de la plantilla #formRef para acceder a los objetos del
modelo de formulario (FormGroup) , la variable #formRef, y la pipe JSON para mostrar
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:
<pre>{{formRef.value | json}}</pre>

[ 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 <form> como hemos


añadido la directiva ngModel en los controles de entrada. Esto se debe a que cada vez
que Angular encuentra una etiqueta <form> en la plantilla, activará y adjuntará
implícitamente la directiva ngForm a la etiqueta <form>, 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 <form>, podemos inhabilitar esta funcionalidad agregando la
directiva ngNoForm como un atributo a la etiqueta <form>.

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:


<form novalidate #formRef="ngForm"
(ngSubmit)="onSubmit(formRef.value)">
</form>

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:


<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" name="email"
ngModel #emailRef="ngModel">
</div>
<div class="row">
<div class="col-sm-6 form-group">
<label>Enter Password</label>
<input type="password" class="form-control"
name="password" ngModel #passwordRef="ngModel">
</div>

[ 107 ]
Handling Forms

<div class="col-sm-6 form-group">


<label>Confirm Password</label>
<input type="password"
class="form-control" name="confirmPassword"
ngModel #confirmPassRef="ngModel">
</div>
</div>
<div class="row">
<div class="col-sm-6 form-group">
<label>Street</label>
<input type="text" class="form-control" name="street"
ngModel #streetRef="ngModel">
</div>
<div class="col-sm-6 form-group">
<label>City</label>
<input type="text" class="form-control" name="city"
ngModel #cityRef="ngModel">
</div> </div>

Ahora agreguemos los campos street, city, state, zip, y country:


<div class="row">
<div class="col-sm-6 form-group">
<label>Street</label>
<input type="text" class="form-control" name="street"
ngModel #streetRef="ngModel">
</div>
<div class="col-sm-6 form-group">
<label>City</label>
<input type="text" class="form-control" name="city"
ngModel #cityRef="ngModel">
</div>
</div>
<div class="row">
<div class="col-sm-6 form-group">
<label>State</label>
<input type="text" class="form-control" name="state"
ngModel #stateRef="ngModel">
</div>
<div class="col-sm-6 form-group">
<label>Zip</label>
<input type="text" class="form-control" name="zip"
ngModel #zipRef="ngModel">
</div>
</div>
<div class="form-group">
<label>Country</label>
<select class="form-control" name="country"

[ 108 ]
Handling Forms

ngModel="" #countryRef="ngModel">
<option value="IN">India</option>
<option value="US">United States of America</option>
</select>
</div>

Añadamos campos de gender y service :


<div class="row">
<div class="col-sm-12 form-group">
<label>Gender</label>
<div>
<label class="check-label">
<input type="radio" name="gender" value="Male"
ngModel #genderRef="ngModel"><i class="blue"></i>Male
</label>
<div class="spacer"></div>
<label class="check-label">
<input type="radio" name="gender" value="Female"
ngModel #genderRef="ngModel"><i class="blue"></i>Female
</label>
<div class="spacer"></div>
<label class="check-label">
<input type="radio" name="gender" value="Other"
ngModel #genderRef="ngModel"><i class="blue"></i>Other
</label>
</div>
</div>
</div>
<div class="form-group">
<label class="check-label">
<input type="checkbox" name="agreement" value=""
ngModel #agreementRef="ngModel"><i class="blue"></i>
I agree to the Terms of Service
</label>
</div>

[ 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:
<div ngModelGroup="address" #addressRef="ngModelGroup">
<!-- street, city, state, zip and country fields code-->
</div>

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:

required: Marca un control para tener un valor no vacío


minlength: Aplica validación de longitud mínima
maxlength: Aplica validación de longitud máxima
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:
<input type="text" class="form-control" name="firstName"
ngModel #firstNameRef="ngModel" required>
<pre>{{firstNameRef?.errors | json}}</pre>
<pre>{{firstNameRef.valid}}</pre>
<pre>{{formRef.valid}}</pre>

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:


<input type="text" class="form-control" name="firstName"
ngModel #firstNameRef="ngModel" required
[class.ctrl-error] = "firstNameRef.touched &&
firstNameRef?.errors?.required">
<div *ngIf="firstNameRef.touched &&
firstNameRef?.errors?.required" class="error-message">
The first name is required.
</div>

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:
<input type="text" class="form-control" name="firstName"
ngModel #firstNameRef="ngModel" required minlength="3">

<div class="error-message" *ngIf="firstNameRef.touched &&


firstNameRef?.errors?.required">
The first name is required.
</div>

<div class="error-message" *ngIf="firstNameRef.touched &&


firstNameRef?.errors?.minlength">
You should enter minimum
{{firstNameRef?.errors?.minlength.requiredLength}}
characters into first name, but you entered only

[ 114 ]
Handling Forms

{{firstNameRef?.errors?.minlength.actualLength}} characters.
</div>

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:
<button type="submit" class="btn btn-secondary"
[disabled]="formRef.invalid">Submit</button>

[ 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,
formularios y aplicar reglas de validación dentro de la clase Component.

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.ts es el siguiente:


import { Component } from '@angular/core';

@Component({
selector: 'forms-app',
template: `<registration-reactive-form>
</registration-reactive-form>`
})
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/registration-


reactive-form.component.ts es 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 <form> utilizando el enlace [formGroup], y
FormControl a la etiqueta <input> utilizando la directiva formControlName:
<form novalidate [formGroup]="registrationForm"
(ngSubmit)="onSubmit(registrationForm.value)">
<input type="email" class="form-control"
formControlName="email">
<div class="error-message"

[ 119 ]
Handling Forms

*ngIf="registrationForm.get('email').touched &&
registrationForm.get('email').hasError('required')">
The email is required.
</div>
<div class="error-message"
*ngIf="registrationForm.get('email').touched &&
registrationForm.get('email').hasError('pattern')">
The email format should be <i>shravan@theshravan.net</i>
</div>
<button type="submit" class="btn btn-secondary"
[disabled]="registrationForm.invalid">Submit</button>
</form>

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:
<div formGroupName="address">
<div class="row">
<div class="col-sm-6 form-group">
<label>Street</label>
<input type="text" class="form-control"
formControlName="street">

[ 120 ]
Handling Forms

</div>
</div>
<div class="form-group">
<label>Country</label>
<select class="form-control" formControlName="country">
<option value="IN">India</option>
<option value="UK">United Kingdom</option>
</select>
<div class="error-message" *ngIf="
registrationForm.get('address').get('country')
.touched &&
registrationForm.get('address').get('country')
.hasError('required')">
The country is required.
</div>
</div>
</div>

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 novalidate [formGroup]="registrationForm"></
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: '',

[ 121 ]
Handling Forms

email: ['', [Validators.required,


Validators.pattern(this.EMAIL_REGEX)]],
address: this.formBuilder.group({
street: '',
city: ['', Validators.required],
state: ['', Validators.required],
zip: '',
country: ['', Validators.required]
})
});
}
}

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.ts es 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'):
<input type="password" class="form-control"
formControlName="password" >
<div class="error-message"
*ngIf="registrationForm.get('password').touched &&
registrationForm.get('password').hasError('required')">
The password is required.
</div>
<div class="error-message"
*ngIf="registrationForm.get('password').touched &&
registrationForm.get('password').hasError('weakPassword')">
The password must be minimum 8 characters, must contain at
least 1 lowercase alphabet, 1 uppercase alphabet, 1 numeric
character, 1 special character.
</div>

[ 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:

<form novalidate [formGroup]="registrationForm">


<!-Remain code is removed for readability-->
<input type="password" class="form-control"
formControlName="confirmPassword">
<div class="error-message" *ngIf="
registrationForm.get('confirmPassword').touched &&
registrationForm.get('confirmPassword').hasError('required')">
The confirm password is required.
</div>
<div class="error-message" *ngIf="
registrationForm.get('confirmPassword').touched &&
registrationForm.hasError('mismatch')">
The confirm password should match password.
</div>
</form>

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
6
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 / http-
client-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/json-
server.

[ 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).


<button (click)="getBooksData()">Get Books Data</button>
<pre>{{booksList | json}}</pre>

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.html es el siguiente:


<div>
<div class="left-container">
<ul>
<li *ngFor="let book of booksList"
(click)="getBookInfo(book.id)">
{{book.title}}
</li>
</ul>
</div>
<div *ngIf='book' class="right-container">
<p>{{book.isbn}}</p>
<p>{{book.title}}</p>
<p>{{book.authors}}</p>
<p>{{book.published}}</p>
<p>{{book.description}}</p>
<p>
<img [src]="book.coverImage" [alt]="book.isbn" />

[ 132 ]
Building a Book Store Application

</p>
</div>
</div>
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 real debería devolver solo la información requerida, y actualizamos
nuestro componente para llamar a la API y obtener la información específica del libro:

El código para src/app/app.component.ts es 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.ts es el siguiente:


import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { Book } from './book';

@Injectable()
export class BookStoreService {

baseUrl: string = 'http://localhost:4567';

constructor(private http: Http) { }

getBooksList(): Observable<Book[]> {

[ 134 ]
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<Book> {


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.ts es 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.ts es 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:4200 en 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:
<head>
<base href="/">
</head>

El código para src/app/app.module.ts es el siguiente:


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './app.component';


import { AboutComponent } from './about.component';

import {
BooksListComponent,
BookDetailsComponent,
NewBookComponent,
BookStoreService
} from './books';

import { Safe } from './safe';

const routes: Routes = [


{path: '', redirectTo: 'books', pathMatch: 'full'},
{path: 'books', component: BooksListComponent},
{path: 'books/new', component: NewBookComponent},
{path: 'books/:id', component: BookDetailsComponent},
{path: 'about', component: AboutComponent},
];

@NgModule({

[ 138 ]
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.ts es 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></
main> y agregamos <router-outlet></router-outlet>, debajo de la etiqueta <main>
para mostrar los otros componentes:

El código para src/app/app.component.html es el siguiente:


<main class="mdl-layout__content page-content">
<router-outlet></router-outlet>
</main>

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.ts es 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
BookDetailsComponent para 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:
<router-outlet></router-outlet>
<router-outlet name="secondary"></router-outlet>

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:
<a routerLink="/books/new">Add Book</a>

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:
<a [routerLink]="['/books', book.id]">View Book</a>

Anteriormente, se usaron dos fragmentos de código en la plantilla de


BooksListComponent para la navegación. También podemos navegar de un componente a
otros componentes de forma imperativa usando los métodos navigate() y
navigateByUrl() en el objeto Router; los utilizaremos en el 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.ts es 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 route: ActivatedRoute,


private router: Router,
private location: Location,
private storeService: BookStoreService) {
}

ngOnInit(): void {
this.route.params.switchMap((params: Params) =>
this.storeService.getBook(+params['id']))
.subscribe(book => this.book = book);
}

deleteBook(id: number) {
this.storeService.deleteBook(id)
.subscribe(res => this.router.navigate(['/books']));
}

goBack() {
this.location.back();
}
}

[ 143 ]
Building a Book Store Application

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.ts es 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 formBuilder: FormBuilder,


private router: Router,
private location: Location,
private storeService: BookStoreService) {
}

ngOnInit() {
this.newBookForm = this.formBuilder.group({
isbn: ['', Validators.required],
title: ['', Validators.required],
authors: ['', Validators.required],
published: ['', Validators.required],
description: ['', Validators.required],
coverImage: ['', Validators.required]
});
}
saveBook() {
if (this.newBookForm.valid) {
var book = this.newBookForm.value as Book;
this.storeService.addBook(book)
.subscribe(res => this.router.navigate(['/books']));
}
}
}

[ 145 ]
Building a Book Store Application

El código para src/app/books/new-book/new-book.component.html es el siguiente:


<section class="new-book-container">
<h4>Add Book</h4>
<form novalidate [formGroup]="newBookForm"
(ngSubmit)="saveBook()">
<div class="mdl-textfield mdl-js-textfield mdl-textfield-
floating-label">
<input class="mdl-textfield__input" type="text" id="isbn"
formControlName="isbn">
<label class="mdl-textfield__label"
for="isbn">ISBN</label>
</div>
<div class="mdl-textfield mdl-js-textfield mdl-textfield-
floating-label">
<input class="mdl-textfield__input" type="text"
id="title" formControlName="title">
<label class="mdl-textfield__label" for="title">
Book Title</label>
</div>
<br/>

<div class="mdl-textfield mdl-js-textfield mdl-textfield-


floating-label">
<input class="mdl-textfield__input" type="text"
id="authors" formControlName="authors">
<label class="mdl-textfield__label"
for="authors">Authors</label>
</div>
<div class="mdl-textfield mdl-js-textfield mdl-textfield-
floating-label ">
<input class="mdl-textfield__input" type="text"
id="published" formControlName="published">
<label class="mdl-textfield__label"
for="published">Published</label>
</div>
<br/>

<div class="mdl-textfield mdl-js-textfield mdl-textfield-


floating-label floating-label-full">
<input class="mdl-textfield__input" type="text"
id="description" formControlName="description">
<label class="mdl-textfield__label"
for="description">Description</label>
</div>
<br/>
<div class="mdl-textfield mdl-js-textfield mdl-textfield-
floating-label floating-label-full">

[ 146 ]
Building a Book Store Application

<input class="mdl-textfield__input" type="text"


id="coverImage" id="coverImage"
formControlName="coverImage">
<label class="mdl-textfield__label"
for="coverImage">Cover Image</label>
</div>
<br/>

<button type="submit" class="mdl-button mdl-js-button mdl-


button--raised mdl-js-ripple-effect mdl-button--colored"
[disabled]="newBookForm.invalid">
Save
</button>

<button type="submit" class="mdl-button mdl-js-button mdl-


button--raised mdl-js-ripple-effect mdl-button--accent"
(click)="this.location.back()">
Cancel
</button>
</form>
</section>

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.ts es 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.ts es 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:

trigger(): Esto crea un disparador de animación con una lista de estados y transición
state(): 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

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.ts es 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.ts es 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.ts es el siguiente:


import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { 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.ts es 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.ts es el siguiente:


import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';


import { DashboardComponent } from './dashboard.component';
import { AboutComponent } from './about.component';
import { 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-store-


extended.

Resumen
Comenzamos este capítulo discutiendo cómo comunicarnos con los servicios REST usando
un cliente HTTP, y desarrollamos un ejemplo básico. Luego, refactorizamos todo el código
relacionado con el cliente HTTP a un servicio. Luego, aprendió sobre los conceptos básicos
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
7
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-tests del proyecto unit-testing-


setup. 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 originales 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 comenzar a escribir pruebas unitarias, verifiquemos nuestra configuración de prueba.
Agregue la siguiente prueba unitaria al proyecto (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.ts es 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.ts es 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');

[ 160 ]
Testing

component.name = 'Angular UI';


expect(component.name).toBe('Angular UI');
});

});

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\book-
store-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.ts es 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.ts es 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.ts es el siguiente:
import { BookStoreService } from './book-store.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { Book } from './book';

describe('BookStoreService', () => {
let bookStoreService: BookStoreService,
mockHttp;

beforeEach(() => {
mockHttp = jasmine.createSpyObj('mockHttp', ['get', 'post', 'delete']);
bookStoreService = new BookStoreService(mockHttp);
});

describe('deleteBook', () => {

it('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);
});

it('should call http delete method with right url', () => {


const id = 12;
const url = `http://58e15045f7d7f41200261f77.mockapi.io/
api/v1/books/${id}`;
mockHttp.delete.and.returnValue(Observable.of(true));
const response = bookStoreService.deleteBook(id);
expect(mockHttp.delete).toHaveBeenCalledWith(url,
jasmine.any(Object));
});

});
});

[ 163 ]
Testing

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.ts es 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 enlaces simples y lógica de métodos es suficiente la mayoría de las veces, pero
también queremos comprender cómo funciona nuestra lógica de componentes junto con las
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.ts es el siguiente:


import { Component } from '@angular/core';

@Component({
selector: 'about-page',
template: `
<div>
<h4>{{heading}}</h4>
<p class="message">{{content}}</p>
</div>
`,
})
export class AboutComponent {
heading = 'This is About Page';
content = '';
}

[ 165 ]
Testing

El código para src/app/about.component.integrated.spec.ts es 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<AboutComponent>;
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:

TestBed: 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.
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. Para el método query(), necesitamos pasar un predicado; se pasa por un
selector de CSS usando el 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-browser proporciona dos métodos más para


acceder a los elementos:

By.all(): Esto coincide con todos los elementos


By.directive(): Esto coincide con los elementos que tienen presente la directiva dada

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.ts es el siguiente:


beforeEach(() => {
TestBed.configureTestingModule({
declarations: [AboutComponent]
});

fixture = TestBed.createComponent(AboutComponent);

[ 167 ]
Testing

component = fixture.componentInstance;
});

describe('heading', () => {
beforeEach(() => {
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);
});

it('should display "new heading"', () => {


fixture.detectChanges();

const previousHeading = component.heading;


component.heading = 'new heading';

expect(element.textContent).toContain(previousHeading);
expect(element.textContent)
.not.toContain(component.heading);

fixture.detectChanges();
expect(element.textContent).toContain(component.heading);
});
});

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


compilar las plantillas y los archivos CSS si estamos usando SystemJS. En nuestra aplicación,
estamos utilizando Angular CLI que internamente usa 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/books-


list.component.integrated.spec.ts es 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<BooksListComponent>,
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 }

[ 170 ]
Testing

]
});
}));

beforeEach(() => {
fixture = TestBed.createComponent(BooksListComponent);
component = fixture.componentInstance;
});
});

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 ComponentFixtureAutoDetect en 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
8
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, basado en las especificaciónes de diseño Google Material. Estos
componentes UI nos ayudan a construir una interfaz de 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:
<link href="../node_modules/@angular/material/
prebuilt-themes/indigo-pink.css" rel="stylesheet">

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:
<link rel="stylesheet" href="https://fonts.googleapis.com/css?
family=Roboto:300,400,500">

<link rel="stylesheet" href="https://fonts.googleapis.com/css?


family=Material+Icons">

[ 175 ]
Angular Material

Uso de componentes de Angular Material


Para comenzar con Angular Material primero, vamos a desarrollar una página de master-
detail. 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.ts es


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 <md-sidenav> para el contenedor del lado izquierdo
El <md-sidenav> y el contenido asociado viven dentro de un <md-
sidenav-container>
Podemos usar un div para contenido asociado dentro de <md-sidenav-
container> para mostrar el contenedor del lado derecho
El <md-sidenav mode="side"> muestra el sidenav lado a lado con el
contenedor del lado derecho.
En <md-sidenav>, necesitamos mostrar la lista de libros, podemos usar
<md-list> o <md-nav-list>:

[ 177 ]
Angular Material

El código para src/app/books/master-detail/master-detail.component.html es el


siguiente:
<div fxLayout="column" fxFlex>
<h2 class="page-title">Books List Master Detail Page</h2>
<md-sidenav-container flexLayout-="row" fxFlex
class="books-list">
<md-sidenav mode="side" opened>
<md-nav-list>
<md-list-item *ngFor="let book of booksList"
(click)="selectedBook = book">
<img md-list-avatar [src]="book.coverImage" />
<h2 md-line>{{book.title}}</h2>
<p md-line> {{book.authors}} </p>
</md-list-item>
</md-nav-list>
</md-sidenav>
<div class="books-list-item" fxFlex>
<div *ngIf="selectedBook" class="books-list-item--detail"
fxLayout="row" fxFlex.sm="column">
<div class="books-list-item--coverimage">
<img [src]="selectedBook.coverImage" />
</div>
<div class="books-list-item--content" fxFlex>
<h3>{{selectedBook.title}}</h3>
<p>{{selectedBook.authors}}</p>
<p>{{selectedBook.published}}</p>
<p>ISBN: {{selectedBook.isbn}}</p>
<p>{{selectedBook.description}}</p>
</div>
</div>
</div>
</md-sidenav-container>
</div>

Una última cosa que tenemos que hacer para que nuestro componente funcione es, estamos usando
componentes UI de Angular Material como <md-sidenav>, <md-sidenav-container>, y <md-nav-
list>. 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.ts es 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.ts es 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
MasterDetailComponent a 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 <md-toolbar>; para la navegación, una vez más, use
<md-sidenav>, <md-nav-list>. En AppComponent, vamos a usar <md-tab-group>
para mostrar varios componentes según la selección de pestañas del usuario:

El código para src/app/app.component.ts es 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.html is as follows:


<div fxLayout="column" fxFlex>
<md-toolbar color="primary">
<button md-icon-button (click)="sidenav.toggle()">
<md-icon>menu</md-icon>
</button>
<span>Book Store</span>
</md-toolbar>

<md-sidenav-container fxFlex>
<md-sidenav mode="over" #sidenav>
<md-nav-list>
<md-list-item *ngFor="let link of links">
<p>{{link.name}}</p>
</md-list-item>
</md-nav-list>
</md-sidenav>
<div class="content" fxLayout="column" fxFlex>
<md-tab-group>
<md-tab label="Master Detail">
<div fxFlex class="master-detail-container">
<bl-master-detail fxFlex></bl-master-detail>
</div>
</md-tab>
<md-tab label="List">
LIST
</md-tab>
</md-tab-group>
</div>
</md-sidenav-container>
</div>

En la plantilla anterior, el sidenav está oculto por defecto usando <md-sidenav mode="over">.
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 <md-toolbar> usando <md-icon>
dentro de un botón que alterna el sidenav usando el método toggle() de <md-sidenav>.
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
<md-card>.

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 <md-card> para mostrar los libros:

El código para src/app/books/list/list.component.ts es 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() {

[ 183 ]
Angular Material

this.getBooks();
}

getBooks() {
this.bookStoreService
.getBooks()
.subscribe(response => this.booksList = response);
}

deleteBook(id: number) {
this.booksList = this.booksList
.filter(book => book.id !== id);
this.bookStoreService.deleteBook(id)
.subscribe(result => this.getBooks());
}
}

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.html es el siguiente:


<div fxLayout="column" fxFlex *ngIf="booksList.length > 0">
<h2 class="page-title">Books List Page</h2>
<section fxLayout="row" fxLayoutWrap fxLayout.sm="column"
fxLayoutGap="24px" fxLayoutAlign="center">

<md-card class="book-card" *ngFor="let book of booksList">


<img md-card-image [src]="book.coverImage">
<md-card-actions>
<button md-button>DETAIL</button>
<button md-button (click)="deleteBook(book.id)">
DELETE
</button>
</md-card-actions>
</md-card>

</section>
</div>

La <md-card> tiene otras secciones como <md-card-title>, <md-card-subtitle>, y


<md-card-footer> 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 <bl-list> en la plantilla AppComponent dentro de <md-tab


label="List"></md-tab> para mostrar el componente:
<md-tab label="List">
<div fxFlex class="books-list-container">
<bl-list fxFlex></bl-list>
</div>
</md-tab>

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:

deleteBook(id: number) {
this.booksList = this.booksList
.filter(book => book.id !== id);
this.bookStoreService.deleteBook(id)
.subscribe(result => {
if (result.ok) {
this.openSnackBar();
}
this.getBooks();
});
}

openSnackBar() {
this.snackBar.open('Book Deleted', 'CLOSE', {
duration: 1000
});
}

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 usar<md-progress-spinner> para mostrar el progreso hasta que
recibamos la respuesta del servidor.
Deberíamos incluir <md-progress-spinner> en la parte superior de nuestra plantilla; lo
mostramos por defecto y lo ocultamos cuando recibimos la respuesta:
<md-progress-spinner color="accent" mode="indeterminate"
[style.display]="spinnerVisibility"
class="spinner">
</md-progress-spinner>

En el componente, estamos usando la propiedad spinnerVisibility para controlar la


visibilidad del componente <md-progress-spinner> :
spinnerVisibility = 'block';

getBooks() {
this.bookStoreService
.getBooks()

[ 187 ]
Angular Material

.subscribe(response => {
this.booksList = response;
this.spinnerVisibility = 'none';
});
}

Aquí está el resultado de usar el componente <md-progress-spinner> :

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.ts es el siguiente:


import 'rxjs/add/operator/delay';

getBooks(): Observable<Book[]> {
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, MdSnackBarModule en


AppMaterialModule.

[ 188 ]
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<AddBookDialogComponent>) {}
}

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:
<h3>Add Book</h3>
<form #form="ngForm" (ngSubmit)="dialogRef.close(form.value)"
ngNativeValidate>

<div fxLayout="column" fxLayoutGap="8px">


<md-input-container>
<input mdInput ngModel name="title"
placeholder="Book Title" required>
</md-input-container>
<md-input-container>
<input mdInput ngModel name="authors"
placeholder="Authors" required>
</md-input-container>
<md-input-container>
<input mdInput ngModel name="published"
placeholder="Published" required>
</md-input-container>
<md-input-container>
<input mdInput ngModel name="isbn"
placeholder="ISBN" required>

[ 189 ]
Angular Material

</md-input-container>
<md-input-container>
<input mdInput ngModel name="coverImage"
placeholder="Cover Image" required>
</md-input-container>
<md-input-container>
<textarea mdInput ngModel name="description"
placeholder="Description"
rows="3" cols="60" required>
</textarea>
</md-input-container>
</div>

<md-dialog-actions align="end">
<button md-button type="button"
(click)="dialogRef.close()">Cancel</button>
<button md-button color="accent">Save Book</button>
</md-dialog-actions>
</form>

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 <md-input-container> 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 AddBookDialogComponent en el array declarations y


FormsModule en el array imports en AppModule.

Podemos usar el componente anterior para mostrar el cuadro de diálogo Agregar libro.
Agreguemos un botón en la barra de herramientas para mostrar el diálogo, de la siguiente manera:

El código para src/app/app.component.html es el siguiente:


<md-toolbar color="primary">
<button md-icon-button (click)="sidenav.toggle()">
<md-icon>menu</md-icon>
</button>
<span>Book Store</span>
<span class="spacer"></span>
<button md-mini-fab (click)="openAddBookDialog()">
<md-icon>add</md-icon>
</button>
</md-toolbar>

[ 190 ]
Angular Material

El código para src/app/app.component.ts es el siguiente:


import { Component } from '@angular/core';
import { MdDialog, MdSnackBar } from '@angular/material';
import { AddBookDialogComponent, BookStoreService }
from './books';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
links = [{
name: 'Books'
}];

constructor(private dialog: MdDialog,


private snackBar: MdSnackBar,
private bookStoreService: BookStoreService) { }

openAddBookDialog() {
this.dialog.open(AddBookDialogComponent)
.afterClosed()
.filter(book => !!book)
.switchMap(book => this.bookStoreService.addBook(book))
.subscribe(result => {
if (result.ok) {
this.openSnackBar();
}
});
}

openSnackBar() {
this.snackBar.open('Book Added', 'CLOSE', {
duration: 1000
});
}
}

[ 191 ]
Angular Material

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


AddBookDialogComponent en 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.ts es


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<object> = [
{code: 'CA', name: 'Canada'},
{code: 'SW', name: 'Switzerland'},
{code: 'IN', name: 'India'},
{code: 'UK', name: 'United Kingdom'},
{code: 'US', name: 'Canada'}
];

genders: Array<string> = [
'Male',
'Female',
'Other'
];

submitUserForm(value: Object) {
console.log(value);
}
}

El código para src/app/user-registration/user-registration.component.html es el


siguiente:
<md-select name="country" placeholder="Country" ngModel
required>
<md-option *ngFor="let country of countries"
[value]="country.code">
{{ country.name }}
</md-option>
</md-select>
<md-radio-group class="gender-radio-group" ngModel
name="gender" class="m-t" required>
<md-radio-button class="gender-radio-button"
*ngFor="let gender of genders" [value]="gender">
{{gender}}
</md-radio-button>
</md-radio-group>
<md-slide-toggle ngModel name="isAdmin" class="m-t">
Admin User
</md-slide-toggle>
<md-checkbox name="agreement" ngModel class="m-t" required>

[ 194 ]
Angular Material

I agree to the Terms of Service


</md-checkbox>

Lo anterior es solo un fragmento de la plantilla UserRegistrationComponent; tel


fragmento de código muestra los nuevos controles, como <md-select>, <md-radio-
group>,<md-slide-toggle>, <md-checkbox>. Deberíamos incluir MdSelectModule,
MdRadioModule, MdCheckboxModule, y MdSlideToggleModule en AppMaterialModule
y UserRegistrationComponent en el array de declaraciones AppModule.

Para navegar al código anterior, debemos agregar el enrutamiento a nuestra aplicación.


Vamos a refactorizar nuestra aplicación. Ahora necesitamos un componente contenedor
para mostrar los componentes master-detail y books-list:

El código para src/app/books/books-container.component.ts es el siguiente:


import { Component } from '@angular/core';

@Component({
selector: 'books-container',
template: `
<md-tab-group>
<md-tab label="Master Detail">
<div fxFlex class="master-detail-container">
<bl-master-detail fxFlex></bl-master-detail>
</div>
</md-tab>
<md-tab label="List">
<div fxFlex class="books-list-container">
<bl-list fxFlex></bl-list>
</div>
</md-tab>
</md-tab-group>
`,
styles: [`
.master-detail-container {
height: calc(100vh - 113px);
overflow: hidden;
padding: 1rem;
}

.books-list-container {
height: 100%;
padding: 1rem;
overflow-x: hidden;
overflow-y: auto;
}
`]

[ 195 ]
Angular Material

})
export class BooksContainerComponent {
}

También deberíamos refactorizar nuestro AppComponent para mostrar los componentes dentro de él.
En la plantilla, estamos agregando <router-outlet> para mostrar los componentes basados en
las rutas seleccionadas:
<md-sidenav-container fxFlex>
<md-sidenav mode="over" #sidenav>
<md-nav-list>
<a md-list-item [href]="link.path"
*ngFor="let link of links">
{{ link.name }}
</a>
</md-nav-list>
</md-sidenav>
<div class="content" fxLayout="column" fxFlex>
<router-outlet></router-outlet>
</div>
</md-sidenav-container>

En la plantilla, necesitamos actualizar el array links:


links = [
{ name: 'Books', path: 'books' },
{ name: 'Registration', path: 'registration' }
];

Finalmente, debemos incluir las rutas en AppModule:

El código para src/app/app.module.ts es el siguiente:

import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [


{path: '', redirectTo: 'books', pathMatch: 'full'},
{path: 'books', component: BooksContainerComponent },
{path: 'registration', component: UserRegistrationComponent }
];

@NgModule({
...
imports: [
...,
RouterModule.forRoot(routes)
],

[ 196 ]
Angular Material

})
export class AppModule {
}

Aquí está el resultado del formulario de registro del usuario con el enrutamiento de la aplicación:

Agregar temas
Los temas permiten al usuario cambiar entre diferentes combinaciones de colores. Vamos a
incluir un archivo theme.scss en nuestra aplicación. Hasta ahora, hemos utilizado el
esquema de color indigo-pink; usemos el esquema de agregar color a la aplicación:

El código para src/theme.scss es el siguiente:


@import '~@angular/material/_theming';

@include mat-core();

$primary: mat-palette($mat-red);
$accent: mat-palette($mat-blue);

$theme: mat-light-theme($primary, $accent);

[ 197 ]
Angular Material

@include angular-material-theme($theme);

Añadimos el tema rojo-azul para la aplicación; podemos elegir cualquier paleta fuera de las
paletas de colores de diseño de Material (https://material.io/guidelines/style/color.html).

También debemos incluir el archivo theme.scss en el array de estilos del archivo angular-cli.json:
"styles": [
"styles.scss",
"theme.scss"
]

Después de agregar el archivo theme.scss, debemos reiniciar la aplicación para ver el

efecto del tema. Aquí está el resultado final:

[ 198 ]
Angular Material

Resumen
Comenzamos este capítulo con la introducción al diseño con Material, y vimos cómo
agregar Angular Material a nuestro proyecto. Luego, aprendió a usar el diseño flexible y
varios componentes de Angular Material para construir diferentes tipos de IU, como la
página Detalles maestros, la página Vista de listas y los formularios. Finalmente, vimos
cómo agregar temas de las paletas de colores de diseño de Material.

[ 199 ]
Index

route params 142, 144


@ Angular
@NgModule() about 6
used, for structuring application components into features 7
feature modules 150 Observables 77
animations.min.js polyfill file
A download link 148
aliased class providers AsyncPipe
using 70 using 81
alternate class providers attribute binding 38
using 69 attribute directives
Angular application about 45
Angular component, using 24 ngClass directive 46
npm packages 25 ngStyle directive 45
setting up 16, 17, 18, 19, 20, 21, 22, 26, 27
SystemJS 23 B
writing 16 Babel
Angular CLI reference 9
reference 127 book list application
Angular forms API building 54
about 92 Book Store application
FormArray 92, 95 developing 126
FormControl 92 books search component
FormGroup 92, 94 building 82, 84, 88
Angular Material components built-in directives
book dialog, adding 189, 190, 192 about 42
books list page 183, 186, 188 attribute directives 45
master-detail page, creating 177, 179, 180 structural directives 42
registration form 195
user registration form 193, 197 C
using 176
class provider
Angular Material
aliased class provider 70
about 173
alternate class provider 69
components, using 176
using 67, 68
project setup 174
using, with dependencies 68, 69
Angular router
navigation 142
integrated unit tests
D components with dependencies, testing 169,
data 171
displaying 35 components, testing 165, 166, 169
sharing, services used 63, 64, 65, 66 interpolation syntax 35, 36, 37
working with 34 isolated unit tests
dependencies about 157, 158
class provider, using with 68, 69 Angular HTTP service, mocking 163
dependency injection 67 components, testing 164
DOM (Document Object Model) 38 HTTP service, mocking 162
DRY (Don't Repeat Yourself) principle 55 services, testing 161
writing 159, 160
E
ECMAScript 2015 (ES2015) 9 J
ECMAScript 6 (ES6) 9 Jasmine
environment setup afterAll() function 157
Node.js, installing 8 afterEaxch() function 157
npm, installing 8 basics 156
event binding 39, 40 beforeAll() function 157
expect() function 156 beforeEach() function 157
describe() 156
F expect() function 157
flexbox layout it() function 156
reference 176 matchers 156
FormControl JSON server
about 92 reference 128
creating 92
input control states 93 L
input control value, accessing 93 language options
input control value, resetting 93 about 8
input control value, setting 93 ECMAScript 2015 9
forms ECMAScript 5 (ES5) 9
limitations 91 TypeScript 9

H M
HammerJS 175 master-detail component
HTTP Client building 47, 48, 49, 50, 51, 52
GET requests, making 131, 134 Material design color palettes
used, for communicating with REST service 127, reference 198
130 Material Design Lite
about 173
I reference 136
input properties multiple components
about 57, 58 working with 55, 56, 57
aliasing 58, 59

[ 201 ]
N R
ngClass directive 46 reactive forms
ngFor directive about 117
about 43 cons 125
syntax 44 pros 125
ngIf directive 43 used, for creating registration form 117
ngModel directive reactive programming
ngForm directive, using 105, 106 about 72
used, for accessing input control 100 reference 73
used, for binding component property 102 Reactive-Extensions for JavaScript (RxJS)
used, for binding string value 101 about 72
using 99 reference 89
ngModelGroup directive registration form
using 110 creating 96, 97, 99
ngStyle directive 45 creating, with [formGroup] 119
ngSubmit method creating, with formControlName 120
used, for submitting form 106, 108 creating, with formGroupName 120
ngSwitch directive 44 creating, with Validators 118
Node CustomValidators, using 122
reference 8 FormBuilder, using 121
npm (node package manager) 7 input control value, accessing with ngModel 100
ngModel directive, using 99
O ngModel, used for binding string value 101
Observable streams validations, adding 112, 114, 115, 116
merging 78 registration forms
Observable.interval() method creating, with FormControl 118
using 79 creating, with FormGroup 118
Observable routed components
about 72, 74, 77 animating 148
mapping values 77 RouterOutlet directive
stream 77 about 139
Observer 73 Named RouterOutlet 142
operators 76 routes
output properties defining 138
about 59, 60, 61 routing 137
aliasing 61, 62
S
P services
project setup 31, 32, 33, 34 used, for sharing data 63, 64, 65, 66
property binding structural directives
about 37 about 42
example 37 ngFor directive 43
syntax 37 ngIf directive 43
ngSwitch directive 44

[ 202 ]
subscription 76 classes 14
enum 12
T function declaration, named function 14
template driven forms function expression, anonymous function 14
about 96 functions 13
cons 116 installing 10
pros 116 number 11
registration form, creating 96 reference 16
test-driven development (TDD) 154 string 11
testing types 10
about 154 void 12
configuration files 155
end-to-end testing 155 U
tools 155 unit tests
unit testing 155 about 157
themes integrated unit tests 165
adding 197 isolated unit tests 157
two-way data binding 41, 42
TypeScript W
about 9 Web Animations API
Any 12 about 148
array 11 reference 148
Boolean 11

Anda mungkin juga menyukai