DEV Community

Cover image for 🧙‍♂️ Directivas en Angular: ¡Magia para tu HTML! 🪄
Cristian Arieta
Cristian Arieta

Posted on

🧙‍♂️ Directivas en Angular: ¡Magia para tu HTML! 🪄

👋 Vamos a explorar el fascinante mundo de las directivas - ¡esos pequeños hechizos que transforman vuestro HTML aburrido en interfaces interactivas y dinámicas!

🔮 Tipos de Directivas: ¡Conócelos todos!

Angular nos ofrece tres tipos mágicos de directivas (¡como si fueran las casas de Hogwarts! 😉):

  1. Directivas de Componentes 🏠 - ¡Son las más conocidas! Básicamente cualquier componente que creas.
   @Component({
     selector: 'app-magic',
     template: '<p>¡Abracadabra!</p>'
   })
Enter fullscreen mode Exit fullscreen mode

¿Por qué se consideran directivas? ¡Porque cuando usas <app-magic> en tu HTML, estás dando instrucciones al DOM!

  1. Directivas Estructurales 🧱 - Estas cambian la estructura de tu página como por arte de magia.
   <!-- Sintaxis tradicional con asterisco -->
   <div *ngIf="tienePermiso">¡Contenido secreto revelado!</div>
   <div *ngFor="let poción of pociones">{{ poción.nombre }}</div>

   <!-- Nueva sintaxis con @ (Angular 17+) -->
   @if (tienePermiso) {
     <div>¡Contenido secreto revelado!</div>
   }

   @for (poción of pociones; track poción.id) {
     <div>{{ poción.nombre }}</div>
   }
Enter fullscreen mode Exit fullscreen mode

¡Fíjate en el asterisco * o el nuevo @! Son como varitas mágicas que indican que algo va a aparecer o desaparecer.

🌈 Directivas Estructurales Integradas en Angular 🌈

1. @if / *ngIf 🚦

Esta directiva es como un guardia de seguridad que decide si un elemento puede entrar a la fiesta del DOM o no.

<!-- Sintaxis tradicional -->
<div *ngIf="usuario.esPremium">¡Contenido exclusivo para ti!</div>

<!-- Sintaxis moderna -->
@if (usuario.esPremium) {
  <div>¡Contenido exclusivo para ti!</div>
} @else {
  <div>¡Hazte premium para ver este contenido!</div>
}
Enter fullscreen mode Exit fullscreen mode

¡La nueva sintaxis @if/@else es mucho más clara que el viejo *ngIf con ng-template para el else!

2. @for / *ngFor 🔄

¡El mago duplicador! Toma un elemento y lo clona para cada item de una colección.

<!-- Sintaxis tradicional -->
<ul>
  <li *ngFor="let hechizo of hechizos; let i = index; trackBy: trackByFn">
    {{i+1}}. {{hechizo.nombre}}
  </li>
</ul>

<!-- Sintaxis moderna -->
<ul>
  @for (hechizo of hechizos; track hechizo.id; let i = $index) {
    <li>{{i+1}}. {{hechizo.nombre}}</li>
  } @empty {
    <li>¡No conoces ningún hechizo todavía!</li>
  }
</ul>
Enter fullscreen mode Exit fullscreen mode

¡La nueva sintaxis @for incluye @empty para cuando la lista está vacía! ¡Y el tracking es más sencillo!

3. @switch / *ngSwitch 🔀

Como un interruptor mágico que elige entre diferentes opciones.

<!-- Sintaxis tradicional -->
<div [ngSwitch]="poción.tipo">
  <div *ngSwitchCase="'curación'">Recupera 50 HP</div>
  <div *ngSwitchCase="'fuerza'">+10 de ataque</div>
  <div *ngSwitchDefault>Efecto desconocido</div>
</div>

<!-- Sintaxis moderna -->
@switch (poción.tipo) {
  @case ('curación') {
    <div>Recupera 50 HP</div>
  }
  @case ('fuerza') {
    <div>+10 de ataque</div>
  }
  @default {
    <div>Efecto desconocido</div>
  }
}
Enter fullscreen mode Exit fullscreen mode

¡La nueva sintaxis es mucho más parecida a un switch de JavaScript!

4. @defer 🚀 (¡Novedad en Angular 17!)

¡La directiva del futuro! Permite cargar contenido de forma perezosa o bajo demanda.

@defer {
  <!-- Contenido pesado que se cargará después -->
  <app-mapa-3d></app-mapa-3d>
} @loading {
  <p>Cargando mapa...</p>
} @error {
  <p>¡Ups! No se pudo cargar el mapa</p>
}

<!-- También podemos definir cuándo cargar -->
@defer (on viewport) {
  <app-galería-imágenes></app-galería-imágenes>
}

@defer (on timer(5s)) {
  <app-publicidad></app-publicidad>
}
Enter fullscreen mode Exit fullscreen mode

¡@defer es como magia avanzada! Permite cargar componentes solo cuando son necesarios, mejorando el rendimiento.

🎨 Directivas de Atributo Integradas 🎨

1. [ngClass] 👕

¡El estilista mágico! Añade o quita clases CSS según condiciones.

<div [ngClass]="{'mago-activo': usuario.conectado, 'mago-durmiente': !usuario.conectado}">
  Estado del mago
</div>

<!-- Equivalente a: -->
<div [class.mago-activo]="usuario.conectado" [class.mago-durmiente]="!usuario.conectado">
  Estado del mago
</div>
Enter fullscreen mode Exit fullscreen mode

2. [ngStyle] 🖌️

¡El pintor instantáneo! Aplica estilos CSS directamente.

<div [ngStyle]="{'color': hechizo.color, 'font-size.px': hechizo.poder * 5}">
  {{hechizo.nombre}}
</div>
Enter fullscreen mode Exit fullscreen mode

3. [ngModel] 📝

¡El escriba mágico! Crea un vínculo bidireccional entre un control de formulario y una variable.

<!-- Requiere FormsModule -->
<input [(ngModel)]="mago.nombre" placeholder="Nombre del mago">
<p>¡Saludos, {{mago.nombre}}!</p>
Enter fullscreen mode Exit fullscreen mode

¡El famoso "banana in a box" [(ngModel)]! Combina ngModel y (ngModelChange) (event binding).

🔨 ¡Creemos nuestra propia directiva! 🚀

Imaginemos que queremos una directiva que haga que los elementos brillen cuando pasamos el ratón encima. ¡Vamos a crearla!

Paso 1: Invocamos al generador mágico 🧙‍♂️

ng generate directive directives/brillo
# O la versión corta para magos avanzados
ng g d directives/brillo
Enter fullscreen mode Exit fullscreen mode

Paso 2: Preparamos nuestro hechizo ✨

@Directive({
  selector: '[appBrillo]'  // 👈 ¡Así lo invocaremos en nuestro HTML!
})
export class BrilloDirective {
  @Input() colorBrillo = 'gold';  // 🌟 Color predeterminado

  constructor(
    private el: ElementRef,      // 📌 Referencia al elemento
    private renderer: Renderer2  // 🎮 El control remoto del DOM
  ) {}

  @HostListener('mouseenter')  // 🐭 Cuando el ratón entra...
  onMouseEnter() {
    this._aplicarBrillo(this.colorBrillo);
  }

  @HostListener('mouseleave')  // 🐭 Cuando el ratón se va...
  onMouseLeave() {
    this._aplicarBrillo(null);  // 💨 Quitamos el brillo
  }

  private _aplicarBrillo(color: string | null) {
    // 🧙‍♂️ Magia pura: cambiamos el estilo sin tocar el DOM directamente
    this.renderer.setStyle(
      this.el.nativeElement,
      'box-shadow',
      color ? `0 0 10px ${color}` : null
    );
    this.renderer.setStyle(
      this.el.nativeElement,
      'transition',
      'box-shadow 0.3s'
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Paso 3: ¡A usarla! 🎉

<button appBrillo colorBrillo="purple">¡Este botón brilla en morado!</button>
<div appBrillo>¡Yo brillo en dorado (valor por defecto)!</div>
Enter fullscreen mode Exit fullscreen mode

🧱 Directivas Estructurales: ¡Construyendo y demoliendo! 🏗️

Las directivas estructurales son como arquitectos del DOM. Vamos a crear una que haga lo contrario que *ngIf (¡porque los rebeldes molan! 😎).

@Directive({
  selector: '[appUnless]'  // 👈 Se usará como *appUnless="condición"
})
export class UnlessDirective {
  private hasView = false;  // 👀 ¿Hemos creado ya la vista?

  constructor(
    private templateRef: TemplateRef<any>,    // 📝 La plantilla a mostrar
    private viewContainer: ViewContainerRef   // 🧪 El contenedor donde mostrarla
  ) {}

  @Input() set appUnless(condition: boolean) {
    // 🔄 Lógica invertida respecto a *ngIf
    if (!condition && !this.hasView) {
      // ✅ Si la condición es falsa Y no hemos creado la vista
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.hasView = true;
    } else if (condition && this.hasView) {
      // ❌ Si la condición es cierta Y tenemos una vista
      this.viewContainer.clear();  // 🧹 Limpiamos el contenedor
      this.hasView = false;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Uso (con ambas sintaxis):

<!-- Sintaxis tradicional -->
<p *appUnless="estáLloviendo">¡Vamos a la playa! 🏖️</p>

<!-- Si implementamos soporte para la nueva sintaxis -->
@unless (estáLloviendo) {
  <p>¡Vamos a la playa! 🏖️</p>
}
Enter fullscreen mode Exit fullscreen mode

🎯 Reglas de Oro del Profesor de Directivas 📚

  1. ¡No toques el DOM directamente! 🚫
   // ❌ NUNCA hagas esto:
   this.el.nativeElement.style.color = 'red';

   // ✅ Usa siempre el Renderer2:
   this.renderer.setStyle(this.el.nativeElement, 'color', 'red');
Enter fullscreen mode Exit fullscreen mode

¿Por qué? Porque Angular puede ejecutarse en plataformas donde el DOM no existe (SSR, web workers).

  1. Nombres con prefijos claros 📋
   // ❌ Mal: selector: '[highlight]'
   // ✅ Bien: selector: '[appHighlight]'
Enter fullscreen mode Exit fullscreen mode

Así evitamos colisiones con otras bibliotecas o directivas nativas.

  1. Una directiva = Una responsabilidad 🎯
   // ❌ Mal: Una directiva que cambia color Y añade animación Y modifica texto
   // ✅ Bien: Directivas separadas para cada función
Enter fullscreen mode Exit fullscreen mode

¡Divide y vencerás! Las directivas pequeñas son más reutilizables.

  1. Documenta tus creaciones mágicas 📝
   /**
    * Aplica un efecto de brillo cuando el usuario pasa el ratón sobre el elemento.
    * @param colorBrillo - Color del efecto (por defecto: gold)
    * @example <div appBrillo colorBrillo="blue">Contenido</div>
    */
Enter fullscreen mode Exit fullscreen mode

Tus compañeros (y tu yo del futuro) te lo agradecerán.

  1. Prueba tus hechizos antes de lanzarlos 🧪
   describe('BrilloDirective', () => {
     // Configuración de pruebas...

     it('debe añadir brillo al pasar el ratón', () => {
       triggerEventHandler('mouseenter', {});
       expect(element.style.boxShadow).toContain('gold');
     });
   });
Enter fullscreen mode Exit fullscreen mode

¡Un mago responsable siempre prueba sus hechizos!

🎓 ¡Tarea para casa! 📝

Ahora que ya sois aprendices de magos de las directivas, os propongo este desafío:

  1. Crea una directiva appCuentaClics que cuente cuántas veces se ha hecho clic en un elemento.
  2. Debe mostrar un pequeño contador en la esquina superior derecha.
  3. Bonus: Añade un método para reiniciar el contador.

¡Recordad compartir vuestros hechizos en la próxima clase! 🧙‍♂️✨

¿Preguntas? ¡Levantad vuestra mano mágica! 🪄

Top comments (0)