back to top

Content Projection: come personalizzare i componenti con ng-content

Nelle precedenti lezioni abbiamo cominciato a vedere quale strumenti vengono messi a disposizione da Angular per lavorare con i template dei componenti. Abbiamo iniziato a creare delle strutture statiche di componenti annidati per poi illustrare in che modo un componente può comunicare con i propri discendenti e viceversa attraverso i meccanismi del binding delle proprietà e degli eventi.

Quello dei template in Angular è un argomento vasto e sono davvero tanti i costrutti sintattici che è possibile utilizzare per realizzare delle applicazioni complesse. Continuiamo ad analizzare dei nuovi metodi per lavorare con i template e in particolare mostriamo come definire dei componenti altamente configurabili attraverso una tecnica che prende il nome di Content Projection. Per chi ha avuto modo di lavorare con AngularJS troverà tale meccanismo abbastanza familiare dato che si tratta dell’erede di quello che nella prima versione del framework prendeva il nome di Transclusion. Cambia la terminologia e la sintassi, ma il concetto rimane lo stesso, ovvero inserire nei template dei nostri componenti degli slot in cui iniettare dall’esterno un contenuto personalizzato (elementi HTML o altri componenti). Cerchiamo di chiarire meglio cosa intendiamo attraverso un esempio.

ng content schema funzionamento

Consideriamo per semplicità due soli componenti ParentComponent e ChildComponent.

tree src/app
src/app
├── app.module.ts
├── child.component.ts
└── parent.component.ts

0 directories, 3 files

All’interno del file child.component.ts definiamo il componente ChildComponent.

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

@Component({
  selector: 'app-child',
  template: `
    <div>
      <ng-content></ng-content>
    </div>
  `,
  styles: []
})
export class ChildComponent {}

Si tratta di un componente abbastanza semplice che non presenta nessuna proprietà. All’interno del suo template abbiamo però inserito <ng-content>, un costrutto messo a disposizione da Angular per rendere un componente configurabile e facilmente riutilizzabile. Infatti <ng-content></ng-content> crea una sorta di alloggiamento all’interno del quale andrà a finire il contenuto passato fra i due tag nel momento in cui andiamo ad usare il componente app-child. Infatti quando inseriamo l’elemento <app-child> all’interno del template del componente ParentComponent possiamo specificare quali elementi devono essere inseriti nel template di ChildComponent al posto di <ng-content></ng-content>.

Vediamo quindi cosa contiene il file parent.component.ts relativo al componente ParentComponent.

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

@Component({
  selector: 'app-root',
  template: `
    <app-child>
      <h1>{{ title }}</h1>
      <p>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit.
      </p>
    </app-child>
  `
})
export class ParentComponent {
  title = 'Esempio ng-content';
}

All’interno del template di ParentComponent inseriamo i tag di apertura e chiusura <app-child> che racchiudono un paragrafo e un’intestazione di primo livello. Questi due elementi verranno inseriti nel template del componente ChildComponent al posto di <ng-content>.

struttura dom applicazione angular ng content

Ovviamente avremmo potuto racchiudere fra i tag di <app-child> anche uno o più elementi relativi a componenti da noi definiti.

Creiamo quindi un nuovo componente RandomComponent come mostrato nel frammento di codice sottostante.

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

@Component({
  selector: 'app-random',
  template: `
    <div>
      <span>{{ random }}</span>
      <button (click)="onClick()">Genera numero casuale</button>
    </div>
  `,
  styles: []
})
export class RandomComponent {
  random = 0;

  onClick(): void {
    this.random = Math.floor(Math.random() * 100);
  }
}

Il componente RandomComponent presenta un pulsante che al click del mouse genera un numero intero casuale nell’intervallo [0-100), ovvero un numero n tale che 0 <= n < 100.

Modifichiamo quindi il template del componente AppComponent.

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

@Component({
  selector: 'app-root',
  template: `
    <app-child>
      <h1>{{ title }}</h1>
      <app-random></app-random>
    </app-child>
  `
})
export class ParentComponent {
  title = 'Esempio ng-content';
}

Il DOM della nostra applicazione conterrà quindi i seguenti elementi.

esempio struttura DOM di un'applicazione angular ng content

Slot multipli

A volte può essere utile creare più slot in cui inserire il contenuto passato fra i tag di un elemento. Per questo motivo possiamo utilizzare più volte <ng-content> all’interno del template di un componente e sfruttare il suo attributo select per decidere quale elemento finirà in una determinata area. Questo attributo accetta un selettore CSS (nome-elemento, .nome-classe, ecc…) per selezionare gli elementi che si vogliono inserire in un certo slot. Possiamo comunque aggiungere un <ng-content> senza attributo select e verrà utilizzato come destinazione di tutti gli elementi che non sono selezionati da nessun altro <ng-content>.

Vediamo anche in questo caso un esempio per meglio illustrare quanto appena affermato. Modifichiamo quindi ParentComponent e ChildComponent come segue.

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

@Component({
  selector: 'app-child',
  template: `
    <div class="heading">
      <ng-content select="h1"></ng-content>
    </div>
    <div class="main">
      <ng-content select=".text"></ng-content>
    </div>
    <div class="component">
      <ng-content select="app-random"></ng-content>
    </div>
    <div class="action">
      <ng-content></ng-content>
    </div>
  `
})
export class ChildComponent {}

All’interno del template di ChildComponent troviamo ben quattro <ng-content>, tre dei quali utilizzano l’attributo select per selezionare quale elemento deve essere inserito al loro posto.

All’interno di ParentComponent cambiamo invece il template e aggiungiamo vari elementi, alcuni dei quali rappresentano gli elementi riferiti dagli attributi select dei vari <ng-content> in ChildComponent.

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

@Component({
  selector: 'app-root',
  template: `
    <app-child>
      <button>Condividi</button>
      <span>Span element</span>
      <h1>{{ title }}</h1>
      <p class="text">
        Maecenas faucibus mollis interdum.
      </p>
      <app-random></app-random>
    </app-child>
  `
})
export class ParentComponent {
  title = 'Esempio ng-content';
}

I vari elementi racchiusi fra i tag <app-child> vengono distribuiti nei diversi <ng-content> in base all’attributo select di questi ultimi. I due elementi <button> e <span> finiscono nell’ordine in cui compaiono in ParentComponent all’interno dell’ultimo <ng-content>.

esempio ng content multipli

Riepilogo

In questa breve lezione abbiamo visto come rendere i nostri componenti altamente configurabili attraverso l’uso di <ng-content> che permette di iniettare all’interno del template di un componente un contenuto personalizzato, nel momento in cui viene utilizzato all’interno del template di un altro componente. Abbiamo visto che è anche possibile inserire più slot in diverse parti del template. Nella prossima lezione continueremo ad esplorare altri strumenti disponibili in Angular per lavorare con i template, in particolare inizieremo a illustrare alcune delle direttive predefinite più utili.

Pubblicitร