Przejdź do treści
School IT / Przestrzeń do nauki

@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.

Angular 22 @if @else @else if operatory logiczne alert *ngIf 45 min
CEL LEKCJI

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 if i dobierzesz kolejność warunków.
  • Połączysz warunki operatorami &&, || i !.
  • Wyjaśnisz różnicę między elementem usuniętym przez @if a ukrytym przez [hidden].
  • Zbudujesz formularz, który znika po wysłaniu i zostawia podsumowanie.
  • Rozpoznasz starszą składnię *ngIf w cudzym kodzie.

Przygotowanie: lekcje 08–11 — umiesz obsłużyć kliknięcie, trzymasz dane w sygnałach. Przewidywany czas: 45 min z zadaniami.

TEORIA

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.

TEORIA

Trzy postacie bloku @if

składnia
@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() &gt; 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>&amp;&amp;</code></td><td>i — oba warunki prawdziwe</td><td><code>wolne() &gt; 0 &amp;&amp; wolne() &lt;= 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>
PRZYKŁAD 1

Komunikat po kliknięciu

Komunikat istnieje tylko wtedy, gdy sygnał zapisany ma wartość true. Zielona ramka to klasy Bootstrapa alert alert-success.

src/app/nowa-12-1/demo.ts — pełny plik
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);
    }
}
src/app/nowa-12-1/demo.html — pełny plik
<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>
wynik po pierwszym kliknięciu
[ 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?

PRZYKŁAD 2

Jeden z dwóch: @else

Gdy są wolne miejsca — przycisk zapisu. Gdy nie ma — szara etykieta. Zawsze widać dokładnie jedno z nich.

src/app/nowa-12-2/demo.ts — pełny plik
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);
    }
}
src/app/nowa-12-2/demo.html — pełny plik
<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>
wynik po dwóch kliknięciach
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ę.

PRZYKŁAD 3

Kilka możliwości: @else if

Wynik testu w punktach zamieniamy na ocenę. Kolejność warunków ma znaczenie — sprawdzamy od najwyższego progu.

src/app/nowa-12-3/demo.ts — pełny plik
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)));
    }
}
src/app/nowa-12-3/demo.html — pełny plik
<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>
wynik po czterech kliknięciach +10
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?

PRZYKŁAD 4

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.

src/app/nowa-12-4/demo.ts — pełny plik
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);
    }
}
src/app/nowa-12-4/demo.html — pełny plik
<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>
trzy stany jednej karty
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.

ELEMENTY WBUDOWANE

Zestawienie elementów

ElementZnaczenieUwagi
@if (warunek) { … }fragment tylko przy prawdziwym warunkuBez importu.
@else { … }fragment w przeciwnym razieZaraz po } bloku @if.
@else if (warunek) { … }kolejny warunek w łańcuchuWygrywa pierwszy pasujący.
&&, ||, !i, lub, nieJak w TypeScripcie.
alert alert-successramka komunikatuTeż alert-danger, alert-warning, alert-info.
form-labelpodpis nad polemBootstrap.
text-success, text-danger…kolor tekstuBootstrap.
*ngIf="warunek"starsza składniaWymaga importu NgIf.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
@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 @elseNG5002: @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 @ifBłą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

Zadania

ZAD 1Tryb ciemny★☆☆

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.

ZAD 2Pokaż szczegóły★☆☆

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.

ZAD 3Kontrola wieku★★☆

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.

ZAD 4Pasek promocji★★☆

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() }}.

ZAD 5Quiz z jednym pytaniem★★★

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 { … }.

ZAD 6Logowanie na niby★★★

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() }}.

PODSUMOWANIE

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 ().
  • @else i @else if wybierają 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 *ngIf robi to samo i wymaga importu NgIf.

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.

Postęp zapisuje się w tej przeglądarce.