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

Componenti

Cosa sono i Componenti

I componenti sono i mattoni fondamentali di ogni applicazione Angular. Ogni componente è una classe TypeScript decorata con @Component che controlla una porzione della UI. Un componente è composto da tre parti principali: la classe (logica), il template (vista) e gli stili (aspetto).

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

@Component({
  selector: 'app-saluto',
  standalone: true,
  template: `<h1>Ciao, {{ nome }}!</h1>`,
  styles: [`h1 { color: #3b82f6; }`]
})
export class SalutoComponent {
  nome = 'Mondo';
}

Il Decoratore @Component

Il decoratore @Component accetta un oggetto di metadati che definisce il comportamento del componente.

Proprietà principali

@Component({
  // Il selettore CSS usato per inserire il componente nel template
  selector: 'app-profilo',

  // Indica che il componente è standalone (non necessita di NgModule)
  standalone: true,

  // Importazioni di altri componenti, direttive e pipe
  imports: [CommonModule, RouterLink],

  // Template inline o riferimento a file esterno
  template: `<div>Template inline</div>`,
  // oppure
  templateUrl: './profilo.component.html',

  // Stili inline o riferimento a file esterno
  styles: [`div { padding: 1rem; }`],
  // oppure
  styleUrl: './profilo.component.scss',

  // Strategia di change detection
  changeDetection: ChangeDetectionStrategy.OnPush,

  // Incapsulamento degli stili
  encapsulation: ViewEncapsulation.Emulated
})
export class ProfiloComponent {}

View Encapsulation

Angular supporta tre modalità di incapsulamento degli stili:

import { Component, ViewEncapsulation } from '@angular/core';

// Emulated (default): emula lo Shadow DOM con attributi
@Component({
  encapsulation: ViewEncapsulation.Emulated,
  styles: [`p { color: red; }`] // Applicato solo a questo componente
})

// ShadowDom: usa il vero Shadow DOM del browser
@Component({
  encapsulation: ViewEncapsulation.ShadowDom,
  styles: [`p { color: blue; }`]
})

// None: stili globali, nessun incapsulamento
@Component({
  encapsulation: ViewEncapsulation.None,
  styles: [`p { color: green; }`] // Applicato globalmente
})

Standalone Components

A partire da Angular 14 (e default da Angular 17), i componenti standalone non necessitano di essere dichiarati in un NgModule. Sono autonomi e dichiarano esplicitamente le proprie dipendenze.

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';
import { CardComponent } from '../card/card.component';

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [CommonModule, RouterLink, CardComponent],
  template: `
    <h2>Dashboard</h2>
    @for (item of items; track item.id) {
      <app-card [titolo]="item.nome" />
    }
    <a routerLink="/impostazioni">Impostazioni</a>
  `
})
export class DashboardComponent {
  items = [
    { id: 1, nome: 'Vendite' },
    { id: 2, nome: 'Utenti' },
    { id: 3, nome: 'Ordini' }
  ];
}

Input e Output

I decoratori @Input e @Output permettono la comunicazione tra componenti padre e figlio.

@Input - Passare dati al figlio

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-card-prodotto',
  standalone: true,
  template: `
    <div class="card">
      <h3>{{ nome }}</h3>
      <p class="prezzo">{{ prezzo | currency:'EUR' }}</p>
      @if (inOfferta) {
        <span class="badge">In Offerta!</span>
      }
    </div>
  `
})
export class CardProdottoComponent {
  @Input() nome = '';
  @Input() prezzo = 0;
  @Input() inOfferta = false;

  // Input obbligatorio (Angular 16+)
  @Input({ required: true }) id!: number;

  // Input con alias
  @Input({ alias: 'descrizione-prodotto' }) descrizione = '';

  // Input con trasformazione (Angular 16+)
  @Input({ transform: booleanAttribute }) attivo = false;
}

Utilizzo nel componente padre:

<app-card-prodotto
  [id]="1"
  [nome]="'Laptop'"
  [prezzo]="999.99"
  [inOfferta]="true"
/>

@Output - Emettere eventi al padre

import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-contatore',
  standalone: true,
  template: `
    <div class="contatore">
      <button (click)="decrementa()">-</button>
      <span>{{ valore }}</span>
      <button (click)="incrementa()">+</button>
    </div>
  `
})
export class ContatoreComponent {
  valore = 0;

  @Output() valoreModificato = new EventEmitter<number>();
  @Output() limiteRaggiunto = new EventEmitter<string>();

  incrementa(): void {
    this.valore++;
    this.valoreModificato.emit(this.valore);
    if (this.valore >= 10) {
      this.limiteRaggiunto.emit('Limite massimo raggiunto');
    }
  }

  decrementa(): void {
    if (this.valore > 0) {
      this.valore--;
      this.valoreModificato.emit(this.valore);
    }
  }
}

Utilizzo nel padre:

<app-contatore
  (valoreModificato)="onValoreModificato($event)"
  (limiteRaggiunto)="mostraAvviso($event)"
/>

Content Projection con ng-content

La content projection permette di inserire contenuto dal componente padre nel template del figlio.

// Componente Card riutilizzabile
@Component({
  selector: 'app-card',
  standalone: true,
  template: `
    <div class="card">
      <div class="card-header">
        <ng-content select="[header]" />
      </div>
      <div class="card-body">
        <ng-content />
      </div>
      <div class="card-footer">
        <ng-content select="[footer]" />
      </div>
    </div>
  `
})
export class CardComponent {}
<!-- Utilizzo con content projection -->
<app-card>
  <h3 header>Titolo della Card</h3>
  <p>Questo contenuto va nel body della card.</p>
  <p>Anche questo paragrafo va nel body.</p>
  <button footer>Azione</button>
</app-card>

Lifecycle Hooks

Angular offre diversi hook del ciclo di vita che permettono di eseguire logica in momenti specifici della vita di un componente.

import {
  Component, OnInit, OnDestroy, OnChanges,
  AfterViewInit, Input, SimpleChanges
} from '@angular/core';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-utente',
  standalone: true,
  template: `<p>Utente: {{ nome }}</p>`
})
export class UtenteComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy {
  @Input() nome = '';
  private sottoscrizione?: Subscription;

  // Chiamato quando un @Input cambia valore
  ngOnChanges(changes: SimpleChanges): void {
    if (changes['nome']) {
      console.log(
        'Nome cambiato da',
        changes['nome'].previousValue,
        'a',
        changes['nome'].currentValue
      );
    }
  }

  // Chiamato una volta dopo la creazione del componente
  ngOnInit(): void {
    console.log('Componente inizializzato');
    // Ideale per: fetch dati, sottoscrizioni iniziali
  }

  // Chiamato dopo che la vista del componente è stata inizializzata
  ngAfterViewInit(): void {
    console.log('Vista inizializzata');
    // Ideale per: accedere a elementi del DOM, inizializzare librerie esterne
  }

  // Chiamato quando il componente viene distrutto
  ngOnDestroy(): void {
    console.log('Componente distrutto');
    // Fondamentale: cancellare sottoscrizioni, timer, listener
    this.sottoscrizione?.unsubscribe();
  }
}

Ordine dei Lifecycle Hooks

Hook Quando viene chiamato
ngOnChanges Ad ogni cambio di un @Input (anche prima di ngOnInit)
ngOnInit Una volta, dopo il primo ngOnChanges
ngDoCheck Ad ogni ciclo di change detection
ngAfterContentInit Dopo la proiezione del contenuto (ng-content)
ngAfterContentChecked Dopo ogni verifica del contenuto proiettato
ngAfterViewInit Dopo l’inizializzazione della vista e delle viste figlie
ngAfterViewChecked Dopo ogni verifica della vista
ngOnDestroy Immediatamente prima della distruzione del componente

Accesso agli Elementi del Template

@ViewChild e @ViewChildren

import { Component, ViewChild, ViewChildren, ElementRef, QueryList, AfterViewInit } from '@angular/core';
import { CardComponent } from './card.component';

@Component({
  selector: 'app-galleria',
  standalone: true,
  imports: [CardComponent],
  template: `
    <input #campoRicerca type="text" placeholder="Cerca...">
    @for (item of items; track item.id) {
      <app-card [titolo]="item.nome" />
    }
  `
})
export class GalleriaComponent implements AfterViewInit {
  items = [{ id: 1, nome: 'Item 1' }, { id: 2, nome: 'Item 2' }];

  // Riferimento a un elemento del template
  @ViewChild('campoRicerca') campoRicerca!: ElementRef<HTMLInputElement>;

  // Riferimento a tutti i componenti CardComponent figli
  @ViewChildren(CardComponent) cards!: QueryList<CardComponent>;

  ngAfterViewInit(): void {
    // Ora possiamo accedere agli elementi
    this.campoRicerca.nativeElement.focus();
    console.log(`Trovate ${this.cards.length} card`);
  }
}

Comunicazione tra Componenti

Riepilogo dei pattern

// 1. Padre -> Figlio: @Input
// Nel padre:
<app-figlio [messaggio]="'Ciao'" />

// 2. Figlio -> Padre: @Output + EventEmitter
// Nel figlio:
@Output() evento = new EventEmitter<string>();
// Nel padre:
<app-figlio (evento)="gestisci($event)" />

// 3. Tra componenti non correlati: Servizio condiviso
@Injectable({ providedIn: 'root' })
export class MessaggioService {
  private messaggioSubject = new BehaviorSubject<string>('');
  messaggio$ = this.messaggioSubject.asObservable();

  inviaMessaggio(msg: string): void {
    this.messaggioSubject.next(msg);
  }
}

Best Practices

  • Mantieni i componenti piccoli e focalizzati su una singola responsabilità
  • Usa standalone components per i nuovi progetti (Angular 17+)
  • Preferisci OnPush change detection per migliori prestazioni
  • Cancella sempre le sottoscrizioni in ngOnDestroy per evitare memory leak
  • Usa @Input({ required: true }) per input obbligatori
  • Evita logica complessa nei template: spostala nella classe del componente
  • Usa content projection (ng-content) per componenti wrapper riutilizzabili
  • Separa template e stili in file esterni per componenti con template complessi