Minden, amit tudnod kell az ng-template, ng-content, ng-container és *ngTemplateOutlet Angularban
Egyike volt azoknak a napoknak, amikor az irodai projektem új funkcióin dolgoztam. Egyszer csak valami felkeltette a figyelmemet:
A DOM vizsgálata közben láttam, hogy az ngcontent
-ot az Angular alkalmazza az elemekre. Hmm… ha ezek tartalmazzák az elemeket a végleges DOM-ban, akkor mire jó a <ng-container>
? Akkoriban összezavarodtam a <ng-container>
és a <ng-content>
között.
A kérdésemre adott válaszok keresése közben felfedeztem a <ng-template>
fogalmát. Meglepetésemre ott volt a *ngTemplateOutlet
is. Az utazásomat úgy kezdtem, hogy két fogalomról kerestem tisztánlátást, de most már négy ilyen fogalom volt, amelyek majdnem ugyanúgy hangzottak!
Voltál már valaha ilyen helyzetben? Ha igen, akkor jó helyen jársz. Szóval minden további nélkül vegyük sorra őket.
<ng-template>
Amint a neve is mutatja, a <ng-template>
egy sablonelem, amelyet az Angular strukturális direktívákkal (*ngIf
, *ngFor
, és saját direktívákkal) együtt használ.
Ezek a sablonelemek csak strukturális direktívák jelenlétében működnek. Az Angular a gazdaelemet (amelyre a direktívát alkalmazza) <ng-template>
belsejébe csomagolja, és a <ng-template>
-t a kész DOM-ban diagnosztikai megjegyzésekkel helyettesítve fogyasztja el.
Nézzünk egy egyszerű példát a *ngIf
-ra:
A fenti képen a *ngIf
Angular értelmezése látható. Az Angular a <ng-template>
-be helyezi a host elemet, amelyre a direktívát alkalmazza, a hostot pedig változatlanul megtartja. A végleges DOM hasonló ahhoz, amit a cikk elején láttunk:
Használat:
Láttuk, hogyan használja az Angular az <ng-template>
-t, de mi van, ha mi szeretnénk használni? Mivel ezek az elemek csak szerkezeti direktívával működnek, így írhatjuk:
Itt home
a komponens boolean
tulajdonsága true
értékre állítva. A fenti kód kimenete a DOM-ban:
Nem lett renderelve semmi! 🙁
De miért nem látjuk az üzenetünket még akkor sem, ha helyesen használjuk a <ng-template>
szerkezeti direktívát?
Ez volt a várt eredmény. Ahogyan azt már megbeszéltük, az Angular a <ng-template>
-t diagnosztikai megjegyzésekkel helyettesíti. Kétségtelen, hogy a fenti kód nem generálna semmilyen hibát, mivel az Angular tökéletesen megfelel az Ön felhasználási esetének. Soha nem tudnánk meg, hogy pontosan mi történt a színfalak mögött.
Versenyezzük össze a fenti két DOM-ot, amelyet az Angular renderelt:
Ha jól figyelünk, a 2. példa végleges DOM-jában van egy extra komment tag. Az Angular által értelmezett kód:
Az Angular becsomagolta a gazdatest <ng-template>
-t egy másik <ng-template>
-be, és nemcsak a külső <ng-template>
-t, hanem a belsőt is diagnosztikai kommentárrá alakította! Ezért nem láthattad az üzenetedet.
Azért, hogy megszabadulj ettől, kétféleképpen érheted el a kívánt eredményt:
1. módszer:
Ezzel a módszerrel megadod az Angularnak a lecsupaszított formátumot, amely nem igényel további feldolgozást. Ezúttal az Angular csak a <ng-template>
-et alakítja át kommentárrá, de a benne lévő tartalmat érintetlenül hagyja (azok már nincsenek semmilyen <ng-template>
-ben, mint az előző esetben). Így helyesen rendereli a tartalmat.
Azzal kapcsolatban, hogy hogyan használjuk ezt a formátumot más szerkezeti direktívákkal, olvassuk el ezt a cikket.
2. módszer:
Ez egy meglehetősen ritkán látott és ritkán használt formátum (két testvér <ng-template>
használatával). Itt egy sablonreferenciát adunk a *ngIf
-nak a then
-ben, hogy megmondjuk neki, melyik sablont kell használni, ha a feltétel igaz.
A több <ng-template>
ilyen módon történő használata nem javasolt (használhatnánk helyette <ng-container>
-t), mivel nem erre szolgálnak. Olyan sablonok tárolására szolgálnak, amelyek több helyen újra felhasználhatók. Erről bővebben a cikk egy későbbi részében lesz szó.
<ng-container>
Írtál vagy láttál már ehhez hasonló kódot:
Az ok, amiért sokan ezt a kódot írják, az az, hogy az Angularban nem lehet több szerkezeti direktívát használni egyetlen host elemen. Most ez a kód jól működik, de több extra üres <div>
-et vezet be a DOM-ba, ha a item.id
egy falsy érték, amire nem biztos, hogy szükség van.
Egy ilyen egyszerű példánál talán nem kell aggódni, de egy hatalmas alkalmazásnál, amely komplex DOM-mal rendelkezik (több tízezer adat megjelenítéséhez), ez problémás lehet, mivel az elemekhez lehet, hogy listenerek kapcsolódnak, amelyek még mindig ott lesznek a DOM-ban az eseményekre figyelve.
Ami még rosszabb, az a beágyazás szintje, amit a stílus (CSS) alkalmazásához kell tennünk!
Nem kell aggódni, itt van a <ng-container>
a segítségünkre!
Az Angular <ng-container>
egy csoportosító elem, amely nem zavarja a stílusokat vagy az elrendezést, mert az Angular nem helyezi el a DOM-ban.
Így ha az 1. példánkat <ng-container>
-val írjuk:
A végleges DOM-ot a következőképpen kapjuk:
Láthatjuk, hogy megszabadultunk az üres <div>
-ektől. A <ng-container>
-t akkor kell használnunk, ha csak több szerkezeti direktívát akarunk alkalmazni anélkül, hogy bármilyen extra elemet bevezetnénk a DOM-unkba.
Bővebb információért olvassuk el a dokumentációt. Van egy másik felhasználási eset, amikor egy sablon dinamikus beillesztésére használjuk egy oldalba. Ezzel a felhasználási esettel a cikk utolsó részében foglalkozom.
<ng-content>
Konfigurálható komponensek létrehozására használják. Ez azt jelenti, hogy a komponensek a felhasználó igényeitől függően konfigurálhatók. Ezt Content Projection néven ismerjük. A közzétett könyvtárakban használt komponensek a <ng-content>
-t használják, hogy konfigurálhatóvá tegyék magukat.
Nézzünk egy egyszerű <project-content>
komponenst:
A <project-content>
komponens nyitó és záró tagjeiben átadott HTML tartalom a kivetítendő tartalom. Ezt nevezzük tartalomvetítésnek. A tartalom a komponensben lévő <ng-content>
belsejében kerül megjelenítésre. Ez lehetővé teszi a <project-content>
komponens fogyasztójának, hogy bármilyen egyéni láblécet átadjon a komponensen belül, és pontosan szabályozza, hogy hogyan szeretné azt megjeleníteni.
Multiple Projections:
Mi lenne, ha eldönthetné, hogy melyik tartalom hova kerüljön? Ahelyett, hogy minden tartalom egyetlen <ng-content>
belsejében vetülne ki, a <ng-content>
select
attribútumával azt is szabályozhatja, hogy a tartalom hogyan kerüljön kivetítésre. Egy elemválasztó segítségével dönti el, hogy melyik tartalom kerüljön kivetítésre egy adott <ng-content>
belsejében.
Íme, hogyan:
Módosítottuk a <project-content>
definíciót a több tartalom kivetítésének elvégzéséhez. A select
attribútum kiválasztja a tartalom típusát, amely egy adott <ng-content>
-n belül kerül megjelenítésre. Itt az első select
a fejléc h1
elem renderelésére szolgál. Ha a kivetített tartalom nem tartalmaz h1
elemet, akkor nem fog renderelni semmit. Hasonlóképpen a második select
egy div
elemet keres. A tartalom többi része az utolsó <ng-content>
belül kerül renderelésre, ha nincs select
.
A komponens hívása így fog kinézni:
*ngTemplateOutlet
…A több helyen újrafelhasználható sablonok tárolására szolgálnak. Erről bővebben a cikk egy későbbi részében lesz szó.
…Van egy másik felhasználási eset, amikor egy sablon dinamikus beillesztésére használjuk egy oldalba. Ezzel a felhasználási esettel a cikk utolsó részében foglalkozom.
Ez az a rész, ahol a fentebb említett két pontról lesz szó. *ngTemplateOutlet
Két forgatókönyvhöz használjuk – egy közös sablon beillesztésére egy nézet különböző szakaszaiba ciklusoktól vagy feltételektől függetlenül, valamint egy magasan konfigurált komponens elkészítésére.
Sablon újrafelhasználása:
Gondoljunk egy olyan nézetre, ahol több helyen is be kell illesztenünk egy sablont. Például egy weboldalon belül elhelyezendő vállalati logó. Ezt úgy érhetjük el, hogy egyszer megírjuk a logó sablonját, és a nézeten belül mindenhol újra felhasználjuk.
A következő kódrészlet:
Mint látható, a logó sablont csak egyszer írtuk meg, és egyetlen kódsorral háromszor használtuk ugyanazon az oldalon!
*ngTemplateOutlet
Elfogad egy context objektumot is, amelyet átadhatunk a közös sablon kimenet testreszabásához. A kontextusobjektummal kapcsolatos további információkért olvassa el a hivatalos dokumentációt.
Csak testreszabható komponensek:
A *ngTemplateOutlet
második felhasználási esete a nagymértékben testreszabható komponensek. Tekintsük korábbi példánkat a <project-content>
komponensre néhány módosítással:
Fentebb a <project-content>
komponens módosított változata, amely három bemeneti tulajdonságot fogad el – headerTemplate
, bodyTemplate
, footerTemplate
. Az alábbiakban a project-content.ts
részlete következik:
Azt szeretnénk elérni, hogy a <project-content>
szülő komponensből kapott fejléc, törzs és lábléc megjelenjen. Ha ezek közül bármelyik nincs megadva, a komponensünk az alapértelmezett sablont fogja megjeleníteni helyette. Így egy erősen testreszabott komponenst hozunk létre.
A nemrég módosított komponensünk használatához:
Ezzel fogjuk átadni a sablonreferenciákat a komponensünknek. Ha bármelyiket nem adjuk át, akkor a komponens az alapértelmezett sablont fogja megjeleníteni.
ng-content vs. *ngTemplateOutlet
Mindkettő segít nekünk abban, hogy nagyon testreszabott komponenseket érjünk el, de melyiket válasszuk és mikor?
Egyértelműen látható, hogy a *ngTemplateOutlet
némileg több lehetőséget ad nekünk az alapértelmezett sablon megjelenítésére, ha nincs megadva.
A ng-content
esetében ez nem így van. Ez úgy jeleníti meg a tartalmat, ahogy van. Maximum a select
attribútum segítségével feloszthatjuk a tartalmat és megjeleníthetjük a nézet különböző helyein. A ng-content
-n belül nem tudja feltételesen megjeleníteni a tartalmat. A szülőktől kapott tartalmat kell megjelenítened, anélkül, hogy a tartalom alapján döntést hozhatnál.
Az, hogy a kettő közül mit választasz, teljesen a felhasználási esetedtől függ. Legalább most már van egy új fegyver *ngTemplateOutlet
a fegyvertárunkban, amely a ng-content
funkciói mellett nagyobb kontrollt biztosít a tartalom felett!