En esta serie vamos a explorar a fondo las Directivas de Angular. Si acabas de aterrizar aquí, quizás te convenga echarle una ojeada a esta guía de iniciación sobre las Directivas. Si todo esto de Angular te suena a chino 🙄, puede que este post con las claves para entenderlo te ayude.
ngIf y ngFor: Recordatorio
Las Directivas (en inglés, Directives) pueden clasificarse en dos grupos: attribute y structural. Las directivas ngIf y ngFor son structural directives, porque cambian el DOM. Vamos a trabajar con una pequeña demo app para usarla como ejemplo durante todo este post.
1. Creamos un proyecto de Angular con ng new [nombre-proyecto].
2. Navegamos hasta la carpeta de nuestro proyecto y la abrimos en nuestro servidor local (en inglés, local server) con ng serve -o
4. Borramos el código con el que viene por defecto el archivo app.component.html y creamos un esqueleto HTML para esta demo-app.
5. Añadimos algo de lógica de programación al archivo TypeScript (en adelante, TS) del AppComponent (un array y una propiedad de tipo boolean).
numbers = [1, 2, 3, 4, 5]; onlyOdd = false;
Verás que la template es muy sencilla, mostrando un botón y un campo vacío. Verás que el botón no hace nada, a pesar de tener escrita cierta lógica de programación en el archivo TS del AppComponent. Vamos a cambiar eso añadiendo un ngFor al <li> de la template, que iterará nuestro array numbers.
6. Mostramos el resultado usando la variable local del loop mediante string interpolation.
Con estos cambios, ya deberías ver una lista de números (el array) en tu navegador. ¡Chachi! Aunque el botón sigue sin hacer nada, porque no estamos utilizando la información de su click event. 😬 Podríamos intentar aplicar un ngIf sobre el mismo <li> y mostrar por ejemplo sólo los números impares, así:
Pero esto no funcionaría, porque Angular no permite usar más de una structural directive sobre un elemento. 👮 Así que no utilizaremos este enfoque.
7. En el app.component.ts, creamos dos arrays con código hardcoded con números pares e impares y comentamos el array numbers porque ya no lo vamos a necesitar.
// numbers = [1, 2, 3, 4, 5]; oddNumbers = [1, 3, 5]; evenNumbers = [2, 4];
8. En el app.component.html sustituimos los elementos del ngFor por uno de nuestros arrays, y replicamos el <li> pero con los datos del otro array. Para mostrar únicamente uno de los dos <li>, dependiendo si el número es par o impar, englobamos cada <li> en un <div> y le aplicamos el ngIf ahí.
Con esto cambios, nuestro botón ya funciona, mostrando sólo números pares o impares a cada click, aunque el nombre del botón deja de tener sentido. 😅
ngClass y ngStyle: Recordatorio
Estas directivas son attribute directives, y aunque no son las únicas, sí son las más famosas 😎. Vamos a recordar cómo funcionan.
1. Añadimos una clase en el archivo app.component.css que colorea los textos de rojo. La llamamos odd.
.odd { color: red; }
2. En el app.component.html, asignamos esta clase sólo si el número es impar (odd). Claro, el primer <li> siempre mostrará números impares, así que aquí estamos haciendo una doble comprobación que no es necesaria, porque resulta redundante. 🥴
3. Aplicamos el mismo concepto con ngClass al segundo <li>, el de los números pares. En este segundo caso, estamos diciendo: "aplica la clase odd cuando el número sea par, o sea, nunca". 🙃
Por otro lado, ngStyle nos permite también pasarle un objeto con una propiedad de CSS y un valor, que puede ser dinámico o estático. Vamos a añadir un valor dinámico, repitiendo la comprobación del ngClass, es decir, comprobando si el número es par o impar, aunque desde el principio sabemos cuál es el resultado en cada <li>.
Cómo crear nuestra propia attribute directive
Basta de recapitulaciones, es momento de crear una attribute directive, aunque sea muy básica. Esto nos permitirá entender mejor las built-in directives. Vamos a crear una directiva que subraye de color verde el elemento HTML al que se aplique. Podríamos hacer esto con clases de CSS, pero vamos a hacer lo mismo con nuestra propia directiva para practicar.
1. Creamos una nueva carpeta dentro de app, llamada basic-highlight, y dentro, un archivo llamado basic-highlight.directive.ts. Escribir "directive" no es obligatorio, sino una convención para distinguirlo rápidamente. 🧐
2. En el archivo basic-highlight.directive.ts exportamos una clase de TS a la que llamamos BasicHighlightDirective.
Para convertir la clase en directive, debemos añadir el decorador @Directive, que debemos importar desde angular/core. Al decorador debemos pasarle un objeto para configurar la directiva. Al objeto no le puede faltar un selector, porque recuerda que las directivas son añadidas a elementos HTML, por tanto, deben tener un nombre con el que poder incluirlas.
Ese nombre debe ser único, y en camelCase.
import { Directive } from '@angular/core'; @Directive({ selector: 'appBasicHighlight' }) export class BasicHighlightDirective { }
Si lo dejásemos así, habríamos creado un elemento HTML, es decir, podríamos utilizar la directiva así:
3. Pero eso no es lo que queremos. Necesitamos que sea de tipo attribute para poder usarla como un atributo típico de HTML. Para eso únicamente tenemos que añadir corchetes al selector, y así Angular la reconocerá como una attribute directive cuando la incluyamos en un elemento HTML.
selector: '[appBasicHighlight]'
Para aplicar los cambios que implementemos en nuestra directiva a un elemento HTML, debemos avisar a Angular que efectivamente queremos hacer cambios sobre un elemento HTML. Esto lo hacemos inyectando el potencial elemento HTML dentro de la directiva (dentro del archivo TS donde configuramos nuestra directiva). 💉
La inyección se lleva a cabo en el constructor. Lo que hacemos es obtener una referencia al elemento HTML sobre el que se aplicará la directiva. Para eso tenemos un data type específico: el ElementRef.
Para poder usar esa referencia en la clase de TS (en la directiva, vaya) podemos usar un "shortcut" de TS: la palabra clave "private". Esto convertirá el parámetro del constructor en una propiedad de TS.
export class BasicHighlightDirective { constructor(private elementRef: ElementRef) { } }
Con estos pasos ya tenemos acceso al potencial elemento HTML, así que ya podemos manipularlo. Un buen sitio para hacerlo es en el método ngOnInit, cuya interface debemos importar.
4. Sobrescribimos un estilo del elementRef, el backgroundColor, por ejemplo.
export class BasicHighlightDirective implements OnInit { constructor(private elementRef: ElementRef) {} ngOnInit() { this.elementRef.nativeElement.style.backgroundColor = 'green'; } }
Con estos cambios, ¡ya podemos usar nuestra directiva! Pero primero debemos avisar a Angular de su existencia (en las declarations del app.module.ts con su correspondiente import).
import { BasicHighlightDirective } from './basic-highlight/basic-highlight.directive'; @NgModule({ declarations: [ AppComponent, BasicHighlightDirective
5. Usamos la directiva en el app.component.html. Podemos aplicarla a un <p> debajo de la <ul>, por ejemplo.
¡Y ya lo tenemos!
💢 Aunque esto funciona perfectamente, no es una buena práctica manipular el DOM de esta manera. De esto hablé en un post relacionado con el tema del @ViewChild. En la siguiente sección vamos a ver un enfoque más adecuado para acceder al DOM y cambiar igualmente una característica de un elemento HTML.
Cómo usar el Renderer para construir una attribute directive mejorada
Lo dicho: acceder al style de un elemento HTML como hemos hecho hasta ahora no es una buena práctica. Entre otros motivos, porque Angular es capaz de ejecutar una template sin su DOM, por tanto, ciertas propiedades podrían no estar disponibles en ese momento. Es lo que sucedería con el uso de los service workers, un uso avanzado que veremos en otro post.
En cualquier caso, para curarnos en salud, lo mejor es no acceder al DOM de esta manera. Porque Angular nos ofrece otra más adecuada, como es usando el Renderer. Vamos a construir otra attribute directive para probar este nuevo concepto.
1. A la altura de la carpeta basic-highlight creamos otra carpeta llamada better-highlight, y dentro, una directiva. Usaré la CLI esta vez, con el comando:
ng generate directive better-highlight --skipTests
2. En el archivo better-highlight.directive.ts inyectamos el renderer en el constructor como parámetro, que con la palabra clave private adoptará la forma de propiedad de TS. El renderer es un data type de Angular de tipo Renderer2, así que se lo indicamos y lo importamos desde angular/core.
3. Lo usamos en el método ngOnInit, cuya interface debemos importar desde angular/core para poder usarla.
4. Dentro del ngOnInit llamamos al renderer, que nos ofrece varios métodos para trabajar con el DOM. Uno de ellos es el setStyle, que nos permite definir estilos de un elemento HTML. Para acceder a dicho elemento, lo hacemos igual que en el directiva basic-highlight: con el ElementRef. Lo añadimos como otro parámetro del constructor, igual que antes. Lo llamamos eleRef, por ejemplo.
Ahora desde el setStyle ya podemos acceder al elemento usando el eleRef, aunque lo que queremos es acceder al elemento HTML subyacente (en inglés, underlying element), así que usamos la propiedad nativeElement. SetStyle admite 4 argumentos:
- el elemento al que queremos aplicarle estilos (el nativeElement del ElementRef)
- el estilo que queremos aplicar (como background-color, padding, margin, etc)
- el valor de la propiedad CSS que queramos (como blue, 10px, etc)
- un flags object (opcional) - como !important
En nuestro caso vamos a darle sólo 3 argumentos, dándole un background-color azul, por ejemplo.
import { Directive, Renderer2, OnInit, ElementRef } from '@angular/core'; @Directive({ selector: '[appBetterHighlight]' }) export class BetterHighlightDirective implements OnInit { constructor(private eleRef: ElementRef, private renderer: Renderer2) { } ngOnInit() { this.renderer.setStyle(this.eleRef.nativeElement, 'background-color', 'blue'); } }
5. Probamos nuestra directiva recién creada en el app.component.html, debajo del <p>.
¡Y listo! Nuestra directiva ya funciona, y el párrafo de arriba debería tener un fondo azul. ¡Genial! 😊
Este enfoque es mucho mejor que el tomado en la directiva basic-highlight, entre otras cosas, porque Angular no está limitado a ejecutarse en el navegador, como menciono más arriba, pudiendo funcionar por ejemplo con service workers. Los service workers son entornos donde es posible no tener acceso al DOM. Así que si usásemos el enfoque de la basic-highlight, podríamos incurrir en errores.
Si necesitas ampliar información sobre el Renderer, aquí tienes la documentación oficial de Angular sobre el tema.
THE END!
¡Y hasta aquí esta primera parte de esta guía avanzada sobre Directivas! Espero que hayas aprendido algo nuevo 😊. Si te queda alguna duda, ¡nos vemos en los comentarios! O puedes pasar directamente a la parte #2 para seguir aprendiendo (disponible próximamente).
Sobre la autora de este post
Soy Rocío, una abogada reconvertida en programadora. Soy una apasionada de aprender cosas nuevas y ferviente defensora de que la única manera de ser feliz es alcanzando un equilibrio entre lo que te encanta hacer y lo que te saque de pobre. Mi historia completa, aquí.
Más recursos de aprendizaje
En mi experiencia, la manera más eficaz para aprender Angular es combinando varias vías de aprendizaje. Uno de mis métodos favoritos son los vídeo-cursos y mi plataforma predilecta para eso es Udemy. He hecho varios cursos pero sólo recomiendo aquellos que verdaderamente me han sido útiles. Aquí van:
Si necesitas apoyo en forma de libro, puede que éstos te sirvan de ayuda:
La programación es un mundo que evoluciona a una velocidad de vértigo. Los autores de estos libros lo saben, por eso suelen encargarse de actualizar su contenido regularmente. Asegúrate de que así sea antes de adquirirlos .
Participo en el programa de afiliados de Udemy y Amazon, lo que significa que, si compras alguno de estos cursos y/o libros, yo me llevaré una pequeña comisión y a ti no costará nada extra. Vamos, lo que se dice un win-win .
Otros artículos que pueden interesarte