Alt, hvad du behøver at vide om ng-template, ng-content, ng-container og *ngTemplateOutlet i Angular
Det var en af de dage, hvor jeg havde travlt med at arbejde på nye funktioner til mit kontorprojekt. Pludselig fangede noget min opmærksomhed:
Mens jeg inspicerede DOM’en, så jeg ngcontent
, der blev anvendt på elementer af Angular. Hmm … hvis de indeholder elementerne i den endelige DOM, hvad er så brugen af <ng-container>
? På det tidspunkt blev jeg forvirret mellem <ng-container>
og <ng-content>
.
I min søgen efter at kende svarene på mine spørgsmål opdagede jeg begrebet <ng-template>
. Til min overraskelse fandtes der også *ngTemplateOutlet
. Jeg startede min rejse med at søge klarhed over to begreber, men nu havde jeg fire af dem, der næsten lød ens!
Har du nogensinde stået i denne situation? Hvis ja, så er du kommet til det rette sted. Så lad os uden videre tage dem en efter en.
<ng-template>
Som navnet antyder, er <ng-template>
et skabelonelement, som Angular bruger sammen med strukturelle direktiver (*ngIf
, *ngFor
, og brugerdefinerede direktiver).
Disse skabelonelementer fungerer kun i tilstedeværelsen af strukturelle direktiver. Angular pakker værtselementet (som direktivet anvendes på) ind i <ng-template>
og forbruger <ng-template>
i den færdige DOM ved at erstatte det med diagnostiske kommentarer.
Se et simpelt eksempel på *ngIf
:
Ovenstående er Angular-tolkningen af *ngIf
. Angular placerer det værtselement, som direktivet anvendes på, inden for <ng-template>
og beholder værtselementet som det er. Den endelige DOM ligner det, vi har set i begyndelsen af denne artikel:
Brug:
Vi har set, hvordan Angular bruger <ng-template>
, men hvad hvis vi ønsker at bruge det? Da disse elementer kun fungerer med et strukturdirektiv, kan vi skrive som:
Her er home
en boolean
-egenskab for komponenten sat til true
-værdi. Output af ovenstående kode i DOM:
Ingen blev renderet! 🙁
Men hvorfor kan vi ikke se vores meddelelse, selv efter at vi har brugt <ng-template>
korrekt med et strukturdirektiv?
Dette var det forventede resultat. Som vi allerede har diskuteret, erstatter Angular <ng-template>
med diagnostiske kommentarer. Der er ingen tvivl om, at ovenstående kode ikke ville generere nogen fejl, da Angular er helt i orden med dit brugsscenarie. Du ville aldrig få at vide, hvad der præcist skete bag kulisserne.
Lad os sammenligne de to ovenstående DOM’er, der blev gengivet af Angular:
Hvis du ser godt efter, er der et ekstra kommentartag i den endelige DOM i eksempel 2. Den kode, som Angular fortolkede, var:
Angular pakkede din vært <ng-template>
ind i en anden <ng-template>
og konverterede ikke kun den ydre <ng-template>
til diagnostiske kommentarer, men også den indre! Derfor kunne du ikke se nogen af dine meddelelser.
For at slippe af med dette er der to måder at få det ønskede resultat på:
Metode 1:
I denne metode forsyner du Angular med det afsukrede format, der ikke behøver yderligere behandling. Denne gang vil Angular kun konvertere <ng-template>
til kommentarer, men lader indholdet indeni det forblive uberørt (de er ikke længere inde i nogen <ng-template>
, som de var i det foregående tilfælde). Det vil således gengive indholdet korrekt.
For at vide mere om, hvordan du bruger dette format med andre strukturelle direktiver, henvises til denne artikel.
Metode 2:
Dette er et ganske uset format og bruges sjældent (ved hjælp af to søskende <ng-template>
). Her giver vi en skabelonreference til *ngIf
i dens then
for at fortælle den, hvilken skabelon der skal bruges, hvis betingelsen er sand.
Det anbefales ikke at bruge flere <ng-template>
på denne måde (du kan bruge <ng-container>
i stedet), da det ikke er det, de er beregnet til. De bruges som en container til skabeloner, der kan genbruges flere steder. Vi vil dække mere om dette i et senere afsnit af denne artikel.
<ng-container>
Har du nogensinde skrevet eller set kode, der ligner dette:
Grunden til, at mange af os skriver denne kode, er den manglende mulighed for at bruge flere strukturelle direktiver på et enkelt værtselement i Angular. Nu virker denne kode fint, men den introducerer flere ekstra tomme <div>
i DOM’en, hvis item.id
er en falsy-værdi, som måske ikke er nødvendig.
Man er måske ikke bekymret for et simpelt eksempel som dette, men for en stor applikation, der har en kompleks DOM (til visning af titusindvis af data), kan dette blive problematisk, da elementerne måske har lyttere knyttet til dem, som stadig vil være der i DOM’en og lytte til begivenheder.
Det, der er endnu værre, er det niveau af indlejring, som du skal gøre for at anvende din styling (CSS)!
Ingen bekymringer, vi har <ng-container>
til undsætning!
Angular <ng-container>
er et grupperingselement, der ikke forstyrrer stilarter eller layout, fordi Angular ikke placerer det i DOM’en.
Så hvis vi skriver vores Eksempel 1 med <ng-container>
:
Vi får den endelige DOM som: <ng-container>
Vi får den endelige DOM som:
Se, at vi er kommet af med de tomme <div>
s. Vi bør bruge <ng-container>
, når vi blot ønsker at anvende flere strukturelle direktiver uden at indføre et ekstra element i vores DOM.
For flere oplysninger henvises til dokumentationen. Der er et andet anvendelsestilfælde, hvor det bruges til at injicere en skabelon dynamisk i en side. Jeg vil dække denne use case i det sidste afsnit af denne artikel.
<ng-content>
De bruges til at oprette konfigurerbare komponenter. Det betyder, at komponenterne kan konfigureres afhængigt af brugerens behov. Dette er velkendt som Content Projection (indholdsprojektion). Komponenter, der anvendes i offentliggjorte biblioteker, gør brug af <ng-content>
for at gøre sig konfigurerbare.
Se på en simpel <project-content>
-komponent:
Det HTML-indhold, der er videregivet inden for åbnings- og lukketags i <project-content>
-komponenten, er det indhold, der skal projiceres. Det er det, vi kalder indholdsprojektion. Indholdet vil blive gengivet inden for <ng-content>
i komponenten. Dette giver forbrugeren af <project-content>
-komponenten mulighed for at videregive enhver brugerdefineret sidefod inden for komponenten og styre præcis, hvordan de ønsker, at den skal gengives.
Multiple Projections:
Hvad nu hvis du kunne bestemme, hvilket indhold der skal placeres hvor? I stedet for at alt indhold projiceres inden for en enkelt <ng-content>
, kan du også styre, hvordan indholdet skal projiceres med select
-attributten for <ng-content>
. Det kræver en elementvælger at bestemme, hvilket indhold der skal projiceres inden for en bestemt <ng-content>
.
Her er hvordan:
Vi har ændret <project-content>
-definitionen til at udføre projektion af flere indhold. select
-attributten vælger den type indhold, der skal gengives inden for en bestemt <ng-content>
. Her har vi først select
til at gengive header h1
-elementet. Hvis det projicerede indhold ikke har noget h1
-element, vil det ikke gengive noget. På samme måde leder den anden select
efter et div
. Resten af indholdet bliver gengivet inde i den sidste <ng-content>
uden select
.
Kaldet af komponenten vil se ud som:
*ngTemplateOutlet
…De bruges som en container til skabeloner, der kan genbruges flere steder. Vi vil dække mere om dette i et senere afsnit af denne artikel.
…Der er et andet anvendelsestilfælde, hvor den bruges til at injicere en skabelon dynamisk i en side. Jeg vil dække dette anvendelsestilfælde i det sidste afsnit af denne artikel.
Dette er det afsnit, hvor vi vil diskutere de to ovennævnte to punkter, der er nævnt før. *ngTemplateOutlet
bruges til to scenarier – til at indsætte en fælles skabelon i forskellige sektioner af en visning uanset sløjfer eller tilstand og til at lave en meget konfigureret komponent.
Skabelongenbrug:
Tænk på en visning, hvor du skal indsætte en skabelon flere steder. F.eks. et firmalogo, der skal placeres på et websted. Vi kan opnå det ved at skrive skabelonen til logoet én gang og genbruge den overalt i visningen.
Følgende er kodestumpen:
Som du kan se, har vi blot skrevet logoskabelonen én gang og brugt den tre gange på den samme side med en enkelt kodelinje!
*ngTemplateOutlet
accepterer også et context-objekt, som kan overdrages for at tilpasse den fælles skabelonudgang. For flere oplysninger om context-objektet henvises til den officielle dokumentation.
Anpasselige komponenter:
Den anden brugssituation for *ngTemplateOutlet
er stærkt tilpassede komponenter. Overvej vores tidligere eksempel på <project-content>
komponent med nogle ændringer:
Ovenfor er den modificerede version af <project-content>
komponenten, der accepterer tre inputegenskaber – headerTemplate
, bodyTemplate
, footerTemplate
. Følgende er uddraget for project-content.ts
:
Det, vi forsøger at opnå her, er at vise header, body og footer som modtaget fra den overordnede komponent i <project-content>
. Hvis en af dem ikke leveres, vil vores komponent vise standardskabelonen i stedet for den. Dermed skabes en meget tilpasset komponent.
For at bruge vores nyligt modificerede komponent:
Det er sådan her, vi vil videregive skabelonrefs til vores komponent. Hvis en af dem ikke overføres, vil komponenten gengive standardskabelonen.
ng-content vs. *ngTemplateOutlet
De hjælper os begge med at opnå meget tilpassede komponenter, men hvilken skal vi vælge og hvornår?
Det kan tydeligt ses, at *ngTemplateOutlet
giver os nogle flere muligheder for at vise standardskabelonen, hvis der ikke er angivet nogen.
Det er ikke tilfældet med ng-content
. Den gengiver indholdet som det er. Du kan højst opdele indholdet og gengive dem forskellige steder i din visning ved hjælp af select
-attributten. Du kan ikke gengive indholdet betinget inden for ng-content
. Du er nødt til at vise det indhold, der er modtaget fra den overordnede, uden mulighed for at træffe beslutninger baseret på indholdet.
Det afhænger imidlertid helt af din brugssituation, om du skal vælge mellem de to. Nu har vi i det mindste fået et nyt våben *ngTemplateOutlet
i vores arsenal, som giver mere kontrol over indholdet ud over funktionerne i ng-content
!