1. Czym są dyrektywy pętli?
Dyrektywy pętli to narzędzia w Angularze, które pozwalają na automatyczne tworzenie wielu elementów na podstawie listy danych. Dzięki nim możemy wyświetlić wszystkie elementy z tablicy bez ręcznego kopiowania kodu HTML.
Przykłady zastosowań:
- Lista filmów w bazie danych
- Menu nawigacyjne z różnymi opcjami
- Tabela z danymi użytkowników
- Galeria zdjęć
- Lista komentarzy pod artykułem
Bez pętli musielibyśmy:
<!-- Bez pętli - dużo powtarzającego się kodu! -->
<div>Film 1: Avengers</div>
<div>Film 2: Spider-Man</div>
<div>Film 3: Batman</div>
<!-- ... i tak dalej dla każdego filmu -->Z pętlą:
<!-- Z pętlą - kod napisany raz działa dla wszystkich filmów! -->
@for (film of filmy; track film.id) {
<div>{{ film.title }}</div>
}2. Nowoczesna składnia @for (Angular 17+)
Podobnie jak z @if, Angular wprowadził nową, lepszą składnię – @for. Jest ona:
- Bardziej czytelna – przypomina pętle for z innych języków programowania
- Nie wymaga importów – nie trzeba dodawać żadnych modułów
- Wydajniejsza – Angular działa szybciej
- Bezpieczniejsza – wymaga
trackco poprawia performance
Podstawowa składnia @for
@for (element of tablica; track element.id) {
<div>{{ element.nazwa }}</div>
}Gdzie:
element– nazwa zmiennej dla każdego elementu (możesz nazwać jak chcesz)tablica– nazwa tablicy z komponentutrack element.id– mówi Angularowi jak identyfikować elementy (WYMAGANE!)
3. Przykłady z @for – od prostych do bardziej skomplikowanych
Przykład 1: Lista filmów – podstawy
// Komponent TypeScript
import { Component } from '@angular/core';
@Component({
selector: 'app-film-list',
standalone: true,
imports: [], // Nie potrzebujemy żadnych importów dla @for!
templateUrl: './film-list.component.html',
styleUrl: './film-list.component.css'
})
export class FilmListComponent {
filmy = [
'Avengers: Endgame',
'Spider-Man',
'Batman',
'Superman',
'Iron Man'
];
}<!-- Szablon HTML -->
<div class="container">
<h2>Lista filmów</h2>
<div class="row">
@for (film of filmy; track $index) {
<div class="col-md-4 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ film }}</h5>
<p class="text-muted">Film numer: {{ $index + 1 }}</p>
</div>
</div>
</div>
}
</div>
</div>Wyjaśnienie:
$index– automatyczna zmienna z numerem elementu (zaczyna od 0)track $index– używamy gdy nie mamy unikalnego ID$index + 1– dodajemy 1, bo użytkownicy liczą od 1, nie od 0
Przykład 2: Obiekty z wieloma właściwościami
export class FilmCatalogComponent {
filmy = [
{
id: 1,
title: 'Avengers: Endgame',
year: 2019,
genre: 'Akcja',
rating: 8.4,
description: 'Finałowa walka z Thanosem'
},
{
id: 2,
title: 'Spider-Man: No Way Home',
year: 2021,
genre: 'Akcja',
rating: 8.2,
description: 'Multiwersum Spider-Mana'
},
{
id: 3,
title: 'The Dark Knight',
year: 2008,
genre: 'Akcja',
rating: 9.0,
description: 'Batman kontra Joker'
},
{
id: 4,
title: 'Inception',
year: 2010,
genre: 'Sci-Fi',
rating: 8.8,
description: 'Świat snów i rzeczywistości'
}
];
}<div class="container">
<h2>Katalog filmów</h2>
<div class="row">
@for (film of filmy; track film.id) {
<div class="col-lg-6 mb-4">
<div class="card h-100">
<div class="card-header d-flex justify-content-between">
<h5 class="mb-0">{{ film.title }}</h5>
<span class="badge bg-primary">{{ film.year }}</span>
</div>
<div class="card-body">
<p class="card-text">{{ film.description }}</p>
<div class="mb-2">
<strong>Gatunek:</strong> {{ film.genre }}
</div>
<div class="mb-2">
<strong>Ocena:</strong>
<span class="badge bg-warning text-dark">{{ film.rating }}/10</span>
</div>
</div>
<div class="card-footer">
<button class="btn btn-primary btn-sm">Zobacz szczegóły</button>
<button class="btn btn-outline-secondary btn-sm ms-2">Dodaj do ulubionych</button>
</div>
</div>
</div>
}
</div>
</div>Przykład 3: @for z @if – warunkowe wyświetlanie
export class FilmFilterComponent {
filmy = [
{ id: 1, title: 'Avengers', rating: 8.4, isPopular: true },
{ id: 2, title: 'Spider-Man', rating: 8.2, isPopular: true },
{ id: 3, title: 'Low Budget Movie', rating: 5.1, isPopular: false },
{ id: 4, title: 'The Dark Knight', rating: 9.0, isPopular: true },
{ id: 5, title: 'Bad Movie', rating: 3.2, isPopular: false }
];
showOnlyPopular = false;
toggleFilter() {
this.showOnlyPopular = !this.showOnlyPopular;
}
}<div class="container">
<h2>Filmy z filtrowaniem</h2>
<div class="mb-3">
<button (click)="toggleFilter()" class="btn btn-outline-primary">
@if (showOnlyPopular) {
<span>Pokaż wszystkie filmy</span>
} @else {
<span>Pokaż tylko popularne</span>
}
</button>
</div>
<div class="row">
@for (film of filmy; track film.id) {
<!-- Pokazuj film tylko jeśli spełnia warunki -->
@if (!showOnlyPopular || film.isPopular) {
<div class="col-md-6 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">
{{ film.title }}
@if (film.isPopular) {
<span class="badge bg-success ms-2">Popularne</span>
}
</h5>
<p>Ocena: {{ film.rating }}/10</p>
@if (film.rating >= 8.0) {
<div class="alert alert-success p-2">
🏆 Doskonały film!
</div>
} @else if (film.rating >= 6.0) {
<div class="alert alert-info p-2">
👍 Dobry film
</div>
} @else {
<div class="alert alert-warning p-2">
👎 Słaby film
</div>
}
</div>
</div>
</div>
}
}
</div>
</div>Przykład 4: Dynamiczne dodawanie i usuwanie elementów
export class FilmManagerComponent {
filmy = [
{ id: 1, title: 'Avengers', year: 2019 },
{ id: 2, title: 'Spider-Man', year: 2021 }
];
newFilmTitle = '';
newFilmYear = '';
nextId = 3;
addFilm() {
if (this.newFilmTitle.length > 0 && this.newFilmYear.length > 0) {
this.filmy.push({
id: this.nextId++,
title: this.newFilmTitle,
year: parseInt(this.newFilmYear)
});
// Wyczyść formularz
this.newFilmTitle = '';
this.newFilmYear = '';
}
}
removeFilm(id: number) {
this.filmy = this.filmy.filter(film => film.id !== id);
}
clearAll() {
this.filmy = [];
}
}<div class="container">
<h2>Zarządzanie filmami</h2>
<!-- Formularz dodawania -->
<div class="card mb-4">
<div class="card-header">
<h5>Dodaj nowy film</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Tytuł filmu:</label>
<input
type="text"
#titleInput
(input)="newFilmTitle = titleInput.value"
value="{{ newFilmTitle }}"
class="form-control"
placeholder="Wpisz tytuł filmu">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Rok produkcji:</label>
<input
type="number"
#yearInput
(input)="newFilmYear = yearInput.value"
value="{{ newFilmYear }}"
class="form-control"
placeholder="2024">
</div>
<div class="col-md-2 mb-3 d-flex align-items-end">
<button (click)="addFilm()" class="btn btn-success w-100">
Dodaj film
</button>
</div>
</div>
</div>
</div>
<!-- Lista filmów -->
<div class="d-flex justify-content-between align-items-center mb-3">
<h5>Lista filmów ({{ filmy.length }} filmów)</h5>
@if (filmy.length > 0) {
<button (click)="clearAll()" class="btn btn-danger btn-sm">
Usuń wszystkie
</button>
}
</div>
@if (filmy.length === 0) {
<div class="alert alert-info">
<h5>📽️ Brak filmów</h5>
<p>Dodaj pierwszy film używając formularza powyżej!</p>
</div>
} @else {
<div class="row">
@for (film of filmy; track film.id) {
<div class="col-md-6 col-lg-4 mb-3">
<div class="card">
<div class="card-body">
<h6 class="card-title">{{ film.title }}</h6>
<p class="card-text text-muted">Rok: {{ film.year }}</p>
<button
(click)="removeFilm(film.id)"
class="btn btn-outline-danger btn-sm">
🗑️ Usuń
</button>
</div>
</div>
</div>
}
</div>
}
</div>Przykład 5: Zagnieżdżone pętle – kategorie filmów
export class FilmCategoriesComponent {
kategorie = [
{
id: 1,
name: 'Akcja',
filmy: [
{ id: 1, title: 'Avengers', rating: 8.4 },
{ id: 2, title: 'John Wick', rating: 7.8 },
{ id: 3, title: 'Mad Max', rating: 8.1 }
]
},
{
id: 2,
name: 'Komedia',
filmy: [
{ id: 4, title: 'Deadpool', rating: 8.0 },
{ id: 5, title: 'Guardians of Galaxy', rating: 8.0 }
]
},
{
id: 3,
name: 'Horror',
filmy: [
{ id: 6, title: 'IT', rating: 7.3 },
{ id: 7, title: 'A Quiet Place', rating: 7.5 },
{ id: 8, title: 'Hereditary', rating: 7.3 }
]
}
];
}<div class="container">
<h2>Filmy według kategorii</h2>
@for (kategoria of kategorie; track kategoria.id) {
<div class="card mb-4">
<div class="card-header">
<h4 class="mb-0">
🎬 {{ kategoria.name }}
<span class="badge bg-secondary ms-2">{{ kategoria.filmy.length }} filmów</span>
</h4>
</div>
<div class="card-body">
<div class="row">
@for (film of kategoria.filmy; track film.id) {
<div class="col-md-4 mb-3">
<div class="card border-light">
<div class="card-body p-3">
<h6 class="card-title">{{ film.title }}</h6>
<div class="d-flex justify-content-between align-items-center">
<span class="badge bg-warning text-dark">{{ film.rating }}/10</span>
<button class="btn btn-outline-primary btn-sm">Szczegóły</button>
</div>
</div>
</div>
</div>
}
</div>
</div>
</div>
}
</div>4. Ważne zmienne w @for
Angular udostępnia specjalne zmienne, które możesz używać wewnątrz pętli:
@for (film of filmy; track film.id) {
<div class="card mb-2">
<div class="card-body">
<h5>{{ film.title }}</h5>
<small class="text-muted">
Pozycja: {{ $index + 1 }} z {{ $count }} |
@if ($first) { <span class="badge bg-success">Pierwszy</span> }
@if ($last) { <span class="badge bg-danger">Ostatni</span> }
@if ($even) { <span class="badge bg-info">Parzysta pozycja</span> }
@if ($odd) { <span class="badge bg-warning">Nieparzysta pozycja</span> }
</small>
</div>
</div>
}Dostępne zmienne:
$index– numer elementu (zaczyna od 0)$count– całkowita liczba elementów$first–truedla pierwszego elementu$last–truedla ostatniego elementu$even–truedla elementów o parzystym indeksie (0, 2, 4…)$odd–truedla elementów o nieparzystym indeksie (1, 3, 5…)
5. @for z @empty – obsługa pustych list
export class EmptyListComponent {
filmy: any[] = []; // Pusta tablica
loadMovies() {
this.filmy = [
{ id: 1, title: 'Avengers', rating: 8.4 },
{ id: 2, title: 'Spider-Man', rating: 8.2 }
];
}
clearMovies() {
this.filmy = [];
}
}<div class="container">
<h2>Lista z obsługą pustej tablicy</h2>
<div class="mb-3">
<button (click)="loadMovies()" class="btn btn-primary me-2">
Załaduj filmy
</button>
<button (click)="clearMovies()" class="btn btn-secondary">
Wyczyść listę
</button>
</div>
@for (film of filmy; track film.id) {
<div class="card mb-2">
<div class="card-body">
<h5>{{ film.title }}</h5>
<p>Ocena: {{ film.rating }}/10</p>
</div>
</div>
} @empty {
<div class="alert alert-info">
<h5>📭 Brak filmów na liście</h5>
<p>Kliknij "Załaduj filmy", aby dodać filmy do listy.</p>
</div>
}
</div>6. Alternatywna składnia *ngFor (starsze projekty)
W starszych projektach może spotkasz tradycyjną składnię *ngFor. Warto ją znać, ale zalecamy używanie @for w nowych projektach.
Porównanie składni
// Komponent wspólny dla obu wersji
export class ComparisonComponent {
filmy = [
{ id: 1, title: 'Avengers', year: 2019 },
{ id: 2, title: 'Spider-Man', year: 2021 }
];
}Wersja z *ngFor (STARSZA)
<!-- Wymaga import { CommonModule } w komponencie! -->
<div *ngFor="let film of filmy; let i = index;" class="card mb-2">
<div class="card-body">
<h5>{{ film.title }}</h5>
<p>Rok: {{ film.year }}</p>
<small>Pozycja: {{ i + 1 }}</small>
</div>
</div>
<!-- Dla pustej listy trzeba osobny *ngIf -->
<div *ngIf="filmy.length === 0">
<p>Brak filmów do wyświetlenia</p>
</div>Kluczowe różnice
| Cecha | @for (NOWA) | *ngFor (STARA) |
|---|---|---|
| Import | Nie wymaga importów | Wymaga CommonModule |
| Składnia | @for (item of items; track item.id) | *ngFor="let item of items; trackBy: fn" |
| Track | Wbudowane w składnię | Wymaga osobnej funkcji |
| Empty | Wbudowane @empty | Wymaga osobnego *ngIf |
| Zmienne | $index, $count itp. | let i = index itp. |
| Czytelność | Bardzo czytelne | Mniej czytelne |
| Wydajność | Lepsza | Dobra |
7. Jak Angular obsługuje pętle
Gdy używasz @for, Angular:
- Iteruje przez tablicę i tworzy element DOM dla każdego elementu
- Używa track do identyfikacji, które elementy się zmieniły
- Aktualizuje tylko zmienione elementy zamiast przebudowywać całą listę
- Pamięta pozycje i nie przesuwa elementów niepotrzebnie
Dlaczego track jest ważne:
- Bez
trackAngular musi odbudować całą listę przy każdej zmianie - Z
trackAngular wie, który element się zmienił i aktualizuje tylko jego - To znacznie przyspiesza działanie aplikacji przy dużych listach
8. Najlepsze praktyki
✅ Co robić:
- Używaj @for w nowych projektach – jest prostszy i lepszy
- Zawsze używaj track – poprawia wydajność Angulara
- Używaj unikalnych ID dla track –
track item.idzamiasttrack $index - Używaj @empty – dla lepszego UX gdy lista jest pusta
- Dawaj sensowne nazwy zmiennym –
filmzamiastitem
❌ Czego unikać:
- Zapominania o track – Angular będzie pokazywał ostrzeżenia
- Używania track $index – gdy masz dostęp do unikalnego ID
- Zbyt zagnieżdżonych pętli – lepiej podziel na komponenty
- Modyfikowania tablicy podczas iteracji – może powodować błędy
Przykład dobrej praktyki:
// ✅ Dobra praktyka
get filtrowane(): Film[] {
return this.filmy.filter(film => film.rating >= this.minRating);
}<!-- ✅ Używaj metod/getterów dla złożonej logiki -->
@for (film of filtrowane; track film.id) {
<div>{{ film.title }}</div>
}9. Ćwiczenia do samodzielnego wykonania
- Lista użytkowników: Wyświetl użytkowników z różnymi rolami i statusami
- Lista zadań: Stwórz listę zadań z możliwością dodawania i usuwania
- Tabela wyników: Wyświetl tabelę z wynikami gier/meczów z sortowaniem
- Galeria zdjęć: Lista zdjęć z opisami (użyj URLs do obrazków)
- Menu restauracji: Kategorie dań z zagnieżdżonymi listami pozycji