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.
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.
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.
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.
[ 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.
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 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 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.
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
[2]
Preface
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 { }
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".
[3]
Preface
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.
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:
[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.
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.
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:
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.
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
[7]
Getting Started
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
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.
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
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
[ 10 ]
Getting Started
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'];
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
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;
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';
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
Functions
Las funciones son los pilares fundamentales de cualquier aplicación de JavaScript. En
JavaScript, declaramos las funciones de dos maneras.
[ 13 ]
Getting Started
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
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.
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;
}());
[ 15 ]
Getting Started
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.
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
[ 17 ]
Getting Started
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"
}
}
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
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().
@NgModule({
imports: [BrowserModule],
declarations: [],
bootstrap: []
})
class HelloWorldAppModule {}
[ 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.
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 {}
[ 20 ]
Getting Started
@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>
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.
<body>
<hello-world-app>Loading...</hello-world-app>
</body>
[ 24 ]
Getting Started
@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.
[ 25 ]
Getting Started
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.
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
¿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.
[ 27 ]
Getting Started
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.
[ 28 ]
Getting Started
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.
[ 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:
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.
├─ 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:
platformBrowserDynamic().bootstrapModule(AppModule);
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
@Component({
selector: 'display-data-app',
template: '<h1>Data Binding in Angular -
Interpolation Syntax</h1>'
})
export class AppComponent {}
[ 32 ]
Basics of Components
<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>
(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:
[ 34 ]
Basics of Components
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.
@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.
[ 35 ]
Basics of Components
Observe que la template anterior es una cadena multilínea, y está rodeada por
símbolos ` (backtick) en lugar de comillas simples o dobles.
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.
[ 36 ]
Basics of Components
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
[ 37 ]
Basics of Components
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.
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.
[ 39 ]
Basics of Components
@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;
}
}
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
[(ngModel)] = "component-property"
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
[ 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.
@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';
}
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>
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.
[ 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:
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>
[ 44 ]
Basics of Components
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;
}
[ 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.
.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
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.
<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
@NgModule({
imports: [BrowserModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}
@Component({
selector: 'books-list',
templateUrl: ./app.component.html'
})
export class AppComponent {
}
[ 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.
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.
[ 49 ]
Basics of Components
[ 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.
[ 51 ]
Basics of Components
}
}
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:
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:
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
@Component({
selector: 'book-details',
templateUrl: './book-details.component.html'
})
export class BookDetailsComponent {
book: Book;
}
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
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.
[ 57 ]
Components, Services, and Dependency Injection
@Component({
selector: 'book-details',
templateUrl: './book-details.component.html',
})
export class BookDetailsComponent {
@Input() book: Book;
}
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.
[ 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);
}
deleteBook () {
}
}
[ 59 ]
Components, Services, and Dependency Injection
@Component({
selector: 'book-details',
templateUrl: './book-details.component.html'
})
export class BookDetailsComponent {
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>
[ 61 ]
Components, Services, and Dependency Injection
[ 62 ]
Components, Services, and Dependency Injection
@Injectable()
export class BookStoreService {
getBooks () {
return this.booksList;
}
[ 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.
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();
[ 64 ]
Components, Services, and Dependency Injection
@Component({
providers: [BookStoreService]
})
export class AppComponent {
constructor(private bookStoreService:BookStoreService) { }
}
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;
[ 65 ]
Components, Services, and Dependency Injection
@Component({
selector: 'books-list',
templateUrl: 'src/app.component.html',
providers: [BookStoreService]
})
export class AppComponent implements OnInit {
booksList: Book[];
selectedBook: Book;
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);
}
}
[ 66 ]
Components, Services, and Dependency Injection
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:
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
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.
[ 68 ]
Components, Services, and Dependency Injection
[ 69 ]
Components, Services, and Dependency Injection
[ 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
}
]
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
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
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
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;
}
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()
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>
Aquí hay un ejemplo de cómo trabajar con eventos DOM usando un Observable.
[ 75 ]
Working with Observables
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.
setTimeout(() => {
subscription.unsubscribe();
}, 10000);
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
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.
@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!');
Cuando se cambia el texto del cuadro de texto, estamos mostrando el mismo texto en el
mensaje
Observable
[ 78 ]
Working with Observables
.merge(btnOb$, textOb$)
.subscribe(res => this.message = res);
}
@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());
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
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
@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.
@Injectable()
[ 83 ]
Working with Observables
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.
[ 84 ]
Working with Observables
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()
[ 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.
@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 = '';
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);
}
}
@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
searchBook(title: string) {
this.bookStoreService
.getBooks(title)
.subscribe(books => this.filteredBooks = books);
}
}
@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[] = [];
}
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.
[ 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:
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
Cada técnica tiene diferentes opiniones sobre cómo manejar formularios; los veremos
en detalle en las próximas secciones.
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();
[ 92 ]
Handling Forms
[ 93 ]
Handling Forms
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('')
});
[ 94 ]
Handling Forms
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.
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']);
Ahora, hemos aprendido las clases básicas para el módulo de formularios angulares. Vamos a
sumergirnos en los diferentes enfoques proporcionados por Angular.
[ 96 ]
Handling Forms
/**
The stylesheet is very long, reader can add it from sample code under
chapter5/forms example.
**/
@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.
@Component({
selector: 'forms-app',
template: '<registration-form></registration-form>'
})
export class AppComponent {
}
[ 97 ]
Handling Forms
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.
@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:
[ 99 ]
Handling Forms
Una vez que guardamos la plantilla, el navegador se actualizará con el siguiente resultado:
{{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:
Una vez que guardamos la plantilla, el navegador se actualizará con el siguiente resultado:
[ 101 ]
Handling Forms
interface User {
firstName: string;
lastName: string;
}
@Component({
selector: 'registration-form',
templateUrl: './registration-form.component.html'
})
export class RegistrationFormComponent {
user: User = {
firstName: 'Shravan',
lastName: 'Kasagoni'
}
}
[ 102 ]
Handling Forms
[ 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:
[ 104 ]
Handling Forms
[ 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:
[ 106 ]
Handling Forms
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.
[ 107 ]
Handling Forms
[ 108 ]
Handling Forms
ngModel="" #countryRef="ngModel">
<option value="IN">India</option>
<option value="US">United States of America</option>
</select>
</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:
[ 110 ]
Handling Forms
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:
[ 111 ]
Handling Forms
[ 112 ]
Handling Forms
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
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">
[ 114 ]
Handling Forms
{{firstNameRef?.errors?.minlength.actualLength}} characters.
</div>
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
[ 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.
[ 117 ]
Handling Forms
@NgModule({
imports: [BrowserModule, ReactiveFormsModule],
declarations: [AppComponent, RegistrationReactiveFormComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
@Component({
selector: 'forms-app',
template: `<registration-reactive-form>
</registration-reactive-form>`
})
export class AppComponent {
}
[ 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)
}
}
[ 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 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;
ngOnInit() {
this.registrationForm = this.formBuilder.group({
firstName: ['Shravan', Validators.required],
lastName: '',
[ 121 ]
Handling Forms
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.
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
ngOnInit () {
this.registrationForm = this.formBuilder.group({
password: ['', [Validators.required,
CustomValidators.passwordStrength]]
});
}
[ 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';
if (CustomValidators.isEmptyValue(password) ||
CustomValidators.isEmptyValue(confirmPassword)) {
return null;
}
static isEmptyValue(value) {
return value == null ||
typeof value === 'string' && value.length === 0;
}
}
[ 124 ]
Handling Forms
In the template:
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:
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
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
[ 127 ]
Building a Book Store Application
[ 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.
[ 129 ]
Building a Book Store Application
Agregue la clase Book bajo la carpeta de la aplicación, que representa la estructura del objeto libro.
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
getBooksData() {
console.log(this.booksList);
}
}
[ 130 ]
Building a Book Store Application
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
[ 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:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
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
}
}
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:
@Injectable()
export class BookStoreService {
getBooksList(): Observable<Book[]> {
[ 134 ]
Building a Book Store Application
@NgModule({
...
providers: [BookStoreService],
...
})
export class AppModule { }
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);
}
}
[ 136 ]
Building a Book Store Application
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:
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>
import {
BooksListComponent,
BookDetailsComponent,
NewBookComponent,
BookStoreService
} from './books';
@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 { }
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
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
}
El código restante anterior se quita para más legibilidad; podemos encontrar el código
completo en el código fuente provisto.
@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[];
ngOnInit() {
this.getBooksList();
}
getBooksList() {
this.storeService.getBooks()
.subscribe(books => this.booksList = books);
}
}
[ 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>
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>
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
@Component({
selector: 'book-details',
templateUrl: './book-details.component.html',
styleUrls: ['./book-details.component.scss']
})
export class BookDetailsComponent implements OnInit {
book: Book;
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'].
[ 144 ]
Building a Book Store Application
@Component({
selector: 'new-book',
templateUrl: './new-book.component.html',
styleUrls: ['./new-book.component.scss']
})
export class NewBookComponent implements OnInit {
newBookForm: FormGroup;
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
[ 146 ]
Building a Book Store Application
[ 147 ]
Building a Book Store Application
En esta sección, vamos a aprender cómo animar mientras navegas entre los
componentes.
...
@NgModule({
...
imports: [
...
BrowserAnimationsModule,
RouterModule.forRoot(routes)
]
...
})
export class AppModule {
}
[ 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%)'
}))
])
]);
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.
@Component({
...
animations: [slideInOutAnimation]
})
export class BookDetailsComponent implements OnInit {
...
[ 149 ]
Building a Book Store Application
Podemos seguir los pasos anteriores para agregar la animación a cualquier componente de nuestro
ejemplo.
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.
[ 150 ]
Building a Book Store Application
@NgModule({
imports: [
RouterModule.forChild(routes)
],
exports: [
RouterModule
]
})
export class BooksRoutingModule {
}
Vamos a crear el módulo de características de libros y agregar todos los componentes, servicios y
y rutas relacionados.
@NgModule({
declarations: [
BooksListComponent,
BookDetailsComponent,
NewBookComponent
],
imports: [
CommonModule,
ReactiveFormsModule,
HttpModule,
BooksRoutingModule
],
providers: [BookStoreService]
})
[ 151 ]
Building a Book Store Application
@NgModule({
imports: [
RouterModule.forRoot(routes)
],
exports: [
RouterModule
]
})
export class AppRoutingModule {
}
@NgModule({
[ 152 ]
Building a Book Store Application
declarations: [
AppComponent,
DashboardComponent,
AboutComponent,
Safe
],
imports: [
BrowserModule,
BooksModule,
AppRoutingModule
],
bootstrap: [AppComponent]
})
export class AppModule {
}
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.
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:
Archivos de configuración
Los siguientes son los archivos de configuración de Karma:
[ 155 ]
Testing
it(): Es una función de Jasmine utilizada para escribir las pruebas unitarias
reales. La sintaxis es la siguiente:
[ 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:
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.
[ 157 ]
Testing
[ 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.
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
describe('AppComponent', () => {
});
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:
describe('AppComponent', () => {
let component: AppComponent;
beforeEach(() => {
component = new AppComponent();
});
[ 160 ]
Testing
});
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:
describe('BookStoreService', () => {
let bookStoreService: BookStoreService;
beforeEach(() => {
bookStoreService = new BookStoreService();
});
});
[ 161 ]
Testing
Dependencias Mocking
Vamos a simular el servicio HTTP de angular utilizando el método jasmine.createSpyObj():
describe('BookStoreService', () => {
let bookStoreService: BookStoreService,
mockHttp;
beforeEach(() => {
mockHttp = jasmine.createSpyObj('mockHttp',
['get', 'post', 'delete']);
mockHttp.delete.and.returnValue(Observable.of(book));
const response = bookStoreService.deleteBook(12);
response.subscribe(value => {
expect(value).toBe(book);
});
[ 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', () => {
mockHttp.delete.and.returnValue(Observable.of(book));
const response = bookStoreService.deleteBook(12);
response.subscribe(value => {
expect(value).toBe(book);
});
});
});
[ 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:
describe('BooksListComponent', () => {
let booksListComponent: BooksListComponent,
mockBookStoreService;
beforeEach(() => {
mockBookStoreService = jasmine.createSpyObj(
'mockBookStoreService', ['getBooks']);
booksListComponent =
new BooksListComponent(mockBookStoreService);
});
describe('ngOnInit', () => {
});
});
[ 164 ]
Testing
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:
@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
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;
});
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:
[ 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.
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.
fixture = TestBed.createComponent(AboutComponent);
[ 167 ]
Testing
component = fixture.componentInstance;
});
describe('heading', () => {
beforeEach(() => {
debugElement = fixture.debugElement.query(By.css('h4'));
element = debugElement.nativeElement;
});
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;
});
[ 168 ]
Testing
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.
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
describe('BooksListComponent', () => {
let fixture: ComponentFixture<BooksListComponent>,
component: BooksListComponent,
debugElement: DebugElement,
element: HTMLElement,
mockBookStoreService;
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;
});
});
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
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
Vamos primero a instalar Angular Material. El siguiente comando instalará Angular Material:
npm install @angular/material --save
@NgModule({
...
imports: [
BrowserModule,
HttpModule,
BrowserAnimationsModule
],
...
})
export class AppModule { }
[ 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">
[ 175 ]
Angular Material
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
],
...
})
https://css-tricks.com/snippets/css/a-guide-to-flexbox/
https://github.com/angular/flex-layout/
[ 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.
@Component({
selector: 'bl-master-detail',
styleUrls: ['./master-detail.component.scss'],
templateUrl: './master-detail.component.html'
})
export class MasterDetailComponent implements OnInit {
booksList: Book[] = [];
selectedBook: Book;
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
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:
const MATERIAL_MODULES = [
MdSidenavModule,
MdListModule
];
@NgModule({
imports: MATERIAL_MODULES,
exports: MATERIAL_MODULES
})
export class AppMaterialModule { }
@NgModule({
...
imports: [
BrowserModule,
HttpModule,
BrowserAnimationsModule,
FlexLayoutModule,
AppMaterialModule
]
...
})
[ 179 ]
Angular Material
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.
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:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
[ 180 ]
Angular Material
})
export class AppComponent {
links = [{
name: 'Books'
}];
}
<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
[ 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>.
@Component({
selector: 'bl-list',
styleUrls: ['./list.component.scss'],
templateUrl: './list.component.html'
})
export class ListComponent implements OnInit {
booksList: Book[] = [];
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.
</section>
</div>
[ 184 ]
Angular Material
[ 185 ]
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
});
}
[ 186 ]
Angular Material
getBooks() {
this.bookStoreService
.getBooks()
[ 187 ]
Angular Material
.subscribe(response => {
this.booksList = response;
this.spinnerVisibility = 'none';
});
}
En nuestros ejemplos, es posible que no podamos ver la ruleta; para simular el retraso,
usamos el operador RxJS delay() en el BookStoreService:
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.
[ 188 ]
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>) {}
}
[ 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.
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:
[ 190 ]
Angular Material
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
links = [{
name: 'Books'
}];
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.
[ 192 ]
Angular Material
@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);
}
}
[ 194 ]
Angular Material
@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>
@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:
@include mat-core();
$primary: mat-palette($mat-red);
$accent: mat-palette($mat-blue);
[ 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"
]
[ 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
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