Dyrektywa @for – pętla w szablonie

@for pozwala wyświetlić całą listę elementów z tablicy – bez kopiowania kodu HTML dla każdego elementu. Jeden szablon, dowolna liczba elementów. Poznasz też słowo kluczowe track i zmienne pomocnicze pętli.

@for track $index @empty
01

Czym jest @for?

Wyobraź sobie, że masz tablicę 20 uczniów i chcesz wyświetlić każdego w osobnym wierszu tabeli. Bez @for musiałbyś skopiować ten sam kod HTML 20 razy. Z @for piszesz go raz.

❌ Bez @for – kopiowanie kodu

<!-- 20 razy ten sam kod – koszmar! -->
<li>{{ uczniowie[0] }}</li>
<li>{{ uczniowie[1] }}</li>
<li>{{ uczniowie[2] }}</li>
<!-- ... i tak 20 razy -->

✅ Z @for – jeden szablon dla wszystkich

<!-- Jeden wzorzec – Angular powtórzy go tyle razy ile trzeba -->
@for (uczen of uczniowie; track uczen) {
  <li>{{ uczen }}</li>
}

@for to odpowiednik pętli for...of z TypeScript – przebiega przez każdy element tablicy i dla każdego z nich renderuje podany blok HTML.

tablica w .ts
[’Anna’, 'Piotr’, 'Ola’]
@for w .html
szablon × 3
przeglądarka
3 elementy li
02

Podstawowa składnia

Składnia @for wygląda tak: @for (element of tablica; track identyfikator) { ... }. Trzy obowiązkowe części: zmienna iterująca, tablica i słowo kluczowe track.

ts/app.component.ts TS
export class AppComponent {
  owoce: string[] = ['Jabłko', 'Banan', 'Mango', 'Kiwi'];
}
html/app.component.html HTML
<ul class="list-group">

  @for (owoc of owoce; track owoc) {
    <li class="list-group-item">{{ owoc }}</li>
  }

</ul>

<!-- Wynik:
  • Jabłko
  • Banan
  • Mango
  • Kiwi
-->
Nazwy zmiennych w @for

Nazwa zmiennej iterującej (owoc) jest dowolna – wybierasz ją sam. Zwyczajowo używamy liczby pojedynczej gdy tablica ma nazwę w liczbie mnogiej: owoceowoc, uczniowieuczen, produktyprodukt. Zmienna ta jest dostępna tylko wewnątrz bloku { }.

03

Słowo kluczowe track – dlaczego jest obowiązkowe?

track to obowiązkowa część @for w Angular 17+. Mówi Angularowi czego używać do identyfikowania każdego elementu listy. Bez tego Angular nie wie które elementy się zmieniły, a które są nowe.

Wyobraź sobie listę zakupów. Dopisujesz nowy produkt na końcu. Angular bez track musiałby zniszczyć i odtworzyć całą listę od nowa. Z track wie dokładnie który element jest nowy i aktualizuje tylko jego – reszta zostaje nieruszona.

track dla prostych tablic (stringi, liczby)

html/track-prosty.html HTML
<!-- Tablica stringów – trackujemy samą wartość -->
@for (miasto of miasta; track miasto) {
  <p>{{ miasto }}</p>
}

<!-- Tablica liczb – tak samo -->
@for (ocena of oceny; track ocena) {
  <span class="badge bg-primary">{{ ocena }}</span>
}

track dla tablic obiektów (zalecane: track po id)

ts/app.component.ts TS
export class AppComponent {
  produkty = [
    { id: 1, nazwa: 'Laptop',    cena: 3499 },
    { id: 2, nazwa: 'Myszka',    cena: 89   },
    { id: 3, nazwa: 'Klawiatura', cena: 149  },
  ];
}
html/track-obiekty.html HTML
<!-- Tablica obiektów – trackujemy po unikalnym id -->
@for (produkt of produkty; track produkt.id) {
  <div class="card mb-2">
    <div class="card-body">
      <h5>{{ produkt.nazwa }}</h5>
      <p>Cena: {{ produkt.cena }} zł</p>
    </div>
  </div>
}
Typ tablicyCo podać w track?Przykład
Stringi / liczby Samą zmienną iterującą track owoc
Obiekty z polem id Pole id obiektu track produkt.id
Gdy nie ma id Indeks jako ostateczność track $index
04

Iteracja po tablicy obiektów

W prawdziwych aplikacjach rzadko iterujemy po prostych stringach. Najczęściej mamy tablicę obiektów – każdy obiekt to jeden rekord danych, np. uczeń, produkt, zamówienie.

ts/app.component.ts TS
export class AppComponent {
  uczniowie = [
    { id: 1, imie: 'Anna',    klasa: '3TI', srednia: 4.8 },
    { id: 2, imie: 'Piotr',   klasa: '3TI', srednia: 3.5 },
    { id: 3, imie: 'Karolina', klasa: '2TI', srednia: 5.0 },
    { id: 4, imie: 'Marek',   klasa: '2TI', srednia: 2.8 },
  ];
}
html/app.component.html HTML
<table class="table table-striped">
  <thead>
    <tr>
      <th>Imię</th>
      <th>Klasa</th>
      <th>Średnia</th>
    </tr>
  </thead>
  <tbody>

    <!-- Każdy uczeń = jeden wiersz tabeli -->
    @for (uczen of uczniowie; track uczen.id) {
      <tr>
        <td>{{ uczen.imie }}</td>
        <td>{{ uczen.klasa }}</td>
        <td>
          <!-- @if wewnątrz @for – wyświetla odznakę dla najlepszych -->
          @if (uczen.srednia >= 4.75) {
            <span class="badge bg-success">{{ uczen.srednia }}</span>
          } @else {
            {{ uczen.srednia }}
          }
        </td>
      </tr>
    }

  </tbody>
</table>
@if wewnątrz @for – to normalne!

Możesz swobodnie zagnieżdżać @if wewnątrz @for i odwrotnie. To jeden z najczęstszych wzorców w Angularze – wyświetlasz listę i dla każdego elementu decydujesz co pokazać w zależności od jego wartości.

05

Zmienne pomocnicze pętli

Angular udostępnia w bloku @for specjalne zmienne pomocnicze zaczynające się od $. Dają informacje o aktualnym miejscu w pętli – indeks, czy to pierwszy element, czy ostatni i inne.

$index, $first, $last

ts/app.component.ts TS
export class AppComponent {
  kroki: string[] = [
    'Pobierz Node.js',
    'Zainstaluj Angular CLI',
    'Utwórz projekt ng new',
    'Uruchom ng serve'
  ];
}
html/zmienne-pomocnicze.html HTML
@for (krok of kroki; track krok;
      let i = $index; let pierwszy = $first; let ostatni = $last) {

  <div class="d-flex align-items-center mb-2">

    <!-- Numer kroku z $index (liczy od 0, dodajemy 1) -->
    <span class="badge bg-primary me-2">{{ i + 1 }}</span>

    <!-- Treść kroku -->
    <span>{{ krok }}</span>

    <!-- Odznaka tylko dla pierwszego elementu -->
    @if (pierwszy) {
      <span class="badge bg-success ms-2">Start</span>
    }

    <!-- Odznaka tylko dla ostatniego elementu -->
    @if (ostatni) {
      <span class="badge bg-warning ms-2">Koniec</span>
    }

  </div>
}

$even, $odd, $count

html/even-odd.html HTML
@for (uczen of uczniowie; track uczen.id;
      let parzyste = $even; let nieparzyste = $odd; let liczba = $count) {

  <!-- Naprzemienne tło wierszy tabeli przez $even / $odd -->
  <tr class="{{ parzyste ? 'table-light' : '' }}">
    <td>{{ uczen.imie }}</td>
  </tr>
}

<!-- $count – łączna liczba elementów w tablicy -->
<p>Łącznie uczniów: {{ liczba }}</p>
ZmiennaTypWartość
$indexnumberIndeks bieżącego elementu (od 0)
$firstbooleantrue dla pierwszego elementu
$lastbooleantrue dla ostatniego elementu
$evenbooleantrue dla parzystych indeksów (0, 2, 4…)
$oddbooleantrue dla nieparzystych indeksów (1, 3, 5…)
$countnumberCałkowita liczba elementów w tablicy
Jak deklarować zmienne pomocnicze?

Po średniku i słowie track dodajesz kolejny średnik, a po nim deklaracje zmiennych oddzielone przecinkami: let nazwaWlasna = $nazwaSystemowa. Nazwa własna może być dowolna – to Ty ją wybierasz.

06

Blok @empty – pusta lista

Co jeśli tablica jest pusta? Bez obsługi tego przypadku użytkownik zobaczyłby po prostu pusty obszar – bez żadnej informacji. @empty pozwala wyświetlić komunikat gdy tablica nie ma elementów.

ts/app.component.ts TS
export class AppComponent {
  koszyk: { id: number; nazwa: string }[] = [];
  // Tablica pusta – koszyk jest na razie pusty

  dodajProdukt(): void {
    this.koszyk.push({ id: this.koszyk.length + 1, nazwa: 'Produkt' });
  }
}
html/app.component.html HTML
<button class="btn btn-primary mb-3" (click)="dodajProdukt()">
  Dodaj do koszyka
</button>

<ul class="list-group">
  @for (produkt of koszyk; track produkt.id) {

    <li class="list-group-item">{{ produkt.nazwa }}</li>

  } @empty {

    <!-- Ten blok pojawi się gdy koszyk = [] -->
    <li class="list-group-item text-muted text-center">
      🛒 Koszyk jest pusty
    </li>

  }
</ul>
@empty zastępuje @if z warunkiem na length

Przed Angularem 17 trzeba było pisać: @if (koszyk.length === 0) { ... } osobno od pętli. Teraz @empty obsługuje ten przypadek elegancko, bezpośrednio w bloku @for.

07

Stary sposób – *ngFor

Tak jak @if zastąpił *ngIf, tak @for zastąpił dyrektywę *ngFor. Poznaj ją, bo spotkasz ją w starszych projektach i tutorialach.

🕰️ Stary sposób – *ngFor

<!-- Wymagało importu NgFor -->
<li
  *ngFor="let owoc of owoce; let i = index"
>
  {{ i + 1 }}. {{ owoc }}
</li>

✅ Nowy sposób – @for (Angular 21)

<!-- Bez importów, czytelniejszy zapis -->
@for (owoc of owoce; track owoc;
      let i = $index) {
  <li>{{ i + 1 }}. {{ owoc }}</li>
}
*ngFor (stary)@for (nowy)
Import wymagany? Tak – NgFor lub CommonModule Nie
track obowiązkowy? Nie (opcjonalny trackBy) Tak – wymuszony przez Angular
Pusta lista Osobny *ngIf Wbudowany @empty
Zmienne pomocnicze let i = index let i = $index
08

Częste błędy

❌ Błąd 1 – Brak track

❌ Błąd kompilacji

<!-- Angular zgłosi błąd – track jest obowiązkowy! -->
@for (owoc of owoce) {
  <li>{{ owoc }}</li>
}

✅ Z track

@for (owoc of owoce; track owoc) {
  <li>{{ owoc }}</li>
}

❌ Błąd 2 – Odwołanie do zmiennej poza blokiem

❌ owoc niedostępny poza @for

@for (owoc of owoce; track owoc) {
  <li>{{ owoc }}</li>
}
<!-- Poza blokiem – owoc nie istnieje! -->
<p>{{ owoc }}</p> <!-- ❌ błąd -->

✅ Użyj osobnej zmiennej w klasie

// .ts – zapamiętaj wybrany element
wybranyOwoc: string = '';

<!-- .html -->
@for (owoc of owoce; track owoc) {
  <li (click)="wybranyOwoc = owoc">{{ owoc }}</li>
}
<p>Wybrano: {{ wybranyOwoc }}</p> <!-- ✅ działa -->

❌ Błąd 3 – track $index dla dynamicznych list

❌ Zły track gdy lista się zmienia

<!-- Gdy usuwasz element z środka listy,
     wszystkie indeksy poniżej się zmieniają -->
@for (p of produkty; track $index) { ... }

✅ Trackuj po unikalnym id

<!-- id nie zmienia się – Angular wie co usunięto -->
@for (p of produkty; track p.id) { ... }
✏️

Zadanie – lista zadań (todo list)

Stwórz prostą listę zadań używając @for, @if i zmiennych szablonowych. To połączenie wszystkiego czego nauczyłeś się do tej pory.

W klasie zdefiniuj:

  • Tablicę obiektów zadania z polami id, tresc, ukonczone (boolean) – wstaw 3–4 przykładowe zadania
  • Metodę dodajZadanie(tresc: string) dodającą nowe zadanie do tablicy
  • Metodę przelaczStatus(id: number) zmieniającą pole ukonczone na przeciwne

W szablonie wyświetl:

  • Formularz z polem tekstowym (#noweZadanie) i przyciskiem „Dodaj”
  • Listę zadań przez @for z track zadanie.id
  • Dla każdego zadania: numer ($index + 1), treść, przycisk „Zrobione/Cofnij”
  • Zadania ukończone wyświetl przekreślone (Bootstrap: klasa text-decoration-line-through) – użyj @if
  • Gdy lista jest pusta – komunikat przez @empty
  • Pod listą wyświetl: „Ukończono X z Y zadań” używając $count
Następny temat

W następnym materiale poznamy Property Binding – wiązanie właściwości elementów HTML ze zmiennymi TypeScript przez nawiasy kwadratowe [ ].