directivas guia avanzada - portada

Directivas en Angular – Guía avanzada – Parte #1

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 ngFor: Recordatorio

Las Directivas (en inglés, Directives) pueden clasificarse en dos grupos: attribute structural. Las directivas ngIf 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

3. Añadimos Bootstrap.

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. 

<div class="container my-4">
  <div class="row">
    <div class="col">
      <button class="btn btn-primary my-4" (click)="onlyOdd = !onlyOdd">
        Only show odd numbers
      </button>
      <ul class="list-group">
        <li class="list-group-item">
        </li>
      </ul>
    </div>
  </div>
</div>

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.

        <li class="list-group-item" *ngFor="let number of numbers">
          {{ number }}
        </li>

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

        <li class="list-group-item"
            *ngFor="let number of numbers"
            *ngIf="number % 2 == 0">

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í.

        <div *ngIf="onlyOdd">
          <li class="list-group-item" *ngFor="let oddNumber of oddNumbers">
            {{ oddNumber }}
          </li>
        </div>
        <div *ngIf="!onlyOdd">
          <li class="list-group-item" *ngFor="let evenNumber of evenNumbers">
            {{ evenNumber }}
          </li>
        </div>

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". 🙃

          <li class="list-group-item" *ngFor="let oddNumber of oddNumbers"
              [ngClass]="{odd: oddNumber % 2 !== 0}">
            {{ oddNumber }}
          </li>
        </div>
        <div *ngIf="!onlyOdd">
          <li class="list-group-item" *ngFor="let evenNumber of evenNumbers"
              [ngClass]="{odd: evenNumber % 2 !== 0}">
            {{ evenNumber }}
          </li>

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

          <li class="list-group-item" *ngFor="let oddNumber of oddNumbers"
              [ngClass]="{odd: oddNumber % 2 !== 0}"
              [ngStyle]="{backgroundColor: oddNumber % 2 !== 0 ? 'slateblue' : 'transparent'}">
            {{ oddNumber }}
          </li>
        </div>
        <div *ngIf="!onlyOdd">
          <li class="list-group-item" *ngFor="let evenNumber of evenNumbers"
              [ngClass]="{odd: evenNumber % 2 !== 0}"
              [ngStyle]="{backgroundColor: evenNumber % 2 !== 0 ? 'slateblue' : 'transparent'}">
            {{ evenNumber }}
          </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-highlighty 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 @Directiveque 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í:

<appBasicHighligh></appBasicHighligh>

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 ngOnInitcuya 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.

      <p appBasicHighlight>Style me with the Basic Highlight Directive!</p>

¡Y ya lo tenemos!

ejemplo custom attribute directive

💢 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 workersun 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 RendererVamos a construir otra attribute directive para probar este nuevo concepto.

1. A la altura de la carpeta basic-highlight creamos otra carpeta llamada better-highlighty 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 Renderer2así que se lo indicamos y lo importamos desde angular/core.

3. Lo usamos en el método ngOnInitcuya 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 ElementRefLo 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 eleRefaunque lo que queremos es acceder al elemento HTML subyacente (en inglés, underlying element), así que usamos la propiedad nativeElementSetStyle 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>.

      <p appBetterHighlight>Style me a better Directive!</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

Días del 353 al 386
Objetivos versus realidad Y nuevamente, llegó otro día clave. Llegó…y pasó. El pasado 4 de marzo este Reto Computer Geek[...]
Construye Minioland: Tu primera aplicación con Angular | Parte #1
¿Qué vamos a construir?Minioland, o así he decidido llamar a esta sencilla app, totalmente responsive y de tipo Single Page[...]
Angular: Entendiendo la Directiva ngModel
Angular es un framework que nos permite, entre otras cosas, añadir contenido dinámico a nuestros archivos HTML. Una de las formas[...]
Si crees que este post puede serle útil a alguien, por favor, ¡compártelo!:

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Como toda web legal que se precie, utilizamos cookies para asegurar que damos la mejor experiencia al usuario en nuestro sitio web. Si continúas utilizando este sitio asumiremos que estás de acuerdo. más información

Los ajustes de cookies de esta web están configurados para "permitir cookies" y así ofrecerte la mejor experiencia de navegación posible. Si sigues utilizando esta web sin cambiar tus ajustes de cookies o haces clic en "Aceptar" estarás dando tu consentimiento a esto.

Cerrar