00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Direttive

Cosa sono le Direttive

Le direttive sono classi che aggiungono comportamento agli elementi nel template Angular. Esistono tre tipi di direttive:

  1. Direttive strutturali: modificano la struttura del DOM aggiungendo o rimuovendo elementi (*ngIf, *ngFor, *ngSwitch)
  2. Direttive di attributo: modificano l’aspetto o il comportamento di un elemento esistente (ngClass, ngStyle)
  3. Componenti: tecnicamente sono direttive con un template associato

Direttive Strutturali

*ngIf

La direttiva *ngIf aggiunge o rimuove un elemento dal DOM in base a una condizione.

import { Component } from '@angular/core';
import { NgIf } from '@angular/common';

@Component({
  selector: 'app-esempio-ngif',
  standalone: true,
  imports: [NgIf],
  template: `
    <!-- Uso base -->
    <p *ngIf="isVisibile">Questo paragrafo è visibile</p>

    <!-- Con else -->
    <div *ngIf="isAutenticato; else loginTemplate">
      <p>Benvenuto, {{ nomeUtente }}!</p>
      <button (click)="logout()">Logout</button>
    </div>
    <ng-template #loginTemplate>
      <button (click)="login()">Effettua il Login</button>
    </ng-template>

    <!-- Con then e else -->
    <div *ngIf="caricamento; then spinnerTemplate; else contenutoTemplate"></div>
    <ng-template #spinnerTemplate>
      <div class="spinner">Caricamento...</div>
    </ng-template>
    <ng-template #contenutoTemplate>
      <div class="contenuto">Dati caricati!</div>
    </ng-template>

    <!-- Con variabile (as) -->
    <div *ngIf="utente$ | async as utente">
      <h2>{{ utente.nome }}</h2>
      <p>{{ utente.email }}</p>
    </div>
  `
})
export class EsempioNgIfComponent {
  isVisibile = true;
  isAutenticato = false;
  nomeUtente = 'Mario';
  caricamento = true;

  login(): void { this.isAutenticato = true; }
  logout(): void { this.isAutenticato = false; }
}

*ngFor

La direttiva *ngFor ripete un elemento per ogni item in una collezione.

import { Component } from '@angular/core';
import { NgFor } from '@angular/common';

interface Prodotto {
  id: number;
  nome: string;
  prezzo: number;
  categoria: string;
}

@Component({
  selector: 'app-lista-prodotti',
  standalone: true,
  imports: [NgFor],
  template: `
    <!-- Uso base -->
    <ul>
      <li *ngFor="let frutto of frutti">{{ frutto }}</li>
    </ul>

    <!-- Con indice e variabili contestuali -->
    <div *ngFor="let prodotto of prodotti; let i = index;
         let primo = first; let ultimo = last;
         let pari = even; let dispari = odd;
         trackBy: trackById">
      <div [class.primo]="primo"
           [class.ultimo]="ultimo"
           [class.pari]="pari"
           [class.dispari]="dispari">
        {{ i + 1 }}. {{ prodotto.nome }} - {{ prodotto.prezzo }}€
      </div>
    </div>

    <!-- ngFor annidato -->
    <div *ngFor="let categoria of categorie">
      <h3>{{ categoria.nome }}</h3>
      <ul>
        <li *ngFor="let item of categoria.items">
          {{ item }}
        </li>
      </ul>
    </div>
  `
})
export class ListaProdottiComponent {
  frutti = ['Mela', 'Banana', 'Arancia', 'Pera'];

  prodotti: Prodotto[] = [
    { id: 1, nome: 'Laptop', prezzo: 999, categoria: 'Elettronica' },
    { id: 2, nome: 'Tastiera', prezzo: 79, categoria: 'Elettronica' },
    { id: 3, nome: 'Libro', prezzo: 25, categoria: 'Cultura' }
  ];

  categorie = [
    { nome: 'Frutta', items: ['Mela', 'Banana'] },
    { nome: 'Verdura', items: ['Carota', 'Spinaci'] }
  ];

  // trackBy migliora le prestazioni evitando di ricreare elementi DOM
  trackById(index: number, prodotto: Prodotto): number {
    return prodotto.id;
  }
}

*ngSwitch

La direttiva *ngSwitch mostra un solo elemento tra diversi in base a una condizione.

import { Component } from '@angular/core';
import { NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common';

@Component({
  selector: 'app-stato-ordine',
  standalone: true,
  imports: [NgSwitch, NgSwitchCase, NgSwitchDefault],
  template: `
    <div [ngSwitch]="statoOrdine">
      <div *ngSwitchCase="'nuovo'" class="badge blu">
        Nuovo ordine - In attesa di elaborazione
      </div>
      <div *ngSwitchCase="'in_lavorazione'" class="badge giallo">
        Ordine in lavorazione
      </div>
      <div *ngSwitchCase="'spedito'" class="badge arancione">
        Ordine spedito - In transito
      </div>
      <div *ngSwitchCase="'consegnato'" class="badge verde">
        Ordine consegnato con successo
      </div>
      <div *ngSwitchCase="'annullato'" class="badge rosso">
        Ordine annullato
      </div>
      <div *ngSwitchDefault class="badge grigio">
        Stato sconosciuto
      </div>
    </div>

    <!-- Selettore per cambiare stato -->
    <select [(ngModel)]="statoOrdine">
      <option value="nuovo">Nuovo</option>
      <option value="in_lavorazione">In Lavorazione</option>
      <option value="spedito">Spedito</option>
      <option value="consegnato">Consegnato</option>
      <option value="annullato">Annullato</option>
    </select>
  `
})
export class StatoOrdineComponent {
  statoOrdine = 'nuovo';
}

Direttive di Attributo

ngClass

La direttiva ngClass permette di aggiungere o rimuovere classi CSS in modo dinamico.

import { Component } from '@angular/core';
import { NgClass } from '@angular/common';

@Component({
  selector: 'app-esempio-ngclass',
  standalone: true,
  imports: [NgClass],
  template: `
    <!-- Stringa singola -->
    <div [ngClass]="'classe-attiva'">Classe singola</div>

    <!-- Array di classi -->
    <div [ngClass]="['bordo', 'ombra', 'rounded']">Classi multiple</div>

    <!-- Oggetto con condizioni -->
    <div [ngClass]="{
      'attivo': isAttivo,
      'disabilitato': isDisabilitato,
      'evidenziato': isEvidenziato,
      'errore': haErrore
    }">
      Classi condizionali
    </div>

    <!-- Combinazione con metodo -->
    <div
      *ngFor="let tab of tabs; let i = index"
      [ngClass]="getClassiTab(i)"
      (click)="selezionaTab(i)"
    >
      {{ tab.nome }}
    </div>
  `,
  styles: [`
    .attivo { background-color: #3b82f6; color: white; }
    .disabilitato { opacity: 0.5; pointer-events: none; }
    .evidenziato { border: 2px solid gold; }
    .errore { border-color: red; color: red; }
  `]
})
export class EsempioNgClassComponent {
  isAttivo = true;
  isDisabilitato = false;
  isEvidenziato = false;
  haErrore = false;
  tabSelezionata = 0;

  tabs = [
    { nome: 'Home' },
    { nome: 'Prodotti' },
    { nome: 'Contatti' }
  ];

  selezionaTab(indice: number): void {
    this.tabSelezionata = indice;
  }

  getClassiTab(indice: number): Record<string, boolean> {
    return {
      'tab': true,
      'tab-attiva': indice === this.tabSelezionata,
      'tab-prima': indice === 0,
      'tab-ultima': indice === this.tabs.length - 1
    };
  }
}

ngStyle

La direttiva ngStyle permette di impostare stili inline dinamicamente.

import { Component } from '@angular/core';
import { NgStyle } from '@angular/common';

@Component({
  selector: 'app-esempio-ngstyle',
  standalone: true,
  imports: [NgStyle],
  template: `
    <!-- Oggetto di stili -->
    <div [ngStyle]="{
      'background-color': colore,
      'padding': padding + 'px',
      'font-size': dimensione + 'px',
      'border-radius': '8px'
    }">
      Testo stilizzato dinamicamente
    </div>

    <!-- Barra di progresso -->
    <div class="barra-container">
      <div
        class="barra-progresso"
        [ngStyle]="{
          'width': progresso + '%',
          'background-color': getColoreProgresso()
        }"
      >
        {{ progresso }}%
      </div>
    </div>

    <!-- Controlli -->
    <input type="color" [(ngModel)]="colore">
    <input type="range" [(ngModel)]="progresso" min="0" max="100">
  `
})
export class EsempioNgStyleComponent {
  colore = '#3b82f6';
  padding = 16;
  dimensione = 16;
  progresso = 45;

  getColoreProgresso(): string {
    if (this.progresso < 30) return '#ef4444';
    if (this.progresso < 70) return '#f59e0b';
    return '#22c55e';
  }
}

Direttive Personalizzate

Direttiva di Attributo Custom

Le direttive di attributo modificano l’aspetto o il comportamento di un elemento esistente.

import { Directive, ElementRef, HostListener, Input, OnInit } from '@angular/core';

@Directive({
  selector: '[appEvidenzia]',
  standalone: true
})
export class EvidenziaDirective implements OnInit {
  @Input() appEvidenzia = '#ffffcc'; // colore di default
  @Input() coloreTestoEvidenziato = '#333';

  private coloreSfondoOriginale = '';
  private coloreTestoOriginale = '';

  constructor(private el: ElementRef<HTMLElement>) {}

  ngOnInit(): void {
    this.coloreSfondoOriginale = this.el.nativeElement.style.backgroundColor;
    this.coloreTestoOriginale = this.el.nativeElement.style.color;
  }

  @HostListener('mouseenter')
  onMouseEnter(): void {
    this.evidenzia(this.appEvidenzia, this.coloreTestoEvidenziato);
  }

  @HostListener('mouseleave')
  onMouseLeave(): void {
    this.evidenzia(this.coloreSfondoOriginale, this.coloreTestoOriginale);
  }

  private evidenzia(coloreSfondo: string, coloreTesto: string): void {
    this.el.nativeElement.style.backgroundColor = coloreSfondo;
    this.el.nativeElement.style.color = coloreTesto;
    this.el.nativeElement.style.transition = 'all 0.3s ease';
  }
}

Utilizzo:

<!-- Con colore di default -->
<p appEvidenzia>Passa il mouse qui</p>

<!-- Con colore personalizzato -->
<p [appEvidenzia]="'#bfdbfe'" coloreTestoEvidenziato="#1e40af">
  Evidenziazione blu
</p>

Direttiva Strutturale Custom

Le direttive strutturali creano o rimuovono elementi dal DOM.

import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';

// Direttiva *appRipeti: ripete un elemento N volte
@Directive({
  selector: '[appRipeti]',
  standalone: true
})
export class RipetiDirective {
  @Input() set appRipeti(volte: number) {
    this.viewContainer.clear();
    for (let i = 0; i < volte; i++) {
      this.viewContainer.createEmbeddedView(this.templateRef, {
        $implicit: i,
        indice: i,
        primo: i === 0,
        ultimo: i === volte - 1
      });
    }
  }

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef
  ) {}
}

Utilizzo:

<!-- Ripete l'elemento 5 volte -->
<div *appRipeti="5; let i">
  Elemento {{ i + 1 }}
</div>

<!-- Con variabili contestuali -->
<p *appRipeti="3; let idx = indice; let isFirst = primo; let isLast = ultimo">
  Indice: {{ idx }}
  {{ isFirst ? '(primo)' : '' }}
  {{ isLast ? '(ultimo)' : '' }}
</p>

Direttiva per Permessi

Un esempio pratico di direttiva strutturale per gestire i permessi:

import { Directive, Input, TemplateRef, ViewContainerRef, OnInit } from '@angular/core';
import { AuthService } from '../services/auth.service';

@Directive({
  selector: '[appHaPermesso]',
  standalone: true
})
export class HaPermessoDirective implements OnInit {
  @Input() appHaPermesso = '';
  private haVista = false;

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef,
    private authService: AuthService
  ) {}

  ngOnInit(): void {
    const haPermesso = this.authService.haPermesso(this.appHaPermesso);

    if (haPermesso && !this.haVista) {
      this.viewContainer.createEmbeddedView(this.templateRef);
      this.haVista = true;
    } else if (!haPermesso && this.haVista) {
      this.viewContainer.clear();
      this.haVista = false;
    }
  }
}
<!-- Mostra solo se l'utente ha il permesso 'admin' -->
<button *appHaPermesso="'admin'">
  Pannello Amministrazione
</button>

<!-- Mostra solo se l'utente ha il permesso 'editor' -->
<div *appHaPermesso="'editor'">
  <h3>Modifica Contenuto</h3>
</div>

Nuova Sintassi vs Direttive Classiche

A partire da Angular 17, la nuova sintassi di controllo del flusso è raccomandata al posto delle direttive strutturali:

<!-- Vecchia sintassi (ancora supportata) -->
<div *ngIf="condizione">Contenuto</div>
<div *ngFor="let item of lista; trackBy: trackById">{{ item.nome }}</div>

<!-- Nuova sintassi (Angular 17+, raccomandata) -->
@if (condizione) {
  <div>Contenuto</div>
}
@for (item of lista; track item.id) {
  <div>{{ item.nome }}</div>
}

La nuova sintassi offre: migliori prestazioni, nessuna necessità di importare direttive, sintassi @empty per liste vuote e type narrowing automatico.

Best Practices

  • Usa la nuova sintassi di controllo del flusso (@if, @for, @switch) nei progetti Angular 17+
  • Usa sempre trackBy con *ngFor (o track con @for) per ottimizzare il rendering delle liste
  • Preferisci [class.nome] per singole classi condizionali e [ngClass] per logica complessa
  • Non applicare due direttive strutturali sullo stesso elemento: usa <ng-container> come wrapper
  • Le direttive personalizzate dovrebbero essere piccole e focalizzate su un singolo comportamento
  • Usa HostListener e HostBinding nelle direttive per interagire con l’elemento host
  • Testa le direttive personalizzate separatamente con test unitari