@if, @else if, @else — fragmenty pod warunkiem
Komunikat „Dziękujemy za zapis” ma się pojawić dopiero po zapisie. Przycisk „Zapisz” ma zniknąć, gdy brak miejsc. Do pokazywania i usuwania kawałków strony służy blok @if — pierwszy z bloków sterujących Angulara.
Czego się dziś nauczysz
- Pokażesz fragment szablonu tylko wtedy, gdy warunek jest spełniony, blokiem
@if. - Wybierzesz jeden z dwóch fragmentów blokiem
@else. - Wybierzesz jeden z wielu fragmentów łańcuchem
@else ifi dobierzesz kolejność warunków. - Połączysz warunki operatorami
&&,||i!. - Wyjaśnisz różnicę między elementem usuniętym przez
@ifa ukrytym przez[hidden]. - Zbudujesz formularz, który znika po wysłaniu i zostawia podsumowanie.
- Rozpoznasz starszą składnię
*ngIfw cudzym kodzie.
Przygotowanie: lekcje 08–11 — umiesz obsłużyć kliknięcie, trzymasz dane w sygnałach. Przewidywany czas: 45 min z zadaniami.
Strona, która się zmienia
Do tej pory wszystko, co było w szablonie, zawsze było na stronie. Zmieniały się najwyżej liczby i napisy. Prawdziwe strony działają inaczej: komunikat o błędzie pojawia się tylko przy błędzie, formularz znika po wysłaniu, a zamiast przycisku pojawia się napis „Brak miejsc”.
Blok @if mówi: „ten kawałek HTML ma istnieć tylko wtedy, gdy warunek jest prawdziwy”. Gdy warunek jest fałszywy, elementu nie ma w ogóle — nie jest ukryty, tylko usunięty ze strony. Znak @ oznacza: „to nie jest HTML, tylko polecenie dla Angulara”. Bloki @if, @for i @switch nie wymagają żadnych importów.
Trzy postacie bloku @if
@if (warunek) {
<p>widać, gdy warunek jest prawdziwy</p>
}
@if (warunek) {
<p>jeden fragment</p>
} @else {
<p>drugi fragment</p>
}
@if (a) {
<p>pierwszy pasujący</p>
} @else if (b) {
<p>…</p>
} @else {
<p>gdy żaden nie pasuje</p>
}
@END
<p>Warunek stoi <strong>w nawiasach okrągłych</strong> i jest dowolnym wyrażeniem: <code>zapisany()</code>, <code>wolne() > 0</code>, <code>imie() !== ''</code>. W łańcuchu Angular sprawdza warunki od góry i pokazuje <strong>pierwszy</strong> pasujący fragment — reszty już nie sprawdza.</p>
<div class="sd-scroll">
<table class="sd-table">
<tr><th style="width:24%">Operator</th><th style="width:30%">Znaczenie</th><th>Przykład</th></tr>
<tr><td><code>&&</code></td><td>i — oba warunki prawdziwe</td><td><code>wolne() > 0 && wolne() <= 3</code></td></tr>
<tr><td><code>||</code></td><td>lub — wystarczy jeden</td><td><code>status() === 'brak' || odwolane()</code></td></tr>
<tr><td><code>!</code></td><td>zaprzeczenie</td><td><code>!wyslano()</code></td></tr>
</table>
</div>
@@BOX info | @if czy [hidden]?
<p><code>[hidden]</code> z lekcji 06 tylko <strong>ukrywa</strong> element — dalej jest w kodzie strony. <code>@if</code> go <strong>usuwa</strong> i tworzy od nowa, gdy warunek znów będzie prawdziwy. Dlatego pole tekstowe w bloku <code>@if</code> po ponownym pojawieniu się jest puste. W tym kursie do pokazywania i ukrywania używamy <code>@if</code>.</p>
Komunikat po kliknięciu
Komunikat istnieje tylko wtedy, gdy sygnał zapisany ma wartość true. Zielona ramka to klasy Bootstrapa alert alert-success.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-demo',
imports: [],
templateUrl: './demo.html'
})
export class Demo {
readonly zapisany = signal(false);
przelacz(): void {
this.zapisany.update(z => !z);
}
}
<div class="p-3">
<button class="btn btn-primary" (click)="przelacz()">Zapisz / wypisz</button>
@if (zapisany()) {
<div class="alert alert-success mt-3" style="width: 24rem">
Dziękujemy! Jesteś na liście uczestników.
</div>
}
</div>
[ Zapisz / wypisz ]
┌──────────────────────────────────────────────┐
│ Dziękujemy! Jesteś na liście uczestników. │ (zielona ramka)
└──────────────────────────────────────────────┘
Kliknij kilka razy — komunikat pojawia się i znika. Otwórz F12 → Elements: gdy komunikatu nie widać, nie ma go też w kodzie strony. update(z => !z) zamienia true na false i odwrotnie.
Zatrzymaj się
Jak inaczej, dłużej, zapisałbyś metodę przelacz, używając set i odczytu sygnału?
Jeden z dwóch: @else
Gdy są wolne miejsca — przycisk zapisu. Gdy nie ma — szara etykieta. Zawsze widać dokładnie jedno z nich.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-demo',
imports: [],
templateUrl: './demo.html'
})
export class Demo {
readonly wolne = signal(2);
zapisz(): void {
this.wolne.update(n => n - 1);
}
}
<div class="card m-3 p-3" style="width: 22rem">
<h5>Turniej retro</h5>
<p>Wolnych miejsc: {{ wolne() }}</p>
@if (wolne() > 0) {
<button class="btn btn-success" (click)="zapisz()">Zapisz się</button>
} @else {
<span class="badge text-bg-secondary fs-6">Brak miejsc</span>
}
</div>
Turniej retro
Wolnych miejsc: 0
[Brak miejsc] (szara etykieta zamiast przycisku)
Liczba wolnych miejsc nie spadnie poniżej zera, bo po dwóch kliknięciach nie ma już czego kliknąć.
Zatrzymaj się
Zabezpieczeniem przed ujemną liczbą jest tu sam szablon. Czy to wystarczy, czy warto też sprawdzić warunek w metodzie zapisz? Pomyśl o innym przycisku, który mógłby kiedyś wywołać tę samą metodę.
Kilka możliwości: @else if
Wynik testu w punktach zamieniamy na ocenę. Kolejność warunków ma znaczenie — sprawdzamy od najwyższego progu.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-demo',
imports: [],
templateUrl: './demo.html'
})
export class Demo {
readonly punkty = signal(50);
zmien(o: number): void {
this.punkty.update(p => Math.min(100, Math.max(0, p + o)));
}
}
<div class="p-3">
<p class="fs-4">Punkty: {{ punkty() }} / 100</p>
<button class="btn btn-outline-secondary me-2" (click)="zmien(-10)">−10</button>
<button class="btn btn-outline-secondary" (click)="zmien(10)">+10</button>
<p class="mt-3 fs-5">
@if (punkty() >= 90) {
<span class="text-success">Ocena: celujący</span>
} @else if (punkty() >= 75) {
<span class="text-success">Ocena: bardzo dobry</span>
} @else if (punkty() >= 50) {
<span class="text-primary">Ocena: dostateczny lub dobry</span>
} @else if (punkty() >= 30) {
<span class="text-warning">Ocena: dopuszczający</span>
} @else {
<span class="text-danger">Ocena: niedostateczny</span>
}
</p>
</div>
Punkty: 90 / 100
Ocena: celujący
Przy 90 punktach pasują aż trzy warunki (>= 90, >= 75, >= 50), a widać tylko pierwszy. Math.min i Math.max w metodzie pilnują, żeby punkty zostały w przedziale 0–100.
Zatrzymaj się
Przenieś warunek >= 50 na samą górę łańcucha. Jaką ocenę dostanie wtedy uczeń z 95 punktami?
Formularz znika po wysłaniu
Typowy schemat z zadań egzaminacyjnych: wypełniasz pole, klikasz przycisk, formularz znika, a w jego miejscu pojawia się podsumowanie. form-label to podpis pola w stylu Bootstrapa.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-demo',
imports: [],
templateUrl: './demo.html'
})
export class Demo {
readonly imie = signal('');
readonly wyslano = signal(false);
wyslij(tekst: string): void {
this.imie.set(tekst.trim());
this.wyslano.set(true);
}
jeszczeRaz(): void {
this.imie.set('');
this.wyslano.set(false);
}
}
<div class="card m-3 p-3" style="width: 24rem">
@if (!wyslano()) {
<label class="form-label">Imię uczestnika</label>
<input #pole class="form-control mb-2">
<button class="btn btn-primary" (click)="wyslij(pole.value)">Wyślij</button>
} @else if (wyslano() && imie() !== '') {
<p class="text-success">Zapisano: <strong>{{ imie() }}</strong></p>
<button class="btn btn-outline-secondary" (click)="jeszczeRaz()">Zapisz kolejną osobę</button>
} @else {
<p class="text-danger">Nie podano imienia.</p>
<button class="btn btn-outline-secondary" (click)="jeszczeRaz()">Spróbuj jeszcze raz</button>
}
</div>
1. pole „Imię uczestnika” i przycisk Wyślij
2. po wysłaniu „Ola”: Zapisano: Ola [Zapisz kolejną osobę]
3. po wysłaniu pustego: Nie podano imienia. [Spróbuj jeszcze raz]
Pole #pole istnieje tylko wewnątrz pierwszego bloku — po wysłaniu znika razem z nim, a po powrocie jest puste.
Starsza składnia: *ngIf
W projektach sprzed Angulara 17, w części arkuszy egzaminacyjnych i w wielu poradnikach zobaczysz <div *ngIf="zapisany">…</div>, a dla „w przeciwnym razie” — *ngIf="zapisany; else inny" z <ng-template #inny>. To ten sam mechanizm; wymaga importu NgIf z @angular/common. W nowym kodzie pisz @if, ale starszą wersję warto umieć przeczytać.
Zatrzymaj się
Czy warunek wyslano() && imie() !== '' da się skrócić? Pomyśl, co już wiadomo, jeśli Angular doszedł do tego miejsca łańcucha.
Zestawienie elementów
| Element | Znaczenie | Uwagi |
|---|---|---|
@if (warunek) { … } | fragment tylko przy prawdziwym warunku | Bez importu. |
@else { … } | fragment w przeciwnym razie | Zaraz po } bloku @if. |
@else if (warunek) { … } | kolejny warunek w łańcuchu | Wygrywa pierwszy pasujący. |
&&, ||, ! | i, lub, nie | Jak w TypeScripcie. |
alert alert-success | ramka komunikatu | Też alert-danger, alert-warning, alert-info. |
form-label | podpis nad polem | Bootstrap. |
text-success, text-danger… | kolor tekstu | Bootstrap. |
*ngIf="warunek" | starsza składnia | Wymaga importu NgIf. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
@if zapisany() { … } | Błąd kompilacji szablonu. Warunek musi być w nawiasach okrągłych: @if (zapisany()). |
@if (zapisany) { … } | Błąd TS2774: This condition will always return true since this function is always defined. Sam sygnał bez nawiasów jest zawsze „prawdziwy” — dopisz (). |
Tekst albo znacznik między } a @else | NG5002: @else block can only be used after an @if or @else if block. Przejście do nowej linii jest w porządku, inna treść — nie. |
@if (punkty() = 100) | NG5002 — pojedyncze = to przypisanie. Porównanie zapisuje się ===. |
Zmienna #pole użyta poza blokiem @if | Błąd kompilacji: zmienna szablonowa istnieje tylko w bloku, w którym ją utworzono. |
Łańcuch od najniższego progu: >= 30, potem >= 50… | Każdy wynik powyżej 30 trafia w pierwszy warunek. Przy progach „co najmniej” zaczynaj od najwyższego. |
Zadania
Przycisk przełącza sygnał ciemny. Gdy jest włączony, pokaż napis „Tryb ciemny”, w przeciwnym razie „Tryb jasny”. Napis na przycisku też ma się zmieniać i mówić, co się stanie po kliknięciu („Włącz tryb jasny” / „Włącz tryb ciemny”).
Kryterium sukcesu: zawsze widać dokładnie jeden napis, a przycisk zapowiada następny stan.
Podpowiedź
Dwa bloki @if/@else — jeden pod przyciskiem, drugi w środku przycisku.
Zrób kartę wydarzenia z przyciskiem „Pokaż szczegóły”. Po kliknięciu pod tytułem pojawia się akapit z salą i godziną, a napis na przycisku zmienia się na „Ukryj szczegóły”.
Kryterium sukcesu: szczegóły pojawiają się i znikają, a w F12 → Elements akapitu nie ma, gdy jest ukryty.
Podpowiedź
Sygnał logiczny i metoda z update(s => !s) jak w przykładzie 1.
Pole na wiek (#pole) i przycisk „Sprawdź”. Pokaż w ramce alert: „Wstęp wolny” dla 18+, „Wymagana zgoda rodzica” dla 13–17, „Wydarzenie dla starszych” poniżej 13. Pusty wpis albo tekst, który nie jest liczbą, daje czerwony komunikat o błędzie.
Kryterium sukcesu: każda z czterech sytuacji daje inny komunikat w innym kolorze ramki.
Podpowiedź
Tekst z pola zamień na liczbę funkcją Number() i zapisz w sygnale. Błąd rozpoznasz przez Number.isNaN() albo pusty tekst; trzymaj go w osobnym sygnale.
Sygnał koszyk przechowuje kwotę zakupów, zmienianą przyciskami „+10 zł” i „−10 zł”. Nad kwotą pokaż: przy 0 zł — „Koszyk jest pusty”; poniżej 100 zł — „Brakuje X zł do darmowej dostawy”; od 100 zł — zieloną ramkę „Darmowa dostawa!”.
Kryterium sukcesu: dla 0, 40 i 120 zł widać trzy różne komunikaty, a brakująca kwota jest policzona poprawnie.
Podpowiedź
Łańcuch @if / @else if / @else; brakującą kwotę policz w klamrach: {{ 100 - koszyk() }}.
Pokaż pytanie „Który znacznik HTML tworzy łącze?” i cztery przyciski z odpowiedziami. Po kliknięciu przyciski znikają, a w ich miejscu pojawia się „Dobrze!” (zielona ramka) albo „Źle, poprawna odpowiedź to <a>” (czerwona) oraz przycisk „Jeszcze raz”, który przywraca pytanie.
Kryterium sukcesu: po każdej odpowiedzi widać tylko wynik, a „Jeszcze raz” przywraca cztery przyciski.
Podpowiedź
Dwa sygnały: odpowiedziano (true/false) i wybrana (tekst). W szablonie: @if (!odpowiedziano()) { przyciski } @else if (wybrana() === 'a') { … } @else { … }.
Dwa pola: login i hasło (type="password" ukrywa wpisywane znaki). Po kliknięciu „Zaloguj” przy loginie admin i haśle 1234 formularz znika i pojawia się powitanie z przyciskiem „Wyloguj”. Przy złych danych formularz zostaje, a pod nim pojawia się czerwony komunikat z liczbą pozostałych prób. Po trzech nieudanych próbach przycisk „Zaloguj” znika na dobre i pojawia się „Konto zablokowane”.
Kryterium sukcesu: wszystkie stany działają; liczba prób jest pamiętana w sygnale; po zablokowaniu nie da się zalogować nawet poprawnymi danymi.
Podpowiedź
Trzy sygnały: zalogowany, blad, proby. Pozostałe próby: {{ 3 - proby() }}.
Co trzeba zapamiętać
@if (warunek) { … }pokazuje fragment tylko przy prawdziwym warunku; przy fałszywym elementu nie ma na stronie.- Warunek stoi w nawiasach okrągłych; sygnał w warunku — zawsze z
(). @elsei@else ifwybierają dokładnie jeden fragment — pierwszy pasujący od góry.- Warunki łączysz przez
&&i||, a odwracasz przez!. - Zmienna szablonowa żyje tylko w bloku, w którym ją utworzono.
- Starsza składnia
*ngIfrobi to samo i wymaga importuNgIf.
Sprawdź się: uczeń z 75 punktami dostaje w Twoim programie ocenę „dostateczny lub dobry”, a powinien „bardzo dobry”. Gdzie szukasz błędu w pierwszej kolejności?
Dokumentacja: angular.dev — bloki sterujące.