Direttive
Cosa sono le Direttive
Le direttive sono classi che aggiungono comportamento agli elementi nel template Angular. Esistono tre tipi di direttive:
- Direttive strutturali: modificano la struttura del DOM aggiungendo o rimuovendo elementi (
*ngIf,*ngFor,*ngSwitch) - Direttive di attributo: modificano l’aspetto o il comportamento di un elemento esistente (
ngClass,ngStyle) - 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
trackBycon*ngFor(otrackcon@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
HostListenereHostBindingnelle direttive per interagire con l’elemento host - Testa le direttive personalizzate separatamente con test unitari