Angular Avanzado
Angular Avanzado
CodingPotions
Contents
Introducción 4
Sobre el autor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Decoradores Angular 14
Decoradores de clase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Decoradores de atributos y parámetros . . . . . . . . . . . . . . . . . . . . . 15
Decoradores de métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Cómo crear tus propios decoradores . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1
Ciclo de vida de los componentes 24
ngOnInit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
ngOnChanges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
AfterViewInit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
OnDestroy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Directivas 28
Directivas de atributo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Directivas estructurales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Observables y promesas 33
Animaciones 36
PWA 42
SEO en Angular 46
Lazy components 53
Testing unitario 55
Tests unitarios con Jasmine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Tests unitarios con Angular . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Testeando clases en Angular . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Usando el servicio real . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Creando un servicio virtual (mockeando) . . . . . . . . . . . . . . . . . . . . . . . 61
Mediante del uso de spy de Jasmine . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Testeando llamadas asíncronas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Accediendo a la vista . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
Testing de llamadas http . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Conclusiones 68
2
“Bad programmers worry about the code. Good programmers worry about
data structures and their relationships.”
— Linus Torvalds
3
Introducción
¡Hola! Lo primero, muchas gracias por adquirir este libro. Me hace mucha ilusión que
confíes en mi para seguir aprendiendo y formándote sobre Angular.
¡ATENCIÓN! Este libro esta pensado para ser usado a partir de la versión 2 de Angular,
si tu intención es aprender cosas de AngularJS (primera versión), lo que hay escrito no
te va a servir.
Los conceptos de este libro no los he ordenado siguiendo ningún orden en particular,
por lo que los puedes leer en el orden que quieras.
También me gustaría pedir perdón porque al ser un libro en PDF no vas a poder copiar y
pegar el código que aparecé aquí escrito. Si necesitas que te pase algún código de los
que aquí aparecen puedes contactar conmigo por Twitter: @CodingPotions
Sobre el autor
https://codingpotions.com.
4
Puedes contactar conmigo mediante mi email: [email protected] o
mandando un mensaje a mi cuenta de Twitter @Codingpotions
5
Debug de aplicaciones Angular
Uno no puede programar algo sin tener un solo fallo, de no ser que estés usando una
estrategia TDD (Test Driven Development) que sea infalible por eso es fundamental
conocer lo que ofrece Angular para hacer debug.
ng.probe($0).componentInstance
Con este comando vas a poder el estado de un componente desde la consola del
navegador. Para usarlo abre el inspector de elementos de tu navegador y selecciona un
componente en la pestaña de Elements. A continuación ejecuta:
ng.probe($0).componentInstance
En este caso $0 es una variable global que se traduce al último elemento seleccionado
en el inspector.
Es importante saber que este comando solo servirá si tenemos activado el modo debug
de Angular. Normalmente el modo debug está desactivado en producción cuando haces
build, para que lo tengas en cuenta.
6
ng.profiler.timeChangeDetection()
El comando profile sirve para medir cosas, por el momento Angular solo ofrece medir
el changeDetection. changeDetection mide el tiempo que pasa cada vez que Angular
detecta un cambio y actualiza la vista. Esto nos puede dar una medida de rendimiento,
si tarda más de 3ms en cada cambio nos tenemos que plantear mejorar su rendimiento.
Augury
Augury es una extensión para el navegador que nos permite ver los componentes de
Angular en el navegador, es decir, ofrece más información sobre los componentes que
el inspector de elementos, como por ejemplo. la relación entre ellos, las rutas, etc.
7
Puedes descargar augury desde su página oficial: Página oficial de Augury
Una vez instales Augury, aparecerá una nueva pestaña en el inspector de elementos:
8
En páginas grandes esto permite tener un control del flujo de la aplicación para poder
depurar y detectar errores. Además esta información puede resultar útil para decidir
que rutas hacer que carguen de forma perezosa para ahorrar recursos.
debugger
Existe un comando especial para Javascript muy útil que también se puede aplicar en
Angular. Se trata de la instrucción debugger, que permite para el código en el punto
que queramos. Esto quiere decir que poniendo este comando vas a poder parar la
ejecución justo antes de un error o de una parte en particular que quieras ver con más
detenimiento. Su sintaxis es muy fácil de recordar:
debugger;
9
Usos de ng-template, ng-container y ngTemplateOutlet
ng-template
Esta etiqueta sin saberlo ya la has usado antes, en concreto cuando creas un ngIf o un
ngFor.
La etiqueta, como su nombre indica, sirve para representar una plantilla a renderizar
en el componente. Esta plantilla o template, se puede componer a su vez de otros
templates.
Vemos un ejemplo:
@Component({
selector: 'app-root',
template: `
<ng-template>
<button class="tab-button"
(click)="login()">{{loginText}}</button>
<button class="tab-button"
(click)="signUp()">{{signUpText}}</button>
</ng-template>
`})
Al compilarse creará:
<ng-template [ngIf]="lessons">
<div class="lessons-list">
...
</div>
</ng-template>
Es mucho más sencillo poner simplemente el ngIf que andar creando un elemento por
10
fuera cada vez. Esto quiere decir que los ng-template quieras o no se usan en toda la
aplicación todo el rato.
Un uso típico que tiene ng-template es el de poner crear elses, es decir, si queremos
hacer:
<ng-template #showItem2>
Dentro del ELSE
</ng-template>
ng-container
Esta etiqueta la encuentro personalmente muy útil. Sirve para no tener en el DOM
elementos de más. Es decir, por ejemplo si quieres poner un IF dentro de la vista, lo
que normalmente harías es meterlo dentro de un <div> o similar:
<div *ngIf="users">
<div class="users" *ngFor="let user of users">
{{user}}
</div>
</div>
Con esto se crea un elemento div exterior (el que crea el ngIf) que realmente no
es necesario, si sustituimos el div exterior por un ng-container no tendremos este
problema:
<ng-container *ngIf="users">
<div class="users" *ngFor="let user of users">
{{user}}
</div>
</ng-container>
11
ngTemplateOutlet
Con la directiva ngTemplateOutlet puedes usar plantillas creadas con ng-template para
usarlas en cualquier parte de la vista:
<ng-template #greet><span>Hello</span></ng-template>
<ng-container *ngTemplateOutlet="greet"></ng-container>
Podemos ver aquí cómo ng-container ayuda en este caso de uso: lo estamos usando
para instanciar en la página la plantilla de carga que definimos anteriormente.
12
Cómo usar SASS con Angular
SASS también se puede usar con Angular, y si todavía no has creado el proyecto con
Angular CLI, lo tienes muy fácil, tan solo tienes que ejecutar:
Puedes cambiar el style=scss por sass o less dependiendo de la variante que quieras.
Si ya tienes el proyecto creado de antes y usas CSS, cambiarlo a SASS puede ser un
poquito más tedioso. Vamos a ver el procedimiento:
"defaults": {
"styleExt": "scss",
"component": {
}
}
Hay un problema con esto, los ficheros que ya hayas creado con sass no se van a renom-
brar. Si decides renombrarlos manualmente, te tienes que acordar en los componentes
de actualizar el nombre también.
Por eso vengo con este script para que el proceso sea automático. Lo primero, buscar y
reemplazar el nombre de todos los ficheros:
Y por último actualizar todas las referencias a ficheros css para usar los scss:
Como en PDF no deja copiar y pegar, te dejo la dirección del script en github:
https://gist.github.com/Frostqui/9fefbf424c887b5a07e773adecc52e8c
13
Listo, ya puedes usar sass en tus proyectos. Esto no quiere decir que sea obligatorio
usarlo, aunque yo personalmente lo recomiendo, sobre todo para tener que escribir
menos código CSS.
Decoradores Angular
Antes de empezar directamente con los decoradores hay que aclarar que existen varios
tipos de decoradores:
Decoradores de clase
Con los decoradores indicas qué tipo de clase vas a declarar. Básicamente existen dos
decoradores de clase, @Component y @Module
Por ejemplo:
@Component({
selector: 'example-component',
template: '<div>Example component!</div>',
})
export class ExampleComponent {
constructor() {
console.log('Hey I am a component!');
}
}
@NgModule({
imports: [],
declarations: [],
})
export class ExampleModule {
constructor() {
console.log('Hey I am a module!');
14
}
}
Estos decoradores van dentro de la clase, en sus propiedades y parámetros. Estoy seguro
de que estos decoradores ya los usado con Angular, por ejemplo:
@Component({
selector: 'example-component',
template: '<div>Example component!</div>',
})
export class ExampleComponent {
constructor() {
console.log('Hey I am a component!');
}
}
Por debajo, Angular hace la magia de usar el decorador @Input para generar lo necesario
para que puedas pasar atributos desde fuera del componente.
@Component({
selector: 'example-component',
template: '<div>Example component!</div>'
15
})
export class ExampleComponent {
constructor(@Inject(MyService) myService) {
console.log(myService); // MyService
}
}
En este caso se usa para hacer inyección de dependencias de los servicios en los compo-
nentes.
Decoradores de métodos
Como te podrás imaginar estos se usan junto a los métodos. Un ejemplo de estos
decoradores es el @HostListener que sirve para capturar eventos, por ejemplo:
@Component({
selector: 'example-component',
template: '<div>Example component!</div>'
})
export class ExampleComponent {
@HostListener('click', ['$event'])
onHostClick(event: Event) {
// clicked, `event` available
}
}
Muy bien, hemos visto los tipos de decoradores que hay, pero seguro que ya los conocías
todos. Vamos a lo realmente interesante, cómo crear los tuyos propios.
Lo que no mucha gente sabe de Angular, es que se pueden crear decoradores. Creando
los tuyos propios vas a agilizar mucho el desarrollo de aplicaciones de Angular porque
los vas a definir una sola vez y los vas a poder reutilizar en muchos sitios.
Los decoradores no son más que funciones que se ejecutan cuando Angular llama al
decorador. La función puede recibir como parámetro la clase que se ha decorado (si
es un decorador de clase), el atributo o parámetro o el método si es un decorador de
método.
16
Vamos a crear un decorador que simplemente imprima por pantalla:
function Console(target) {
console.log('La clase decorada es: ', target);
}
@Console
class ExampleClass {
constructor() { }
}
Otra ventaja de los decoradores es que se le pueden pasar parámetros, en ese caso
tienes que adaptar un poco la estructura del decorador:
function Console(message) {
// Message es el parámetro que le pasas al decorador
console.log(message);
return function(target) {
// Dentro de esta función recibes la clase o el método decorado
console.log('La clase decorada es: ', target);
};
}
Y listo, ya puedes crear tus propios decoradores para reutilizar código entre componentes.
Yo personalmente todavía no creo mis propios decoradores porque es algo que he
aprendido recientemente, pero no descarto aplicarlos para nuevos proyectos.
17
Gestión de multiples entornos
Es muy común disponer de varios entornos de pruebas antes de publicar una aplicación
Angular, sobre todo en entornos profesionales. Estos entornos se suelen conocer como
preproducción y producción. El entorno de producción es el entorno que utilizan los
usuarios, es decir, en una web, el entorno de producción es la maquina donde está
desplegada la web que ve la gente. Los entornos de preproducción se utilizan para
hacer pruebas antes de publicar algo que puedan ver los usuarios o clientes de nuestra
aplicación.
Si utilizas Angular y lo conectas a una API REST, puede darse el caso en el que la API
también tenga entorno de preproducción, por eso en este capítulo vamos a ver cómo
podemos hacer para conectarnos a la API de preproducción o de producción dependiendo
de dónde despleguemos la aplicación de Angular sin tener que tocar nada.
18
En el gráfico de arriba puedes ver un ejemplo de flujo, pero el que yo recomiendo es el
siguiente:
Con este procedimiento tenemos que tener claro que a master se sube cada menos
tiempo que a develop, y solo cosas que estén funcionando y estén propadas en el
entorno de preproducción.
Muy bien, vamos a ver cómo se puede integrar esto con Angular.
Por ejemplo, vamos a configurar dos variables distintas para los endpoints de la API.
Empecemos configurando la API que se va a utilizar en producción. Abrimos el archivo
environment.prod.ts y añadimos:
Los archivos son iguales, excepto que cambia la dirección del endpoint de la API.
19
console.log(environment.apiEndpoint);
Para que Angular sustituya bien la variable por el valor de la del entorno de producción,
tienes que hacer el build así:
ng build --prod
Si estos dos entornos se te queda cortos no te preocupes porque puedes crear todos
los que quieras. Por ejemplo, pongamos que queremos crear un entorno llamado test
para hacer pruebas rápidas antes de pasar a preproducción. Lo primero que tienes que
hacer es crear un nuevo archivo, al mismo nivel que los otros dos entornos, llamado
environment.tests.ts.
"tests": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.tests.ts"
}
],
// ...
}
Con el replace indicas el fichero base, y con el with indicas qué fichero se va a usar
para ese entorno.
20
ng build --configuration=tests
Y listo, puedes añadir los entornos que quieras con las variables que quieras para cada
uno de ellos. Haciendo el build con la configuración de entorno que quieres usar, no
tendrás que cambiar nunca las variables a mano.
21
Aislamiento de etilos para los componentes
Angular ofrece por defecto el aislamiento de estilos, esto quiere decir que aunque
pongas la misma clase a un elemento del html en distintos componentes, sus estilos
no se van a compartir. Esto es muy útil para el desarrollo de componentes porque no
tienes que usar prefijos para las clases para no entrar en conflicto con estilos de otros
componentes. Para realizar esto lo que hacen los frameworks es asginar clases con un
hash para que cada componente tenga clases únicas.
Para que funcione el aislamiento tienes que crear los componentes así:
...
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss']
})
...
Con esto vas a conseguir que los estilos queden mucho más simples al poder reutilizar
las clases. Otra de sus ventajas es la facilidad de compartir componentes entre proyectos,
ya que siempre vas a tener la seguridad de que los estilos no van a chocar con los del
nuevo proyecto.
:host
A veces queremos dar estilos al propio elemento HTML del componente, y no lo que
haya dentro de él.
Digamos, por ejemplo, que queremos dar forma al propio componente app-root,
añadiéndole, por ejemplo, un borde adicional.
Esto se debe a que todos los estilos dentro de ese archivo se incluirán en los elementos
de la plantilla, y no en el elemento app-root externo en sí.
22
Si queremos cambiar el estilo del elemento host del propio componente, necesitamos
el selector especial :host.
:host {
border: 2px solid red;
display: block;
padding: 20px;
}
::ng-deep
Si queremos que nuestros estilos de componentes pasen a todos los elementos hijos
de un componente, pero no a ningún otro elemento de la página, podemos hacerlo
combinando el :host con el selector ::ng-deep
:host ::ng-deep h2 {
color: red;
}
Esto aplicará estilo a todos los h2 de app-root pero no a los de fuera suya. Es decir, es
útil porque no tenemos que poner clases adicionales para dar estilo sino que directamente
con ng-deep aplicamos a todos pero no a los de toda la aplicación web.
23
Ciclo de vida de los componentes
Sabes cómo se crean los componentes, pero ¿sabes por qué estado pasan hasta que se
destruyen?
ngOnInit
24
import {Component, OnInit} from '@angular/core';
ngOnInit() { this.getProducts(); }
getProducts() {
this.productService.getProducts()
.subscribe(
products => this.products = products,
error => this.errorMessage = <any>error);
}
}
Como ves, lo primero que hay que hacer es importar Component y onInit de Angular.
Al crear el componente haces que implemente de OnInit y dentro del componente
llamas a ngOnInit con lo que quieres que se ejecute al principio, en este caso la llamada
a getProducts(). El OnInit se suele usar mucho para este tipo de cosas, llamadas a
servicios para inicializar lo que se quiere pintar en pantalla.
ngOnChanges
Este método se ejecuta cuando Angular detecta un cambio en uno de los @Input y
antes que OnInit, es decir, cuando desde fuera se modifica la variable que se le pasa al
componente hijo. Veamos un ejemplo:
25
ngOnChanges(changes: SimpleChanges) { console.log(changes.previousValue); }
Este método tiene una particularidad, y es que recibe como parámetro un objeto de
tipo SimpleChanges. Este objeto contiene la información del cambio, es decir, qué valor
tenía antes y el nuevo valor que recibe.
class SimpleChange {
constructor(previousValue: any, currentValue: any)
previousValue : any
currentValue : any
isFirstChange() : boolean
}
AfterViewInit
Este método se ejecuta cuando Angular ya ha inicializado y cargado la vista. Este método
te puede interesar si necesitas que pase algo o se ejecute algo tras haber cargado la
vista. Por ejemplo para realizar una petición en segundo plano con la vista ya cargada.
26
OnDestroy
Este método te puede servir para resetear estados, guardar cambios, etc.
27
Directivas
Las directivas en Angular son muy interesantes, permiten modificar el DOM y sus estilos.
Hay 3 tipos de directivas:
• Componetes: Los componentes que has visto hasta ahora son directivas también
porque cambian el comportamiento del DOM
• Estructurales: Añaden y eliminan elementos del DOM
• De atributo: Cambian los estilos y atributos de elementos del DOM o de otras
directivas
Directivas de atributo
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor() { }
}
Esta es la estructura básica de una directiva de atributo en Angular. Dentro del selector
he puesto el nombre que va a tener la directiva para poder referenciarla, en este caso
la he llamado poniendo app delante para que su nombre no entre en conflicto con
selectores de librerías.
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(el: ElementRef) {
28
el.nativeElement.style.backgroundColor = 'yellow';
}
}
Si ahora haces ng serve verás que el elemento sobre el que lo has aplicado tiene el fondo
amarillo. Aparte de poder modificar los estilos del DOM, también podemos hacerlo
dependiendo de si se produce un evento determinado, por ejemplo:
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.highlightColor || 'red');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
29
}
En este ejemplo más complejo, hacemos uso del @HostListener para poder reaccionar a
los eventos producidos por el ratón, en este caso cuando estamos sobre el elemento.
Además, hay un atributo @Input para poder pasar el color desde el elemento sobre el
que se va a usar.
Para usarlo:
Este ejemplo se muy sencillo y tiene poca utilidad pero te puedes hacer una idea de
las posibilidades que ofrece esto. Puedes crear una serie de directivas con efectos
reaccionando a eventos para que las puedas reutilizar a lo largo de todo el proyecto.
Directivas estructurales
Las directivas estructurales están a cargo del DOM, pueden modificar sus elementos,
añadir, borrar, etc. Puedes reconocer una directiva estructural porque para usarlas pones
un asterisco * y después su nombre. Sin darte cuenta llevas mucho tiempo usando
directivas estructurales, pero no te has dado cuenta. Por ejemplo *ngIf=“condicion”
es una directiva estructural que se encarga de mostrar o no el elemento sobre el que
se aplica la directiva dependiendo de la condición. La directiva de ngIf no muestra y
oculta los elementos sobre los que se aplica sino que físicamente los borra y los crea
dinámicamente.
Los nombres de las directivas estructurales se suelen poner con la convención lower-
CamelCase, es decir, empiezan en minúsculas y cada palabra se pone junto a la anterior
en mayúsculas la primera letra. El asterisco se pone porque es azúcar sintáctico, es decir,
realmente por debajo Angular cuando llega al asterisco, lo cambia por un template, de
esta forma:
<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
({{i}}) {{hero.name}}
</div>
30
<div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</ng-template>
Como en las otras directivas, se define su selector entre corchetes. Su selector será la
forma de llamar a la directiva desde la vista. Una particularidad de esta directiva es que
dentro del constructor define dos objetos:
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef)
/**
* Add the template content to the DOM unless the condition is true.
*/
@Directive({ selector: '[appUnless]'})
export class UnlessDirective {
private hasView = false;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef) { }
31
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
}
32
Observables y promesas
Los observables permiten el paso de información y mensajes entre los que los publican
y los que están suscritos a esos mensajes. Un ejemplo de uso de Observables es el paso
de información entre los servicios y los componentes.
Los mensajes son lazy (perezosos), eso quiere decir que solo se envían a los que estén
suscritos. Dentro del observable defines una función en la que se publican los valores
pero que no se va a ejecutar hasta que no lo recibe un consumidor, es decir, hasta que
el consumidor ejecuta suscribe(). Veamos un ejemplo con un productor:
33
getSeeschweiler(): Observable<any>{
return this.http.get('https://api.github.com/users/seeschweiler');
}
Para este ejemplo lo que hago es hacer una llamada a la api de github. Como ves,
devuelvo en la llamada un Observale de tipo any, aunque lo suyo es crear una interfaz
con el modelo de datos que venga desde la api. En este caso, la función que se va a
ejecutar en el susbscribe() del consumidor es la llamada http devolviendo el valor de la
petición.
this.service.getSeeschweiler().subscribe(
data => {
this.data = data;
console.log(data);
}
);
Una buena práctica para el uso de los Observables es usar el objeto Subscription para
tener un control de la subscripción al observable. Esto es recomendable para poder
poner en el OnDelete una llamada para desuscribirte y así evitar pérdidas de memoria:
@Component({
selector: ‘app-model’,
templateUrl: ‘./model.component.html’,
styleUrls: [‘./model.component.css’]
})
34
constructor(private modelService: ModelService) { }
ngOnInit() {
this.modelSubscription = this.modelService.getUsers().subscribe(users => {
console.log(users);
})
}
ngOnDestroy() {
if (this.modelSubscription) {
this.modelSubscription.unsubscribe();
}
}
}
35
Animaciones
Aunque puedes crear tus propias animaciones con CSS, o incluso tus propios componentes
con Animaciones, Angular tiene una forma nativa de crear animaciones. Además de
poder crearlas, ofrece maneras de poder lanzarlas y controladas con determinados
eventos, por ejemplo, cuando se muestran los elementos de una listas, que se vayan
lanzando las animaciones escalonadamente.
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule
],
declarations: [ ],
bootstrap: [ ]
})
export class AppModule { }
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
36
styleUrls: ['app.component.css'],
animations: [
// Aquí metes las animaciones
]
})
Ahora que tienes activadas las animaciones en Angular, las puedes usar importando en
cada componente lo que vayas a usar. Angular ofrece lo siguiente:
• trigger(): Inicia la animación y sirve como contenedor para todas las demás.
• style(): Define uno o más estilos de CSS para usar en animaciones.
• state(): Crea un conjunto de estilos CSS con nombre que deben aplicarse en una
transición a un estado dado.
• animate(): Especifica la información de tiempo para una transición. Puede con-
tener llamadas style() dentro.
• transition(): Define la secuencia de animación entre dos estados con nombre.
• keyframes(): Permite un cambio secuencial entre estilos dentro de un intervalo
de tiempo especificado.
• group(): Especifica un grupo de pasos de animación (animaciones internas) que
se ejecutarán en paralelo.
• query(): Se usa para encontrar uno o más elementos HTML internos dentro del
elemento actual.
• sequence(): Especifica una lista de pasos de animación que se ejecutan secuen-
cialmente, uno por uno.
• stagger(): Escalona el tiempo de inicio de las animaciones para múltiples elemen-
tos.
• animation(): Produce una animación reutilizable que puede ser invocada desde
cualquier otro lugar. Usado junto con useAnimation().
• useAnimation(): Activa una animación reutilizable. Se utiliza con animation().
• animateChild(): Permite que las animaciones de los componentes hijo se ejecuten
en el mismo tiempo que el padre.
Si te estás preguntando qué son los estados, Angular define los siguientes estados:
37
que ser definido explícitamente en el código.
Veamos un ejemplo de animación en Angular. Por ejemplo vamos a crear una animación
que cambie el tamaño de un elemento del DOM:
animations: [
trigger('changeDivSize', [
state('initial', style({
backgroundColor: 'green',
width: '100px',
height: '100px'
})),
state('final', style({
backgroundColor: 'red',
width: '200px',
height: '200px'
})),
transition('initial=>final', animate('1500ms')),
transition('final=>initial', animate('1000ms'))
]),
]
Definimos nuestros propios estados con los estilos que queremos para ese estado. Debajo
usamos el método transition() para que cree la animación entre ambos estados. Puedes
ver que ya estamos utilizando 4 funciones que ofrece Angular para las animaciones:
trigger, state, transition y animate para poder elegir la duración de la animación.
Para poder ver bien la animación vamos a crear un método para poder cambiar de
estado:
currentState = 'initial';
changeState() {
this.currentState = this.currentState === 'initial' ? 'final' : 'initial';
}
38
<div [@changeDivSize]=currentState></div>
<br />
Si te fijas, en el botón llamamos al método para cambiar de estado al ser pulsado. Debajo,
creamos el div en el que aplicar al animación con [@changeDivSize] y le pasamos la
variable donde guardamos el estilo.
Supongamos ahora que queremos crear una animación al añadir y quitar elementos de
un array. Para ello existe una keyword especial que se usa al definir la animación para
decidir los estilos al aparecer elementos y desaparecer:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
animations: [
trigger('itemAnim', [
39
transition(':enter', [
style({transform: 'translateX(-100%)'}),
animate(350)
]),
transition(':leave', [
group([
animate('0.2s ease', style({
transform: 'translate(150px,25px)'
})),
animate('0.5s 0.2s ease', style({
opacity: 0
}))
])
])
])
]
})
En este caso no hace falta definir los estados como hemos hecho antes porque Angular
ya los define (enter y leave). Primero creamos la animación al añadir elementos al
array, para este ejemplo entrarán desde la izquierda. Para la animación de salida,
utilizo group() para poder encadenar dos animaciones, una de desplazamiento y otra de
opacidad.
<input #itemInput
(keyup.enter)="addItem(itemInput.value); itemInput.value=''">
<button
(click)="addItem(itemInput.value); itemInput.value=''">
Add
</button>
<ul *ngIf="items">
<li *ngFor="let item of items"
(click)="removeItem(item)"
[@itemAnim]>{{ item }}</li>
</ul>
40
El botón simplemente añade los elementos a la lista que creemos mediante el input y
al hacer clic sobre ellos los borramos para que se muestre la animación de salida.
41
PWA
En los últimos tiempos se habla mucho de las webs PWA, pero ¿qué son?. PWA es el
acrónimo de Progressive Web Application, es decir, son páginas webs progresivas que
aparte de adaptarse a todo tipo de pantallas, ofrecen la ventaja de poder instalarse
en dispositivos móviles. Actualmente Google permite subir webs PWA a la Play Store.
Además otra de las ventajas es que permiten ser utilizadas sin conexión, ya que los
recursos de la web quedan almacenados en la caché del navegador.
Crear una PWA con Angular es muy sencillo. Existe un paquete que precisamente crea
un service worker por lo que solo necesitas crear un archivo manifest manualmente en
la carpeta src. Veamos un ejemplo de manifest.json:
{
"short_name": "Nombre corto de tu web",
"name": "Nombre largo de tu web",
"icons": [
42
{
"src":"/assets/icon512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "index.html",
"background_color": "#FBFBFB",
"theme_color": "#022E63",
"display": "standalone",
"orientation": "portrait"
}
Este ejemplo contiene los parámetros mínimos que se requieren en todo archivo mani-
fest.json.
El icono y el nombre corto serán los que se muestren en la pantalla del dispositivo móvil
al ser instalada la web.
Para que funcionen los service workers en Angula tienes que activar un flag en el fichero
de configuración angular-cli.json:
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico",
"manifest.json",
"service-worker.js"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
43
"tsconfig": "tsconfig.json",
"testTsconfig": "tsconfig.json",
"prefix": "app",
"serviceWorker": true,
"styles": [
"styles.css"
],
...
ng set apps.0.serviceWorker=true
...
@NgModule({
imports: [
...
ServiceWorkerModule.register('/ngsw-worker.js', {enabled: environment.production})
],
...
})
export class AppModule { }
Y por último crear el archivo de configuración del service worker, llamado ngsw-
config.json:
{
"index": "/index.html",
"assetGroups": [{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html"
44
],
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**"
]
}
}]
}
ng build --prod
Si entras en la carpeta /dist/ te econtrarás con el fichero del service worker y el manifest
si todo lo has creado bien.
Si has desplegado Angular tras hacer el build. al abrir la página web dos veces, de-
jando pasar 5 minutos, aparecerá un cartel preguntando si instalar la app, al aceptar,
automáticamente se añadirá la web junto a las aplicaciones del móvil.
45
SEO en Angular
El SEO es una parte importantísima para cualquier negocio. El SEO se basa en conseguir
que más gente visite tu página web o la de tu empresa. Hay dos tipos de SEO princi-
palemente, SEO on page y SEO off page. El SEO on page se trata de todo lo que puedes
hacer respecto al código de tu web para que genere más tráfico y es lo que vamos a ver
en esta sección. El SEO off page, como su nombre indica es todo lo relacionado con tu
negocio fuera de tu página. es decir, en redes sociales, publicidad, etc.
Como bien sabes, Angular es un framework para construir páginas SPA, es decir, todo el
contenido se pinta de una vez al cargar la página. Esto tiene una desventaja, como la
página se renderiza en el cliente que abre la web, para los motores de búsqueda no hay
contenido. Haz la prueba, ejecuta:
ng serve
curl localhost:4200
46
Esto es lo que los motores de búsqueda ven de tu página. No hay contenido sino que se
genera mediante javascript al abrir la página y eso es un problema. ¿Cuál es la solución
entonces? Server side rendering, es decir, necesitamos que la web se renderice en un
servidor para servirla ya renderizada.
ng add @ng-toolkit/universal
Este comando añadirá todo lo necesario para transformar una aplicación angular a
server side rendering.
A continuación ejecuta:
npm install
47
Si ahora haces:
curl localhost:8080
Ahora el contenido al menos se renderiza para los motores de búsqueda. Recuerda que
lo que escribes también lo lee Google, no solo el título y la descripción.
Otro factor muy importante para el SEO es todo lo que puedes añadir en la etiqueta
de tu página web. Como se trata de una web SPA, todas las páginas usan el mismo
archivo html y es Angular el que se encarga de gestionar la vista, por lo que tendremos
que actualizar las etiquetas del
dinámicamente:
Veamos un ejemplo:
48
@Component({
selector: 'app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class HomeComponent {
constructor(private meta: Meta) {
this.meta.addTag({ name: 'twitter:card', content: 'summary_large_image' });
this.meta.addTag({ name: 'twitter:site', content: '@codingpotions' });
this.meta.addTag({ name: 'twitter:title', content: 'Blog de desarrollo web' });
this.meta.addTag({ name: 'twitter:image', content: 'https://codingpotions.com' });
}
}
En este ejemplo Angular añadirá las etiquetas para la vista previa en twitter en este
componente, y como se trata del componente app y todos heredan de él entonces se
incluirán también en cada página hija.
Angular también permite modificar una etiqueta meta que ya has creado:
this.meta.updateTag(
{ name: 'twitter:card', content: 'summary' },
`name='twitter:card'`
);
Ahora ya sabes cómo hacer que el contenido se renderice para los motores de búsqueda
y cómo crear y actualizar las etiquetas del
, con eso te debería bastar para poder hacer SEO con Angular.
49
Cómo estructurar un proyecto grande con Angular
El concepto clave es que tienes que tener una estructura que te permita buscar rápi-
damente el código que necesitas cambiar. Tienes que empezar el proyecto teniendo
al menos una idea de cómo quieres que termine el proyecto para poder tener una
estructura acorde.
La carpeta core
|-- core
|-- [+] authentication
|-- [+] footer
|-- [+] guards
|-- [+] http
|-- [+] interceptors
|-- [+] mocks
|-- [+] services
|-- [+] header
|-- core.module.ts
|-- ensureModuleLoadedOnceGuard.ts
|-- logger.service.ts
Aquí tambien puedes añadir servicios, en este caso yo suelo meter los servicios globales
para la aplicación ya que lo suyo es que cada módul de la aplicación gestione sus
servicios.
50
Otro detalle a tener en cuenta es que he añadio el archivo code.module.ts. Para facilitar
la modularidad de la aplicación se recomienda separar también los modulos, es decir,
los archivos en los que se importan los componentes y servicios que se vayan a usar.
En esta carpeta también se suelen colocar los guards que como sabes, sirven para
proteger rutas y páginas a ciertos usuarios que puedes definir.
La carpeta shared
|-- shared
|-- [+] components
|-- [+] directives
|-- [+] pipes
|-- shared.module.ts
Como su nombre indica, en esta carpeta se encuentran todo lo que se pueda compartir
a lo largo de toda la aplicación. Por ejemplo, dentro de la carpeta componets puedes
colocar botones, spinners, iconos, etc.
Además, esta carpeta sirve para alojar directivas y pipes ya que también se pueden usar
en otros componentes de la web.
La carpeta pages
|-- pages
|-- [+] page1
|-- [+] page2
|-- [+] page3
|-- pages.module.ts
Aquí suelo colocar las vistas de la aplicación, es decir, los componentes encargados de
cada página de la web. Cada componente aquí colocado tiene asociado una vista en la
aplicación, de esta forma tengo agrupadas todas las páginas que van a componer el
proyecto.
En el ejemplo he llamado a las carpetas page1, page2 y page3 pero lo suyo es que las
nombres con el nombre que tiene la página para que sean fáciles de localizar.
La carpeta components
|-- components
51
|-- [+] component1
|-- [+] component2
|-- [+] component3
|-- components.module.ts
Este es el cajón desastre por así decirlo, aquí meto todo lo que no va dentro de las otras
carpetas. Como en el apartado anterior, lo suyo es que cada carpeta tenga el nombre
del componente que va dentro para que sea todo más fácil de localizar
Otras consideraciones
Mucha gente también suele crear una carpeta llamada UI en la que coloca componentes
únicamente de diseño, es decir, componentes que no siguen una lógica de negocio y
que son puramente estéticos. No la suelo usar porque al final en los proyectos que yo
hago no termino con tantos componentes de diseño. No me parece mala idea y si a ti
te parece que tiene sentido en tu proyecto puedes usarlo sin problemas.
Eso es todo en cuanto a estructura de proyectos grandes, como he dicho utiliza lo que a
ti te venga mejor en el proyecto que estás desarrollando, siempre tienes que adaptar las
guías a las necesidades de tu propio proyecto. Intenta que todo sea modular, separado
entre sí y con nombres que te sean fáciles de buscar y con eso no vas a tener problemas
en proyectos grandes.
52
Lazy components
¿Sabes lo que significa laziness en el frontend? Cargar algo de forma lazy significa que
solo se carga cuando se necesita. Con Angular crear aplicaciones que se cargan al inicio
para que al navegar por las páginas el usuario no tenga que esperar? pero ¿no sería
fantástico que pudieras decidir qué quieres cargar al inicio y que no? Por ejemplo te
interesa que ciertas páginas que no se suelen visitar no estén siempre cargadas y así
hacer que la primera carga de la web sea más rápida.
En este caso la ruta lazymodule se cargará cuando navegue hasta ella pero no antes.
Fíjate en que en este caso hay que crear un atributo loadChildren. También a la hora
de poner la ruta al fichero es distinto. Tienes que indicar la ruta a un fichero module,
que será el encargado de declarar el componente. Además, tienes que indicar seguido
de # el nombre dentro del archivo module.
53
{ path: '', component: Component1Component },
{ path: 'component2', component: Component2Component },
{ path: 'component3', component: Component3Component },
];
@NgModule({
imports: [
RouterModule.forChild(routes)
],
declarations: [Component1Component,Component2Component,Component3Component]
})
export class LazyModuleModule {}
54
Testing unitario
Los tests son una pieza fundamental en los proyectos de hoy en día. Si tienes un proyecto
grande es esencial tener una buena suite de tests para poder probar la aplicación sin
tener que hacerlo manualmente. Además si lo combinas con la integración continua
puedes minimizar el riesgo y los bugs futuros.
55
Tests unitarios con Jasmine
Para hacer tests unitarios en Angular se suele usar Jasmine. Jasmine es un framework
Javascript (No es exclusivo de Angular, lo puedes usar en cualquier aplicación web), para
la definición de tests usando un lenguaje natural entendible por todo tipo de personas.
• describe: Define una suite de tests, es decir, una colección de tests. Ésta función
recibe dos parámetros, un string con el nombre de la suite y una función donde
definiremos los tests.
• it: Define un test en particular. Recibe cómo parámetro el nombre del test y una
función a ejecutar por el test.
56
• expect: Lo que espera recibir el test. Es decir, con expect hacemos la comprobación
del test. Si la comprobación no es cierta el test falla. En el ejemplo anterior
comprobamos si true es true luego el test pasa. Cómo ves no podemos simplemente
hacer la comprobación haciendo poniendo la operación ===, tenemos que usar las
funciones que vienen con Jasmine, las cuales son:
– expect(array).toContain(member);
– expect(fn).toThrow(string);
– expect(fn).toThrowError(string);
– expect(instance).toBe(instance);
– expect(mixed).toBeDefined();
– expect(mixed).toBeFalsy();
– expect(mixed).toBeNull();
– expect(mixed).toBeTruthy();
– expect(mixed).toBeUndefined();
– expect(mixed).toEqual(mixed);
– expect(mixed).toMatch(pattern);
– expect(number).toBeCloseTo(number, decimalPlaces);
– expect(number).toBeGreaterThan(number);
– expect(number).toBeLessThan(number);
– expect(number).toBeNaN();
– expect(spy).toHaveBeenCalled();
– expect(spy).toHaveBeenCalledTimes(number);
– expect(spy).toHaveBeenCalledWith(. . . arguments);
Jasmine también viene con funciones que se pueden ejecutar antes de realizar un test,
o después:
Por ejemplo:
beforeEach(() => {
57
expected = "Hello World";
});
afterEach(() => {
expected = "";
});
Si has creado el proyecto y los componentes usando Angular cli, te habrás dado cuenta
de que al generar un componente, también se crea un archivo .spec.ts, y eso es porque
Angular cli se encarga por nosotros de generar un archivo para testear cada uno de los
componentes. Además mete en el archivo el código necesario para empezar a probar
y testear los componentes. Por ejemplo, el archivo notes.component.spec.ts que se
creó cuando generé un componente para crear y mostrar notas tiene esta pinta:
describe('NotesComponent', () => {
let component: NotesComponent;
let fixture: ComponentFixture<NotesComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ NotesComponent ]
})
.compileComponents();
}));
58
beforeEach(() => {
fixture = TestBed.createComponent(NotesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
Lo primero que hace es crear una suite de tests para el componente con el método
describe. Tras crear la suite crea dos variables que va a necesitar para testear los
componentes, el propio componente, que lo mete en la variable component y una
variable fixture de tipo ComponentFixture del componente, la cual sirve para tener el
componente pero añadiendo más información para que sea más fácil de testear.
A contunuación llama al método beforeEach con una función asíncrona (sirve para
asegurar que se termina de ejecutarla función asíncrona antes de pasar un test) para
crear todas las dependencias del componente, en tese caso, el componente en sí. Si
usáramos en el componente un servicio, habría que incluirlo también, creando una
sección llamada providers (como en el app.module.ts).
Después vuelve a llamar a la función beforeEach, esta vez, sin ser asíncrona. Crea una
instancia fixture del componente usando TestBed, el cual se encargará de inyectar las
dependencias definidas anteriormente mediante configureTestingModule. Para sacar
el componente en sí del fixture usa componentInstance.
Por último crea un test para comprobar que el componente se crea correctamente,
para ello, llama a la función expect y espera que se cree bien y tenga error mediante
toBeTruthy().
Para correr los tests y ver los resultados con Angular cli el comando es:
ng test
59
Usando el servicio real
beforeEach(() => {
service = new AuthService();
component = new LoginComponent(service);
});
afterEach(() => {
localStorage.removeItem('token');
service = null;
component = null;
});
});
En este caso, a diferencia de la estructura que crea Angular cli, no estoy usando TestBed,
porque por el momento no me hace falta. Simplemente creo el componente y el servicio
y paso el servicio como parámetro al componente para que se inyecte mediante inyección
de dependencias. Cuando hago el test, simplemente llamo al método del componente y
hago la comprobación.
Esta técnica puede venir bien para aplicaciones pequeñas, pero si el componente necesita
muchas dependencias puede llegar a ser muy tedioso andar creando todos los servicios.
Además esto no favorece la encapsulación porque estamos creando servicios y no
60
estamos aislando el testeo del componente.
Además de esta forma, tenemos que meter a mano en el localStorage un valor para que
el authService funciona y devuelva true.
class MockAuthService {
authenticated = false;
isAuthenticated() {
return this.authenticated;
}
}
beforeEach(() => {
service = new MockAuthService();
component = new LoginComponent(service);
});
afterEach(() => {
service = null;
component = null;
});
61
});
Esta vez, en lugar de usar el authService real, creamos nuestra propia clase MockAuth-
Service dentro del propio test, la cual tendrá un método con el mismo nombre que el
servicio real, pero en su lugar devuelve el valor directamente.
Si aún asi crear el servicio virtual resulta costoso, siempre podemos extender del servicio
real, sobreescribiendo los métodos que nos interesen:
isAuthenticated() {
return this.authenticated;
}
}
TestBed.overrideComponent(
LoginComponent,
{set: {providers: [{provide: AuthService, useClass: MockAuthService}]}}
);
62
let spy: any;
beforeEach(() => {
service = new AuthService();
component = new LoginComponent(service);
});
afterEach(() => {
service = null;
component = null;
});
Como ves, con la función spyOn de Jasmine podemos hacer que el servicio devuelva
directamente true en la llamada a el nombre de función que le pasamos como parámetro
al spy.
Si por ejemplo tenemos un test que testea un método asíncrono del componente o del
servicio (una llamada a una API por ejemplo), podemos hacer lo siguiente:
63
Accediendo a la vista
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [LoginComponent]
});
submitButton = fixture.debugElement.query(By.css('button_submit'));
});
});
En este caso, accedemos al botón html de la vista del componente de login mediante el
debugElement del fixture, el cual nos da acceso a hacer querys de elementos html de la
vista.
El TestBed del componente nos proporciona una manera de provocar que la vista
de actualice con la nueva información en caso de que hayamos cambiado algo en el
componente:
64
fixture.detectChanges()
Para testear las llamdas HTTP podemos hacer dos tests, uno para comprobar que la
petición se ha realizado correctamente y otro test para verificar que la información que
llega de la API es correcta. Para lo segundo podemos usar la clase MockBackend de
Angular, que es capaz de simular un backend con información creada por nosotros, para
que cuando el servicio realice la llamada HTTP en realidad llame al MockBackend para
que no tenga ni que hacer la llamada real y podamos comprobar la información que
llega al servicio.
• https://angular-2-training-book.rangle.io/handout/testing/services/http.html
• https://medium.com/spektrakel-blog/angular-testing-snippets-httpclient-
d1dc2f035eb8
65
Algunos consejos para terminar
Antes de acabar me gustaría darte unos pequeños consejos que seguramente te resulten
útiles:
• Intentar tener más o menos clara la estructura que va a tener el proyecto, o por
lo menos decide hacia donde va a ir. También es importante que decidas si vas
a usar un gestor de estado tipo Ngrx o Ngxs. Es importante para no tener que
refactorizar más adelante todo el proyecto.
• Piensa en utilizar lo que ofrece Angular y antes de hacer nada mira si ya existe una
solución dentro del ecosistema de Angular. Por ejemplo si necesitas cambiar un
estilo CSS dinámicamente no uses el código Javascript como hasta ahora, utiliza lo
que ofrece Angular para cambiar estilos.
• Intenta utilizar los principios de Optimistic UI. Optimistic UI consiste en una manera
de mostrar la información sin esperar respuesta del servidor. Por ejemplo imagina
que estás desarrollando una app de chat en tiempo real. Si cada vez que envías un
mensaje tienes que esperar por la respuesta del servidor, la interfaz de usuario no
resultará inmediata.
Una forma de resolver esto para este ejemplo sería, pintar directamente el mensaje
en pantalla como si se hubiera enviado correctamente. Si la respuesta que llega del
servidor es errónea entonces muestras el error. Esto consigue que de la aparciencia
al usuario de que la aplicación funciona rápidisima.
66
tienes a tu disposición los tipos para las variables, lo que va a facilitar mucho su
depuración
• Si puedes usa algún precompilador de código css como scss, eso agilizará mucho
la creación de estilos.
• Crea tu propia librería de componentes. Esto es algo que empecé a usar hace poco.
Te creas una librería con los componentes que quieras y los subes a un repositorio.
Cuanto tengas que empezar un proyecto nuevo de Angular, siempre puedes bajarte
esa librería para tener componentes ya creados.
67
Conclusiones
Eso es todo, muchas gracias por haber dedicado tiempo a leer este libro. Espero que
te haya gustado el libro, o que al menos hayas aprendido algún concepto que antes
no sabías. He intentado proyectar en el libro todos mis conocimientos sobre Angular
explicados de forma sencilla.
Angular no termina aquí, todavía tiene más cosas, es un framework muy grande que
ofrece todo tipo de funcionalidades. Además según pasa el tiempo sacan nuevas
verisones con más funcionalidad todavía. Es por eso que te animo a que sigas aprendi-
endo. En el mundo de la programación es vital estar siempre en continuo aprendizaje y
mejora.
Con todo lo que has aprendido hasta ahora deberías ser capaz de crear aplicaciones web
con Angular completas. Te animo que sigas descubriendo y a que trates de enseñar a la
gente el potencial de este framework.
Como he dicho antes, muchas gracias por confiar en mí. Nos vemos en la próxima.
68
Copyright (c) 2019 Coding Potions
69