W tym poście dowiesz się, jak używać dyrektywy Angulara NgFor do zapętlania danych w celu renderowania danych lub komponentów. Renderowanie listy <todo-item>
komponentów byłoby świetnym przypadkiem użycia NgFor.
Jako że Angular jest frameworkiem reaktywnym, często można zobaczyć NgFor używane razem z obserwowalnymi, więc nasze przykłady kodu również będą w stylu reaktywnym. NgFor obsługuje również tablice i obiekty tablicopodobne – zbadamy każde z tych podejść.
Czym jest NgFor?
NgFor jest jedną z najczęściej używanych dyrektyw Angulara, która jest dostarczana wraz z Angular’s CommonModule.
🙌 Wskazówka: Dołącz
BrowserModule
do modułu głównego swojej aplikacji, ponieważ zawiera on jużCommonModule
dla nas!
NgFor pozwala nam zapętlić dane i uzyskać dostęp do każdego value
i index
– podobnie jak w przypadku zwykłego Array ForEach.
Dyrektywa NgFor robi również o wiele więcej niż tylko pętla i daje nam wartość i indeks, może być połączona z obserwowalnymi przez async
pipe lub zwiększyć naszą wydajność renderowania z trackBy
funkcją, którą możemy dostarczyć.
Dla tego artykułu będziemy włączać kolejny komponent ContactCardComponent
do naszego @NgModule
:
Nasz ContactCardComponent
przyjmuje pojedynczy @Input
z contact
:
Więc teraz mamy już wszystko skonfigurowane, co dalej?
Iterowanie kolekcjami
Teraz, gdy nasz ContactCardComponent
jest zawarty w naszym module, możemy skonfigurować nasz AppComponent
tak, aby korzystał z tego zbioru danych:
Jak wspomniano we wstępie, używam Observable.of
tutaj z RxJS, aby dać mi strumień Observable z wyników, jest to miły sposób naśladowania odpowiedzi Observable, takich jak podczas używania modułu Angulara HttpClient
do zwracania danych z API.
Dlaczego w praktyce
Jak już jesteśmy skonfigurowani, możemy zajrzeć do naszego szablonu AppComponent
:
@Component({ selector: 'app-root', template: ` <div class="app"> <ul> <li> <contact-card></contact-card> </li> </ul> </div> `})
Widzisz, że deklaruję <contact-card>
wewnątrz tutaj, ponieważ chcemy iterować po naszym zbiorze danych i wypełniać każdy kontakt poprzez @Input
ustawiony wewnątrz naszego ContactCardComponent
.
Jednym ze sposobów, w jaki moglibyśmy to zrobić, jest użycie ngFor
w samym komponencie, jednak dla uproszczenia użyjemy listy nieuporządkowanej. Dodajmy więc ngFor
:
<ul> <li *ngFor="let contact of contacts"> <contact-card></contact-card> </li></ul>
Działa się tutaj kilka rzeczy, pierwszą z nich jest zauważenie znaku *
na początku ngFor
, Do tego, co to oznacza, przejdziemy w następnej części, kiedy przyjrzymy się elementowi <ng-template>
. Po drugie, tworzymy kontekst o nazwie contact
, używając pętli „for of”.
Dyrektywa ngFor
sklonuje element <li>
oraz jego węzły podrzędne. W tym przypadku <contact-card>
jest węzłem potomnym, a karta zostanie „wytłoczona” w DOM dla każdego konkretnego elementu wewnątrz naszej kolekcji contacts
.
🎉 Pobierz za darmo!
Wyjdź poza Array ForEach. Nabierz pewności siebie z bardziej zaawansowanymi metodami, takimi jak Reduce, Find, Filter, Every, Some i Map i w pełni zrozum, jak zarządzać strukturami danych JavaScript.
- W pełni zrozumieć, jak zarządzać strukturami danych JavaScript z niezmiennymi operacjami
- 31 stron składni deep-dive, rzeczywistych przykładów, wskazówek i sztuczek
- Napisz czystszą i lepiej ustrukturyzowaną logikę programowania w ciągu 3 godzin
Jako dodatkowy bonus, wyślemy Ci również kilka dodatkowych smakołyków przez kilka dodatkowych e-maili.
Więc teraz mamy contact
dostępny jako indywidualny Obiekt, możemy przekazać indywidualny contact
do „:
<ul> <li *ngFor="let contact of contacts"> <contact-card ="contact"></contact-card> </li></ul>
Jeśli używamy statycznej tablicy, lub wiążemy wynik z Observable do szablonu, możemy pozostawić szablon w obecnej postaci. Opcjonalnie możemy jednak powiązać obserwowalne bezpośrednio z szablonem, co oznacza, że będziemy potrzebowali rury async
, aby wszystko zakończyć:
<ul> <li *ngFor="let contact of contacts | async"> <contact-card ="contact"></contact-card> </li></ul>
Używanie trackBy dla kluczy
Jeśli pochodzisz z tła AngularJS, prawdopodobnie widziałeś „track by” podczas używania ng-repeat
, i podobnie w React land, używając key
na elemencie kolekcji.
Co więc one robią? Asocjują obiekty, lub klucze, z konkretnymi węzłami DOM, więc jeśli coś się zmieni lub będzie musiało zostać ponownie wyświetlone, framework może to zrobić znacznie sprawniej. Angular’s ngFor
domyślnie używa sprawdzania tożsamości obiektów dla ciebie, co jest szybkie, ale może być szybsze!
To właśnie tutaj trackBy
wchodzi w grę, dodajmy trochę więcej kodu, a następnie wyjaśnijmy:
<ul> <li *ngFor="let contact of contacts | async; trackBy: trackById;"> <contact-card ="contact"></contact-card> </li></ul>
W tym miejscu dodaliśmy trackBy
, następnie nadaliśmy mu wartość trackById
. To jest funkcja, którą dodamy w klasie komponentu:
trackById(index, contact) { return contact.id;}
Wszystko, co robi ta funkcja, to użycie niestandardowego rozwiązania śledzenia dla naszej kolekcji. Zamiast używać tożsamości obiektu, mówimy Angularowi tutaj, aby używał unikalnej id
właściwości, którą zawiera każdy contact
obiekt. Opcjonalnie możemy użyć właściwości index
(która jest indeksem w kolekcji każdego elementu, tj. 0, 1, 2, 3, 4).
Jeśli twój interfejs API zwraca unikalne dane, to użycie tego byłoby lepszym rozwiązaniem niż index
– ponieważ index
może ulec zmianie, jeśli zmienisz kolejność swojej kolekcji. Użycie unikalnego identyfikatora pozwala Angularowi znacznie szybciej zlokalizować węzeł DOM powiązany z obiektem, a także ponownie wykorzystać komponent w DOM, jeśli zajdzie potrzeba jego aktualizacji – zamiast niszczyć go i odbudowywać.
Przechwytywanie „index” i „count”
Dyrektywa ngFor
nie kończy się tylko na iteracji, zapewnia nam również kilka innych przyjemności. Zbadajmy index
i count
, dwie publiczne właściwości wystawione nam na każdą ngFor
iterację.
Utwórzmy kolejną zmienną o nazwie i
, do której przypiszemy wartość index
. Angular eksponuje dla nas te wartości pod maską, a gdy spojrzymy na kolejną sekcję z elementem <ng-template>
, możemy zobaczyć, jak są one skomponowane.
Aby wylogować indeks, możemy po prostu interpolować i
:
<ul> <li *ngFor="let contact of contacts | async; index as i;"> Index: {{ i }} <contact-card ="contact"></contact-card> </li></ul>
Da nam to każdy indeks, począwszy od 0
, dla każdego elementu w naszej kolekcji. Wyeksponujmy również count
:
count
zwróci nam długość kolekcji na żywo, odpowiednik contacts.length
. Te mogą być opcjonalnie związane i przekazywane do każdego komponentu, na przykład możesz chcieć wylogować całkowitą długość kolekcji gdzieś, a także przekazać index
konkretnego kontaktu do funkcji @Output
:
Dostęp do pierwszego, ostatniego, nieparzystego, parzystego
Cztery kolejne właściwości eksponowane przez ngFor
(no, właściwie pod spodem korzysta z NgForOfContext
, klasy, która wewnętrznie generuje każdy kontekst ngFor
). Przyjrzyjmy się szybko kodowi źródłowemu do tego celu:
Jak wspomniałem powyżej, NgForOfContext
jest tym, co konstruuje nasze ngFor
elementy, i możesz zobaczyć, że w constructor
przyjrzeliśmy się już index
i count
! Ostatnie rzeczy, którym musimy się przyjrzeć, to gettery, które możemy wyjaśnić na podstawie powyższego kodu źródłowego:
- first: zwraca
true
dla pierwszego elementu w kolekcji, dopasowuje indeks do zera - last: zwraca
true
dla ostatniego elementu w kolekcji, pasuje do indeksu z całkowitą liczbą, minus jeden, aby przesunąć „licznik” w dół o jeden, aby zaspokoić indeksy oparte na zerze - even: zwraca
true
dla parzystych elementów (np.np. 2, 4) w kolekcji, używa%
operatora modulus do obliczenia na podstawie indeksu - odd: zwraca
true
dla nieparzystych elementów (np. 1, 3), po prostu odwracathis.even
wynik
Używając tego, możemy dodać warunkowe zastosowanie takich rzeczy jak stylizacja, lub podpiąć się do właściwości last
aby wiedzieć kiedy kolekcja zakończyła renderowanie.
Dla tego szybkiego demo, użyjemy ngClass
aby dodać kilka stylów do każdego <li>
(zauważ jak tworzymy więcej zmiennych, tak jak index
):
I kilka stylów:
Nie będziemy demonstrować first
i last
, ponieważ jest to dość oczywiste z powyższego, jak możemy je podpiąć!
element
Wcześniej w tym artykule wspomnieliśmy, że przyjrzymy się zrozumieniu co oznacza *
w naszych szablonach. Składnia tego elementu jest taka sama jak *ngIf
, który prawdopodobnie też już widziałeś.
Więc w następnej sekcji, weźmiemy głębiej pod lupę ngFor
*
oraz element <ng-template>
, aby wyjaśnić bardziej szczegółowo, co tak naprawdę się tutaj dzieje.
Kiedy używamy gwiazdki (*
) w naszych szablonach, informujemy Angulara, że używamy dyrektywy strukturalnej, która jest również składnią cukru (miłym skrótem) dla użycia elementu <ng-template>
.
i Web Components
Czym więc jest element <ng-template>
? Po pierwsze, zróbmy krok wstecz. Cofniemy się, aby pokazać tutaj trochę kodu AngularJS, być może robiłeś to już wcześniej lub zrobiłeś coś podobnego w innym frameworku/bibliotece:
<script type="text/ng-template"> <div> My awesome template! </div></script>
To nadpisuje type
na tagu <script>
, który uniemożliwia silnikowi JavaScript parsowanie zawartości tagu <script>
. Dzięki temu my, lub framework taki jak AngularJS, możemy pobrać zawartość znacznika script i użyć go jako pewnego rodzaju szablonu HTML.
Web Components wprowadziło kilka lat temu nową specyfikację podobną do tego pomysłu, zwaną <template>
:
<template> <div> My awesome template! </div></template>
Aby złapać nasz powyższy szablon i go zainicjować, zrobilibyśmy to w zwykłym JavaScripcie:
Zauważ jak mamy id=host
, który jest naszym „hostem” Node’a, do którego szablon ma zostać wstrzyknięty.
Mogłeś zobaczyć ten termin w Angular na kilka sposobów, takich jak _nghost
prefiksy na Nodes (ng-host) lub host
własność w dyrektywach.
ngFor i ng-template
Przede wszystkim, <ng-template>
jest własną implementacją Angulara znacznika <template>
, pozwalającą nam myśleć o projektowaniu aplikacji w komponentach webowych i ideach za nimi stojących. Zapewnia nam również więcej mocy niż element <template>
daje nam domyślnie, płynnie wpasowując się w sposób, w jaki Angular kompiluje nasz kod.
Jak więc powyższe wyjaśnienie <template>
mówi nam więcej o ngFor
i *
? Gwiazdka jest skrótem składniowym do użycia elementu <ng-template>
.
Zacznijmy od podstawowego przykładu ngFor
:
<ul> <li *ngFor="let contact of contacts | async"> <contact-card ="contact"></contact-card> </li></ul>
I zademonstrujmy odpowiednik <ng-template>
:
<ul> <ng-template ngFor let-contact ="contacts | async"> <li> <contact-card ="contact"></contact-card> </li> </ng-template></ul>
To jest zupełnie co innego! Co się tutaj dzieje?
Gdy używamy *ngFor
, mówimy Angularowi, aby traktował element, z którym związany jest *
jako szablon.
Angularowy element
<ng-template>
nie jest prawdziwym Web Componentem (w przeciwieństwie do<template>
). Odzwierciedla on jedynie koncepcje stojące za nim, aby umożliwić użycie<ng-template>
tak, jak jest to zamierzone w specyfikacji. Kiedy skompilujemy nasz kod (JiT lub AoT), nie zobaczymy żadnych elementów<ng-template>
wyprowadzonych w DOM. Nie oznacza to jednak, że nie możemy używać takich rzeczy jak Shadow DOM, ponieważ są one nadal całkowicie możliwe.
Kontynuujmy i zrozummy, co ngFor
let-contact
i ngForOf
robią powyżej.
ngFor i osadzone szablony widoków
Pierwsza rzecz, ngFor
jest dyrektywą! Sprawdźmy część kodu źródłowego:
@Directive({selector: ''})export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCheck {...}
Tutaj Angular używa selektorów atrybutów jako wartości selector
, aby powiedzieć dekoratorowi @Directive
jakich atrybutów szukać.
Dyrektywa używa , co sugeruje, że istnieją dwa atrybuty jako selektor łańcuchowy. Jak więc działa
ngFor
, jeśli nie używamy ngForOf
?
Kompilator Angulara przekształca wszelkie elementy <ng-template>
i dyrektywy użyte z gwiazdką (*
) w widoki, które są oddzielne od widoku komponentu głównego. To dlatego każdy widok może być tworzony wiele razy.
Podczas fazy kompilacji weźmie
let contact of contacts
i skapitalizujeof
, i utworzy niestandardowy klucz, aby utworzyćngForOf
.
W naszym przypadku Angular skonstruuje widok, który utworzy wszystko od znacznika <li>
do wewnątrz:
<!-- view --><li> <contact-card ="contact"></contact-card></li><!-- /view -->
Tworzy również niewidzialny kontener widoku, który ma zawierać wszystkie instancje szablonu, działając jako placeholder dla treści. Kontener widoku, który Angular stworzył, zasadniczo owija „widoki”, w naszym przypadku jest to tuż wewnątrz tagów <ul>
. To mieści wszystkie szablony, które są tworzone przez ngFor
(jeden dla każdego wiersza).
Pseudo wyjście może wyglądać tak:
ngFor
tworzy „widok osadzony” dla każdego wiersza, przechodząc przez widok, który utworzył i kontekst wiersza (indeks i dane wiersza). Ten osadzony widok jest następnie wstawiany do kontenera widoku. Gdy dane się zmieniają, śledzi elementy, aby sprawdzić, czy zostały przeniesione. Jeśli się przesunęły, zamiast odtwarzać widoki osadzone, przesuwa je, aby znajdowały się we właściwej pozycji lub niszczy je, jeśli już nie istnieją.
Kontekst i przekazywanie zmiennych
Kolejnym krokiem jest zrozumienie, w jaki sposób Angular przekazuje kontekst do każdego <contact-card>
:
<ng-template ngFor let-contact ="contacts | async"> <li> <contact-card ="contact"></contact-card> </li></ng-template>
Więc teraz zrozumieliśmy ngFor
i ngForOf
, w jaki sposób Angular kojarzy let-contact
z poszczególnymi contact
, do których następnie wiążemy właściwości?
Ponieważ let-contact
nie ma wartości, jest tylko atrybutem, to właśnie tutaj Angular zapewnia „implikowaną” wartość, lub $implicit
jak to się nazywa pod maską.
Podczas gdy Angular tworzy każdy element ngFor
, używa klasy NgForOfContext
obok EmbeddedViewRef
, i przekazuje te właściwości dynamicznie. Oto mały wycinek z kodu źródłowego:
Poza tym fragmentem kodu, możemy również zobaczyć jak nasze wspomniane wcześniej index
i count
właściwości są aktualizowane:
Możesz przekopać się przez kod źródłowy dyrektywy bardziej szczegółowo tutaj.
W ten sposób możemy następnie uzyskać dostęp do index
i count
w ten sposób:
Zauważ, jak dostarczamy let-i
i let-c
wartości, które są eksponowane z instancji NgForRow
, w przeciwieństwie do let-contact
.
Aby poznać więcej technik, najlepszych praktyk i wiedzy ekspertów w świecie rzeczywistym, gorąco polecam sprawdzenie moich kursów Angulara – poprowadzą Cię one przez Twoją podróż do opanowania Angulara w pełni!