Wszystko, co musisz wiedzieć o ng-template, ng-content, ng-container i *ngTemplateOutlet w Angularze
To był jeden z tych dni, kiedy byłem zajęty pracą nad nowymi funkcjami dla mojego projektu biurowego. Nagle coś przykuło moją uwagę:
Podczas przeglądania DOM zobaczyłem, że ngcontent
jest nakładane na elementy przez Angular. Hmm… jeśli zawierają one elementy w ostatecznym DOM, to jaki jest pożytek z <ng-container>
? W tym czasie pomyliłem <ng-container>
z <ng-content>
.
W poszukiwaniu odpowiedzi na moje pytania odkryłem koncepcję <ng-template>
. Ku mojemu zaskoczeniu, było tam również *ngTemplateOutlet
. Zacząłem moją podróż szukając jasności na temat dwóch pojęć, ale teraz miałem ich cztery, brzmiące prawie tak samo!
Czy kiedykolwiek znalazłeś się w takiej sytuacji? Jeśli tak, to jesteś we właściwym miejscu. Więc bez dalszych ceregieli weźmy je po kolei.
<ng-template>
Jak sama nazwa wskazuje <ng-template>
jest elementem szablonowym, którego Angular używa z dyrektywami strukturalnymi (*ngIf
, *ngFor
, i dyrektywami niestandardowymi).
Te elementy szablonowe działają tylko w obecności dyrektyw strukturalnych. Angular opakowuje element hosta (do którego stosowana jest dyrektywa) wewnątrz <ng-template>
i konsumuje <ng-template>
w gotowym DOM, zastępując go komentarzami diagnostycznymi.
Rozważmy prosty przykład *ngIf
:
Powyżej przedstawiono Angularową interpretację *ngIf
. Angular umieszcza element hosta, do którego zastosowana jest dyrektywa, wewnątrz <ng-template>
i zachowuje hosta takim, jakim jest. Końcowy DOM jest podobny do tego co widzieliśmy na początku tego artykułu:
Usage:
Widzieliśmy jak Angular używa <ng-template>
, ale co jeśli chcemy go użyć? Ponieważ te elementy działają tylko z dyrektywą strukturalną, możemy napisać jako:
Tutaj home
jest właściwością boolean
komponentu ustawioną na wartość true
. Wyjście powyższego kodu w DOM:
Nic nie zostało wyrenderowane! 🙁
Ale dlaczego nie widzimy naszej wiadomości nawet po użyciu <ng-template>
poprawnie z dyrektywą strukturalną?
To był oczekiwany rezultat. Jak już wspomnieliśmy, Angular zastępuje <ng-template>
komentarzami diagnostycznymi. Bez wątpienia powyższy kod nie wygenerowałby żadnego błędu, ponieważ Angular doskonale radzi sobie z twoim przypadkiem użycia. Nigdy nie dowiedziałbyś się, co dokładnie stało się za kulisami.
Porównajmy powyższe dwa DOM-y, które zostały wyrenderowane przez Angulara:
Jeśli przyglądasz się uważnie, w końcowym DOM-ie Przykładu 2 znajduje się jeden dodatkowy znacznik komentarza. Kod, który zinterpretował Angular to:
Angular zawinął twój host <ng-template>
wewnątrz innego <ng-template>
i przekonwertował nie tylko zewnętrzny <ng-template>
na komentarze diagnostyczne, ale także wewnętrzny! Dlatego nie mogłeś zobaczyć żadnej ze swoich wiadomości.
Aby się tego pozbyć, istnieją dwa sposoby na uzyskanie pożądanego rezultatu:
Metoda 1:
W tej metodzie dostarczasz Angularowi oczyszczony format, który nie wymaga dalszego przetwarzania. Tym razem Angular przekonwertowałby tylko <ng-template>
na komentarze, ale pozostawia treść wewnątrz nich nietkniętą (nie są już wewnątrz żadnego <ng-template>
, jak to było w poprzednim przypadku). Tak więc, będzie renderował treść poprawnie.
Aby dowiedzieć się więcej o tym, jak używać tego formatu z innymi dyrektywami strukturalnymi odnieś się do tego artykułu.
Metoda 2:
Jest to dość niewidziany format i rzadko używany (używając dwóch rodzeństw <ng-template>
). Tutaj dajemy szablonowi odwołanie do *ngIf
w jego then
, aby powiedzieć mu, który szablon powinien być użyty, jeśli warunek jest prawdziwy.
Używanie wielu <ng-template>
w ten sposób nie jest zalecane (możesz użyć <ng-container>
zamiast tego), ponieważ nie do tego są one przeznaczone. Są one używane jako kontener do szablonów, które mogą być ponownie wykorzystane w wielu miejscach. Więcej na ten temat powiemy w dalszej części tego artykułu.
<ng-container>
Czy kiedykolwiek napisałeś lub widziałeś kod przypominający ten:
Powodem, dla którego wielu z nas pisze ten kod jest niemożność użycia wielu dyrektyw strukturalnych na jednym elemencie hosta w Angular. Teraz ten kod działa dobrze, ale wprowadza kilka dodatkowych pustych <div>
w DOM, jeśli item.id
jest wartością falsy, która może nie być wymagana.
Jeden może nie być zaniepokojony dla prostego przykładu takiego jak ten, ale dla ogromnej aplikacji, która ma złożony DOM (do wyświetlania dziesiątek tysięcy danych), może to stać się kłopotliwe, ponieważ elementy mogą mieć słuchaczy dołączonych do nich, które nadal będą tam w DOM słuchając zdarzeń.
Co jest jeszcze gorsze, to poziom zagnieżdżania, który musisz zrobić, aby zastosować stylizację (CSS)!
No worries, we have <ng-container>
to the rescue!
The Angular <ng-container>
is a grouping element that doesn’t interfere with styles or layout because Angular doesn’t put it in the DOM.
Więc jeśli napiszemy nasz Przykład 1 z <ng-container>
:
Uzyskamy końcowy DOM jako:
Zobacz, że pozbyliśmy się tych pustych <div>
s. Powinniśmy używać <ng-container>
, gdy chcemy zastosować wiele dyrektyw strukturalnych bez wprowadzania dodatkowego elementu do naszego DOM.
Po więcej informacji odsyłam do dokumentów. Jest jeszcze jeden przypadek użycia, w którym jest on używany do dynamicznego wstrzykiwania szablonu na stronę. Zajmę się tym przypadkiem użycia w ostatniej części tego artykułu.
<ng-content>
Służy do tworzenia konfigurowalnych komponentów. Oznacza to, że komponenty te mogą być konfigurowane w zależności od potrzeb ich użytkownika. Jest to dobrze znane jako projekcja treści (Content Projection). Komponenty, które są używane w publikowanych bibliotekach, korzystają z <ng-content>
, aby uczynić się konfigurowalnymi.
Rozważmy prosty komponent <project-content>
:
Treść HTML przekazana wewnątrz znaczników otwierających i zamykających komponentu <project-content>
jest treścią do projekcji. To właśnie nazywamy projekcją zawartości. Zawartość będzie renderowana wewnątrz <ng-content>
wewnątrz komponentu. To pozwala konsumentowi komponentu <project-content>
przekazać dowolną niestandardową stopkę wewnątrz komponentu i kontrolować dokładnie jak chce, aby była ona renderowana.
Wielokrotne projekcje:
A co jeśli mógłbyś zdecydować, która zawartość powinna być umieszczona gdzie? Zamiast każdej zawartości wyświetlanej wewnątrz pojedynczego <ng-content>
, możesz również kontrolować, w jaki sposób zawartość będzie wyświetlana za pomocą atrybutu select
atrybutu <ng-content>
. Pobiera on selektor elementu, aby zdecydować, która zawartość ma być rzutowana wewnątrz konkretnego <ng-content>
.
Oto jak:
Zmodyfikowaliśmy definicję <project-content>
, aby wykonać projekcję wielu treści. Atrybut select
wybiera typ zawartości, która będzie renderowana wewnątrz danego <ng-content>
. Tutaj mamy pierwszy select
do renderowania elementu header h1
. Jeśli projektowana treść nie ma elementu h1
to nie będzie renderować niczego. Podobnie drugi select
szuka elementu div
. Reszta treści zostanie wyrenderowana wewnątrz ostatniego <ng-content>
bez select
.
Wywołanie komponentu będzie wyglądać następująco:
*ngTemplateOutlet
…Są one używane jako kontener do szablonów, które mogą być ponownie użyte w wielu miejscach. Więcej na ten temat powiemy w dalszej części artykułu.
…Jest jeszcze jeden przypadek użycia, w którym jest on używany do dynamicznego wstrzykiwania szablonu na stronę. Omówię ten przypadek użycia w ostatniej części tego artykułu.
To jest sekcja, w której omówimy dwa powyższe punkty wspomniane wcześniej. *ngTemplateOutlet
jest używany w dwóch scenariuszach – do wstawiania wspólnego szablonu w różnych sekcjach widoku niezależnie od pętli lub warunku oraz do tworzenia wysoce skonfigurowanego komponentu.
Ponowne użycie szablonu:
Rozważmy widok, w którym trzeba wstawić szablon w wielu miejscach. Na przykład, logo firmy, które ma być umieszczone na stronie internetowej. Możemy to osiągnąć pisząc szablon dla logo raz i używając go ponownie wszędzie w obrębie widoku.
Poniżej znajduje się fragment kodu:
Jak widać, napisaliśmy szablon logo raz i użyliśmy go trzy razy na tej samej stronie w jednej linii kodu!
*ngTemplateOutlet
akceptuje również obiekt kontekstu, który może zostać przekazany w celu dostosowania wspólnego wyjścia szablonu. Więcej informacji o obiekcie kontekstowym można znaleźć w oficjalnych dokumentach.
Dostosowywalne komponenty:
Drugim przypadkiem użycia dla *ngTemplateOutlet
są wysoce dostosowane komponenty. Rozważmy nasz poprzedni przykład <project-content>
komponentu z pewnymi modyfikacjami:
Powyżej znajduje się zmodyfikowana wersja komponentu <project-content>
, który akceptuje trzy właściwości wejściowe – headerTemplate
, bodyTemplate
, footerTemplate
. Poniżej znajduje się snippet dla project-content.ts
:
To, co staramy się tutaj osiągnąć, to wyświetlanie nagłówka, ciała i stopki w formie otrzymanej od komponentu nadrzędnego <project-content>
. Jeśli któryś z nich nie zostanie dostarczony, nasz komponent wyświetli w jego miejsce domyślny szablon. W ten sposób tworzymy wysoce spersonalizowany komponent.
Aby użyć naszego ostatnio zmodyfikowanego komponentu:
W ten sposób będziemy przekazywać refy szablonów do naszego komponentu. Jeśli któryś z nich nie zostanie przekazany, komponent będzie renderował domyślny szablon.
ng-content vs. *ngTemplateOutlet
Obydwa pomagają nam osiągnąć wysoce spersonalizowane komponenty, ale który wybrać i kiedy?
Wyraźnie widać, że *ngTemplateOutlet
daje nam trochę więcej możliwości pokazania domyślnego szablonu, jeśli żaden nie został przekazany.
Nie ma to miejsca w przypadku ng-content
. Renderuje on treść taką, jaka jest. Maksymalnie możesz podzielić treść i renderować je w różnych miejscach twojego widoku za pomocą atrybutu select
. Nie można warunkowo renderować zawartości wewnątrz ng-content
. Musisz pokazać zawartość, która jest otrzymywana od rodzica bez środków do podejmowania decyzji na podstawie zawartości.
Jednakże wybór wyboru między tymi dwoma całkowicie zależy od twojego przypadku użycia. Przynajmniej teraz mamy nową broń *ngTemplateOutlet
w naszym arsenale, która zapewnia większą kontrolę nad treścią oprócz funkcji ng-content
!
.