Directivas en Angular | Guía de iniciación

Última actualización: 1 noviembre, 2020

TEN EN CUENTA QUE...

En este artículo vamos a introducir un pilar básico de Angular: las directivas. Si acabas de aterrizar aquí y estos temas te suenan a chino, te recomiendo que eches un vistazo a esta introducción a Angular antes.

¿Qué son las directivas?

Las directivas (en inglés, directives), junto con los componentes, son una pieza clave para poder construir aplicaciones con Angular. Son, esencialmente, instrucciones para manipular el DOM. De hecho, los componentes son un tipo de directiva, ya que cuando usamos el selector de un componente, le estamos pidiendo a Angular que muestre dicho componente (y su lógica de programación) en algún lugar determinado del DOM

Por esta razón, podemos afirmar que los componentes son directives, pero con una template, es decir, con un archivo HTML detrás. Existen otras directivas sin template, como cualquier directive personalizada que podamos crear nosotros mismos (en inglés, custom directive).  

Un ejemplo de custom directive podría ser una directive que le diese a un párrafo un color azul de fondo. Su sintaxis sería así:

<p appTurnBlue>This text has a blue background</p>
Lo más común es añadir directivas como en el ejemplo de arriba, mediante atributos HTML, aunque no es la única manera. También podríamos añadirlas mediante una etiqueta HTML o una clase de CSS.

Para configurar nuestra directive, necesitaríamos crear un archivo TypeScript (en adelante, TS) y definirla ahí, donde añadiríamos la lógica de programación para añadir un fondo azul a un elemento: 

@Directive({
  selector: '[appTurnBlue]'
})
export class TurnBlueDirective {
  // business logic here
  // ...
}

🤓 En otro artículo de nivel más avanzado aprenderemos a crear nuestras propias directives (custom directives). Por ahora, es fundamental saber que Angular trae por defecto unas cuantas directives muy útiles (en inglés, built-in directives). Las podemos reconocer, entre otras cosas, por su prefijo "ng". ¡Vamos a verlas!

Ejemplo para nuestra demo

Para poder entender las diferentes directives que trae Angularvamos a crearnos un ejemplo sobre el que poder trabajar. Partiremos del mismo ejemplo que usé en el artículo sobre databindingasí que es vital que entiendas los conceptos básicos del databinding. Si ya los entiendes, puedes ir a esta sección de ese artículo y seguir los pasos para construir nuestra demo.

Después, vuelve aquí y prepárate para aprender sobre built-in directives. 💪

Tipos de directivas

attribute vs structural directives esquema

Aprendí sobre tipos de directivas y sobre otros temas de Angular en profundidad en este completo curso de Angular.

ngIf directive

Imaginemos que no queremos mostrar el mensaje que dice "No counter was created yet" cuando se inicia nuestra app. En su lugar, si el usuario no ha creado ningún contador, no mostramos nada. Pero si ha hecho click sobre el botón "Add Counter", mostraremos un mensaje que diga que un contador ha sido creado.

Es decir, queremos crear una condición. ¿Y cómo se crean condiciones en JavaScript? Exacto, con if statements, que tendrás perfectamente dominados si has hecho algún buen curso como éste de Shaun Pelling que hice yo. Angular nos ofrece una herramienta basada en los if statements: la directiva ngIf.

Vamos a modificar nuestro CountersComponent para reflejar ese cambio.

1. Comentamos la parte donde mostramos la propiedad allowNewCounter, (porque ya no la vamos a necesitar) y la propiedad counterCreationStatus.

2. Creamos un <p> nuevo con un mensaje tipo "A new counter was created, its name is: X", y le pasamos la propiedad counterName mediante string interpolation.

<!-- <p>{{ allowNewCounter }} </p>
<p [innerText]="allowNewCounter"></p> -->
<!-- <p>{{ counterCreationStatus }}</p> -->
<p>A new counter was created, its name is: {{ counterName}}</p>

Con estos cambios, verás que el two way databinding sigue funcionando, pero no resulta muy coherente que se muestre la frase "A new counter was created, its..." si ningún contador ha sido creado. Aquí es donde entra en juego la directiva ngIf.

sintaxis más común

*ngIf="condition"

La condición tiene que ser cualquier expresión que sea true false o que pueda resultar en true false.

sintaxis menos común

*ngIf="condition; else condition"

tipo

Structural directive, porque cambia la estructura del DOM, es decir, o incluye un elemento o no lo incluye. No está ocultoSimplemente, o existe o no existe en el DOM.


El asterisco inicial es lo que indica que es una structural directive.

3. Nos creamos una propiedad en el archivo counters.component.ts de tipo boolean, llamada counterCreated y le damos el valor de false. En el método onCreateCounter() le damos el valor de true, porque ahí el usuario ya ha creado un contador al hacer click sobre el botón y disparar así el evento y llamar al método.

  counterCreated = false;

  constructor() {
    setTimeout(() => {
      this.allowNewCounter = true;
    }, 2000);
   }

  ngOnInit() {
  }

  onCreateCounter() {
    this.counterCreationStatus = 'A counter was created! Its name is ' + this.counterName;
    this.counterCreated = true;
  }

4. En el counters.component.html vinculamos esa propiedad al ngIf.

<p *ngIf="counterCreated">A new counter was created, its name is: {{ counterName }}</p>

¡Y listo! Si guardas y vas al navegador, verás que no aparece ningún mensaje y que éste solo aparecerá si haces click en el botón para añadir un contador. 😃 Recuerda que esto y mucho más lo aprendí en este completo curso de Angular.

Para entender bien lo que significa que el ngIf es una directiva estructural, abre las dev tools y en la pestaña Elements, fíjate lo que pasa cuando introduces una palabra y haces click en el botón: el mensaje aparece en el DOM, cuando antes simplemente no existía. 😊

Añadiendo un else al ngIf

Podemos añadir un complemento a nuestra *ngIf para tener una alternativa en caso de que la condición vinculada al ngIf no se cumpla. En vanilla JavaScript (en adelante, JS), a esto se le conoce como un else statement. 

1. Siguiendo con nuestro ejemplo, en el archivo counters.component.html nos vamos a crear un bloque de código else que muestre un mensaje que represente lo opuesto al mensaje de nuestro ngIf, por ejemplo "No counter was created yet!".

Como decimos, sólo queremos mostrar ese mensaje si la propiedad counterCreated es false. Es decir, si el usuario aún no ha creado ningún contador haciendo click sobre el botón.

2. Para conseguir esto, vamos a crearnos algo nuevo, una local reference, en el bloque que queremos que actúe como alternativa al ngIf.

Una "local reference" actúa como un marcador en tu código. Lo representamos con el nombre que queramos y una almohadilla delante.

Ese "bloque alternativo" lo englobamos en una directiva que viene con Angular, la <ng-template> directive, que se utiliza para marcar puntos en el DOM, algo así como poner banderines 🚩 que complementan a la local reference. Ahí es donde la colocamos. 

3. Conectamos el <ng-template> con nuestro ngIf, añadiéndole una segunda parte con el else y haciendo referencia a nuestro marcador (el nombre sin la almohadilla)

<p *ngIf="counterCreated; else noCounter">
   A new counter was created, its name is: {{ counterName }}
</p>
<ng-template #noCounter>
  <p>No counter was created yet!</p>
</ng-template>

¡Y ya está! 👏 Ahora si vas al navegador, verás que el mensaje "No counter was created yet!" aparecerá hasta que escribas algo en el <input> y pulses el botón.

Dicho esto, podemos ahorrarnos la parafernalia del <ng-template> en este caso, porque podríamos haber creado simplemente otro párrafo con un segundo *ngIf, invirtiendo el valor del counterCreated. Así: 

<!-- <p *ngIf="counterCreated; else noCounter">A new counter was created, its name is: {{ counterName }}</p> -->
<p *ngIf="counterCreated">A new counter was created, its name is: {{ counterName }}</p>
<p *ngIf="!counterCreated">No counter was created yet!</p>
<!-- <ng-template #noCounter>
  <p>No counter was created yet!</p>
</ng-template> -->

ngStyle directive

Esta directiva nos permite añadir propiedades de CSS dinámicamente. Es decir, un margin, padding, color, etc.

sintaxis

[ngStyle]="{'two-words-property': expression}"

[ngStyle]="{singleWordProperty: expression}"

Los corchetes no son parte de la definición de esta directiva, sino que los añadimos para vincular esta directiva mediante property binding.

Esta directiva recibe un objeto JS, donde definimos key value pairs. Es decir, el nombre de la propiedad, por ejemplo color, y su valor, por ejemplo red. 

tipo

Attribute directive. Se llaman así porque parecen atributos comunes de HTML. Tampoco incluyen o eliminan elementos del DOM, sólo cambian el elemento HTML en el que se incluyen.

Para explicar esta directiva, vamos a trabajar en el archivo counter.component.ts. Recordemos que ahí mostrábamos el estado del contado (off). 

1. Supongamos que queremos mostrar el status de un contador (on o off) de manera aleatoria. Así que creamos un método constructor, y dentro, asignamos la propiedad counterStatus a un valor aleatorio (usando la función Math.random()) y usamos una ternary operation para asignarle el valor de on off según qué número nos salga de la operación aleatoria. 

  constructor() {
    this.counterStatus = Math.random() > 0.5 ? 'on' : 'off';
  }

Ahora, en el navegador verás que el status de nuestros contadores cambia aleatoriamente (recarga la página varias veces y verás como va cambiando, tanto el uno como el otro). Lo que podemos hacer ahora es asignar un color a cada mensaje (el <p> "Counter with ID 10 is currently...") dependiendo de si el status es on off. Aquí es donde entra en juego la directiva ngStyle. 

2. Para eso, añadimos la directiva en el <p> de nuestro archivo counter.component.html, igual que si fuera un atributo común del elemento <p>. Pero no podemos añadirla simplemente así:  

<p ngStyle>Server ID is {{ serverId }} and its status is {{ getServerStatus() }}</p>

Porque debemos utilizar property binding sobre esta directiva. Es decir, usamos la sintaxis del property binding, con los corchetes [ ]. Los corchetes indican que queremos vincular el contenido (que en este caso es una directiva) a una de sus propiedades. Y no por casualidad, la directiva  ngStyle tiene una propiedad con el mismo nombre. 😏

ngStyle recibe key-value pairsasí que vamos a pasarle un backgroundColor  y un valor para ese color, por ejemplo, red.

<p [ngStyle]="{backgroundColor: 'red'}">
  {{ 'Counter' }} with ID {{ counterId }} is currently {{ getCounterStatus() }}
</p>

Pero esto haría que el color de fondo fuese siempre rojo, y nosotros queremos que el color se asigne dinámicamente dependiendo del estado del contador (on off). Para eso, en el value, en lugar de darle un color, le pasamos un método al que llamaremos getColor().

<p [ngStyle]="{backgroundColor: getColor()}">
  {{ 'Counter' }} with ID {{ counterId }} is currently {{ getCounterStatus() }}
</p>

3. Vamos al archivo counter.component.ts y creamos ahí el método getColor()Queremos que el <p> tenga un fondo verde si el status es on y rojo si el status es off. 

  getColor() {
    return this.counterStatus === 'on' ? 'green' : 'red';
  }

¡Y ya lo tenemos! Prueba a recargar la página varias veces y verás como el color va cambiando dinámicamente según el estatus del contador. 😲 En este curso de Angular es donde aprendí a cambiar el color dinámicamente, entre otras muchas cosas.

ngStyle ejemplo

ngClass directive

Esta directiva está relacionada con la anterior, y nos permite añadir o quitar clases de CSS dinámicamente.

sintaxis

[ngClass]="{'two-words-class': true condition}"

[ngClass]="{oneWordClass: true condition}"

También tenemos que pasarle un objeto JSEse objeto acepta key-value pairs, donde la key es el nombre de la clase de CSS y el value es la condición que debe cumplirse (o sea, resultar en true) para que ese clase se añada.

También en este caso, los corchetes no son parte de la definición de esta directiva, sino que los añadimos para vincular esta directiva mediante property binding.

tipo

Attribute directive

Seguiremos trabajando en el CounterComponent para explicar esta directiva, donde crearemos una clase de CSS (en inglés, CSS class) que posteriormente añadiremos a la template del CounterComponent dinámicamente.

1. Vamos a crearnos el archivo counter.component.css (no olvides declararlo en counter.component.ts, 

styleUrls: ['./counter.component.css'] ) y una CSS class para poder trabajar.

La llamamos active, y la utilizaremos para cambiar el color del texto a blanco si el estado de un contado es on.

.active {
  color: white;
}

2. En el counter.component.html, añadimos la directiva en el mismo <p> donde tenemos el ngStyle. Creamos la condición directamente en el value del objeto JS, comprobando si el contador está on, condición que debe de cumplirse para que la clase active sea añadida. 

<p [ngStyle]="{backgroundColor: getColor()}"
   [ngClass]="{active: counterStatus === 'on'}">
   {{ 'Counter' }} with ID {{ counterId }} is currently {{ getCounterStatus() }}
</p>

🧐 Hemos hecho la comprobación directamente en el template, pero podríamos haberla hecho externamente, en el counter.component.ts. Al ser algo tan pequeñín, no hay problema con hacerlo directamente inline.

Ahora, si guardas y vas al navegador, verás que cuando el contador está on, el texto se vuelve blanco. Puedes inspeccionar el elemento con las dev tools para comprobar que la clase active solo se añade al <p> cuando el estatus es on¡Perfecto!

Concatenación

Tanto en el ngStyle como en el ngClass podemos aplicar más de una clase o de una propiedad CSS. Es decir, esto es totalmente válido:

<p [ngStyle]="{backgroundColor: getColor(),
               otraPropiedadCSS: otraFunción(),}"
   [ngClass]="{active: counterStatus === 'on',
               otraClaseCss: otraPropiedadTS === 'otro valor'}">
   {{ 'Counter' }} with ID {{ counterId }} is currently {{ getCounterStatus() }}
</p>

Ya que lo habitual en CSS en escribir las clases con guiones, en lugar de en camelCase, el snippet de arriba tendría más sentido así:

<p [ngStyle]="{backgroundColor: getColor(),
               otraPropiedadCSS: otraFunción(),}"
   [ngClass]="{active: counterStatus === 'on',
               'otra-clase-css': otraPropiedadTS === 'otro valor'}">
   {{ 'Counter' }} with ID {{ counterId }} is currently {{ getCounterStatus() }}
</p>

¡No olvides añadir las comillas!

ngFor directive

Tal y como está nuestra demo app, lo único que ocurre cuando añadimos un contador al hacer clic en el botón es que mostramos su nombre, pero en realidad no se está añadiendo a la lista de contadores que tenemos. Recuerda que esa "lista" no es más que dos selectores <app-counter> puestos uno debajo del otro.

Y es que la lista es estática, la hemos creado nosotros manualmente en el archivo counters.component.htmlPero podemos cambiar esto con la directiva ngFor.

Lo suyo sería tener un array de contadores que añadiese cada contador nuevo a la lista dinámicamente. 

1. Para eso, vamos al archivo counters.component.ts y creamos un array de contadores con por ejemplo dos nombres hardcoded

2. Al método onCreateCounter() le pasamos este array y le decimos que añada el nombre del counter que el usuario cree (counterName). Lo que queremos conseguir es replicar de algún modo el contenido del CounterComponent.

  counters = ['Work hours counter', 'Study hours counter'];

  constructor() {
    setTimeout(() => {
      this.allowNewCounter = true;
    }, 2000);
   }

  ngOnInit() {
  }

  onCreateCounter() {
    this.counterCreationStatus = 'A counter was created! Its name is ' + this.counterName;
    this.counterCreated = true;
    this.counters.push(this.counterName);
  }

3. En el archivo counters.component.html, eliminamos uno de los selectores <app-counter>, dejando solo uno.

sintaxis

*ngFor="let item of items"

o, dicho de otro modo:


*ngFor="let nameTemporaryVar of myArray"

Esto recorrerá todos los elementos de nuestro array.

sintaxis + complemento

*ngFor="let item of items; let i = index"

Añadiendo esta segunda parte a la directiva obtenemos acceso al índice de cada elemento, es decir, la i será 0 la primera vez que Angular recorra el array, 1 la segunda vez, etc.

La i es una variable local, así que podemos darle el nombre que queramos, pero llamarla i es lo más estándar. 

tipo

Structural directive

Sobre el <app-counter> que nos queda, le aplicamos la sintaxis del ngFor, en su modalidad más sencilla. Ésta se asemeja a la sintaxis del loop de JS for...of. 

<app-counter *ngFor="let counter of counters"></app-counter>

👀 "counter" es el nombre de nuestra variable temporal, que podemos llamarla como queramos, pero lo normal (y lo más lógico) es llamarla con el mismo nombre de nuestro array pero en singular.

¡Y ya lo tenemos! Ahora a cada click en el botón de "Add Counter", nuestra lista de contadores crece.

👍 💃 👌 🎉

ngFor + índice

Este complemento al ngFor sirve para obtener cierta información extra sobre nuestro loop, concretamente, el índice (en inglés, index) sobre la actual iteración (= vuelta) en la que se encuentre el loop. Hagamos un ejemplo para entenderlo bien. 

Supongamos que tenemos un botón que cada vez que lo pulsamos imprime la fecha en la que hemos hecho click. Lo hace a modo de lista, imprimiendo cada fecha una debajo de la otra. A partir del tercer click, la información sobre los clicks debe volverse blanca sobre un fondo azul.

Vamos a trabajar directamente en el AppComponent. 

1. En la template del AppComponentcreamos un botón y le añadimos un click event, vinculado a un método que posteriormente configuraremos. Lo llamamos displayClickDate().

<button class="btn btn-warning" (click)="displayClickDate()">
   Click me to see the time
</button>

2. En el TS del AppComponentcreamos un array vacío que usaremos para contar las veces que el usuario ha hecho click en el botón. Llamamos al array "clicks". 

3. En el método displayClickDate(), utilizamos el método new Date() y se lo pasamos a la propiedad clicks usando push().

export class AppComponent {
  clicks = [];

  displayClickDate() {
    this.clicks.push(new Date());
  }
}

4. Para mostrarlo en el navegador, volvemos al app.component.html y utilizamos ngFor sobre una etiqueta <li>, que podemos escribir debajo del botón. Utilizando string interpolation sobre la variable temporal del ngFor podremos ver la fecha en la que hemos hecho click sobre el botón, mostrándose a modo de lista una fecha nueva a cada click. 👌

      <li *ngFor="let click of clicks">{{ click }}</li>

5. Usando ngStylele pedimos que a partir del tercer click, la info salga con fondo azul.

<li *ngFor="let click of clicks"
    [ngStyle]="{backgroundColor: click > 2 ? 'blue': 'transparent'}">
    {{ click }}
</li>

Pero de esta manera, todos los elementos de la lista nos saldrán con fondo azul, y eso no es lo que queremos. Esto sucede porque la variable temporal click es un stringy no un número. Y en JS por definición, un string es siempre mayor que un número. Y eso hace que nuestra comprobación ( click > 2 ) siempre sea true. 🤓

Aquí es donde entra en juego la utilidad del index del ngFor. La variable local que establecemos (i = index) nos da el número (la vuelta) de la iteración en la que nos encontramos, por tanto, así es como podemos saber cuándo hemos llegado a 3ª vez para poder darle un fondo azul. Así que con esto ya podemos actualizar nuestro código:

<li *ngFor="let click of clicks; let i = index"
    [ngStyle]="{backgroundColor: i > 2 ? 'blue': 'transparent'}">
    {{ click }}
</li>

6. En cuanto al texto blanco, podemos usar ngClassPara eso, definimos una clase en el app.component.css.

.text-white {
  color: white;
}

Y seguimos la misma lógica sobre el tercer click, usando la variable local i.

<li *ngFor="let click of clicks,let i = index" 
    [ngStyle]="{backgroundColor: i > 2 ? 'blue': 'transparent'}"
    [ngClass]="{'text-white': i > 2}">
    {{ click }}    
</li>

¡Y voilà! 🤗

THE END!

¡Y con esto terminamos esta guía de iniciación a las directivas de Angular! Espero que hayas aprendido algo nuevo 😊.  Si te queda alguna duda, ¡nos vemos en los comentarios!

Si quieres ayudar a hacer este blog sostenible, puedes invitarme a un café digital ツ
¡Gracias!¡Gracias!

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:

angular the complete guide - curso Max S.

  Max Schwarzmüller

curso angular fernando herrera

   Fernando Herrera

Si necesitas apoyo en forma de libro, puede que éstos te sirvan de ayuda:

libro 1 angular
libro 2 angular

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

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

Otros artículos que pueden interesarte

Guía de iniciación al data binding en Angular
¿Qué es el databinding?El databinding es la forma que tiene Angular para permitirnos mostrar contenido dinámico en lugar de estático (en inglés, hardcoded). Podríamos[...]
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[...]
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