Tutto quello che dovete sapere su ng-template, ng-content, ng-container e *ngTemplateOutlet in Angular
Era uno di quei giorni in cui ero occupato a lavorare su nuove funzionalità per il mio progetto in ufficio. All’improvviso, qualcosa ha attirato la mia attenzione:
Durante l’ispezione del DOM ho visto il ngcontent
applicato da Angular sugli elementi. Hmm… se contengono gli elementi nel DOM finale, allora a cosa serve il <ng-container>
? In quel momento mi sono confuso tra <ng-container>
e <ng-content>
.
Nella ricerca delle risposte alle mie domande ho scoperto il concetto di <ng-template>
. Con mia grande sorpresa, c’era anche *ngTemplateOutlet
. Ho iniziato il mio viaggio cercando chiarezza su due concetti, ma ora ne avevo quattro, che suonavano quasi uguali!
Ti sei mai trovato in questa situazione? Se sì, allora siete nel posto giusto. Quindi, senza ulteriori indugi, prendiamoli uno per uno.
<ng-template>
Come suggerisce il nome, il <ng-template>
è un elemento template che Angular usa con direttive strutturali (*ngIf
, *ngFor
, e direttive personalizzate).
Questi elementi template funzionano solo in presenza di direttive strutturali. Angular avvolge l’elemento host (a cui viene applicata la direttiva) dentro <ng-template>
e consuma il <ng-template>
nel DOM finito sostituendolo con commenti diagnostici.
Considerate un semplice esempio di *ngIf
:
Viene mostrata sopra l’interpretazione Angular di *ngIf
. Angular mette l’elemento host a cui viene applicata la direttiva dentro <ng-template>
e mantiene l’host così com’è. Il DOM finale è simile a quello che abbiamo visto all’inizio di questo articolo:
Uso:
Abbiamo visto come Angular usa <ng-template>
ma se volessimo usarlo? Poiché questi elementi funzionano solo con una direttiva strutturale, possiamo scrivere come:
Qui home
è una proprietà boolean
del componente impostato al valore true
. L’output del codice di cui sopra in DOM:
Non è stato reso nulla! 🙁
Ma perché non possiamo vedere il nostro messaggio anche dopo aver usato <ng-template>
correttamente con una direttiva strutturale?
Questo era il risultato atteso. Come abbiamo già discusso, Angular sostituisce il <ng-template>
con commenti diagnostici. Senza dubbio il codice di cui sopra non genererebbe alcun errore, poiché Angular è perfettamente a posto con il vostro caso d’uso. Non riusciresti mai a sapere cosa è successo esattamente dietro le quinte.
Confrontiamo i due DOM di cui sopra che sono stati resi da Angular:
Se guardi attentamente, c’è un tag di commento extra nel DOM finale dell’Esempio 2. Il codice che Angular ha interpretato è stato:
Angular ha avvolto il tuo host <ng-template>
dentro un altro <ng-template>
e ha convertito non solo il <ng-template>
esterno in commenti diagnostici ma anche quello interno! Questo è il motivo per cui non potevi vedere nessuno dei tuoi messaggi.
Per sbarazzarti di questo ci sono due modi per ottenere il risultato desiderato:
Metodo 1:
In questo metodo, stai fornendo ad Angular il formato de-sugared che non ha bisogno di ulteriore elaborazione. Questa volta Angular convertirebbe solo <ng-template>
in commenti, ma lascia il contenuto al suo interno intatto (non sono più dentro nessun <ng-template>
come nel caso precedente). Così, renderà il contenuto correttamente.
Per saperne di più su come utilizzare questo formato con altre direttive strutturali, fate riferimento a questo articolo.
Metodo 2:
Questo è un formato abbastanza inedito e viene usato raramente (usando due <ng-template>
fratelli). Qui stiamo dando un riferimento al template *ngIf
nel suo then
per dirgli quale template dovrebbe essere usato se la condizione è vera.
L’uso di più <ng-template>
come questo non è consigliato (si potrebbe usare <ng-container>
invece) perché non è quello per cui sono fatti. Sono usati come un contenitore di modelli che possono essere riutilizzati in più posti. Ne parleremo meglio in una sezione successiva di questo articolo.
<ng-container>
Hai mai scritto o visto del codice simile a questo:
La ragione per cui molti di noi scrivono questo codice è l’impossibilità di usare più direttive strutturali su un singolo elemento ospite in Angular. Ora questo codice funziona bene ma introduce diversi <div>
vuoti extra nel DOM se item.id
è un valore falso che potrebbe non essere richiesto.
Potrebbe non essere preoccupato per un semplice esempio come questo, ma per un’applicazione enorme che ha un DOM complesso (per visualizzare decine di migliaia di dati) questo potrebbe diventare problematico in quanto gli elementi potrebbero avere degli ascoltatori attaccati a loro che saranno ancora lì nel DOM ad ascoltare gli eventi.
Quello che è ancora peggio è il livello di annidamento che si deve fare per applicare lo stile (CSS)!
Nessuna preoccupazione, abbiamo <ng-container>
in soccorso!
L’angolare <ng-container>
è un elemento di raggruppamento che non interferisce con stili o layout perché Angular non lo mette nel DOM.
Quindi se scriviamo il nostro Esempio 1 con <ng-container>
:
Abbiamo il DOM finale come:
Vedi che ci siamo liberati di quei <div>
vuoti. Dovremmo usare <ng-container>
quando vogliamo solo applicare più direttive strutturali senza introdurre alcun elemento extra nel nostro DOM.
Per maggiori informazioni fate riferimento alla documentazione. C’è un altro caso d’uso in cui viene utilizzato per iniettare dinamicamente un template in una pagina. Tratterò questo caso d’uso nell’ultima sezione di questo articolo.
<ng-content>
Sono usati per creare componenti configurabili. Questo significa che i componenti possono essere configurati a seconda delle esigenze del suo utente. Questo è ben noto come proiezione del contenuto. I componenti che sono usati nelle librerie pubblicate fanno uso di <ng-content>
per rendersi configurabili.
Considera un semplice componente <project-content>
:
Il contenuto HTML passato dentro i tag di apertura e chiusura del componente <project-content>
è il contenuto da proiettare. Questo è ciò che chiamiamo Proiezione del contenuto. Il contenuto sarà reso all’interno del <ng-content>
all’interno del componente. Questo permette all’utente del componente <project-content>
di passare qualsiasi piè di pagina personalizzato all’interno del componente e controllare esattamente come vogliono che sia reso.
Proiezioni multiple:
E se tu potessi decidere quale contenuto dovrebbe essere messo dove? Invece di ogni contenuto proiettato all’interno di un singolo <ng-content>
, puoi anche controllare come il contenuto verrà proiettato con l’attributo select
di <ng-content>
. Ci vuole un selettore di elementi per decidere quale contenuto proiettare all’interno di un particolare <ng-content>
.
Ecco come:
Abbiamo modificato la definizione <project-content>
per eseguire la proiezione multi-contenuto. L’attributo select
seleziona il tipo di contenuto che sarà reso all’interno di un particolare <ng-content>
. Qui abbiamo prima select
per rendere l’intestazione h1
dell’elemento. Se il contenuto proiettato non ha alcun elemento h1
non renderà nulla. Allo stesso modo il secondo select
cerca un div
. Il resto del contenuto viene reso all’interno dell’ultimo <ng-content>
senza select
.
Chiamare il componente sarà come:
*ngTemplateOutlet
…Sono usati come un contenitore di template che possono essere riutilizzati in più posti. Ne parleremo meglio in una sezione successiva di questo articolo.
…C’è un altro caso d’uso in cui viene utilizzato per iniettare dinamicamente un template in una pagina. Tratterò questo caso d’uso nell’ultima sezione di questo articolo.
Questa è la sezione dove discuteremo i due punti menzionati prima. *ngTemplateOutlet
è usato per due scenari – per inserire un template comune in varie sezioni di una vista indipendentemente dai cicli o dalle condizioni e per fare un componente altamente configurato.
Riutilizzo del template:
Considera una vista dove devi inserire un template in più punti. Per esempio, un logo aziendale da inserire all’interno di un sito web. Possiamo ottenerlo scrivendo il template per il logo una volta e riutilizzandolo ovunque all’interno della vista.
Segue lo snippet di codice:
Come potete vedere abbiamo appena scritto il template del logo una volta e lo abbiamo usato tre volte nella stessa pagina con una sola riga di codice!
*ngTemplateOutlet
accetta anche un oggetto context che può essere passato per personalizzare l’output comune del template. Per maggiori informazioni sull’oggetto contesto, fate riferimento alla documentazione ufficiale.
Componenti personalizzabili:
Il secondo caso d’uso di *ngTemplateOutlet
è costituito da componenti altamente personalizzati. Considerate il nostro precedente esempio di <project-content>
componente con alcune modifiche:
Sopra è la versione modificata del componente <project-content>
che accetta tre proprietà di input – headerTemplate
, bodyTemplate
, footerTemplate
. Di seguito lo snippet per project-content.ts
:
Quello che stiamo cercando di ottenere qui è di mostrare header, body e footer come ricevuti dal componente padre di <project-content>
. Se uno qualsiasi di essi non viene fornito, il nostro componente mostrerà il modello predefinito al suo posto. Quindi, creando un componente altamente personalizzato.
Per usare il nostro componente recentemente modificato:
Ecco come passeremo i riferimenti dei template al nostro componente. Se uno qualsiasi di essi non viene passato, allora il componente renderà il template di default.
ng-content vs. *ngTemplateOutlet
Entrambi ci aiutano ad ottenere componenti altamente personalizzati, ma quale scegliere e quando?
Si può vedere chiaramente che *ngTemplateOutlet
ci dà qualche potere in più nel mostrare il template di default se nessuno è fornito.
Questo non è il caso di ng-content
. Rende il contenuto così com’è. Al massimo potete dividere il contenuto e renderlo in diverse posizioni della vostra vista con l’aiuto dell’attributo select
. Non potete rendere condizionatamente il contenuto all’interno di ng-content
. Dovete mostrare il contenuto che viene ricevuto dal genitore senza mezzi per prendere decisioni basate sul contenuto.
Tuttavia, la scelta di selezionare tra i due dipende completamente dal vostro caso d’uso. Almeno ora abbiamo una nuova arma *ngTemplateOutlet
nel nostro arsenale che fornisce più controllo sul contenuto in aggiunta alle caratteristiche di ng-content
!