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

@switch — jedna wartość, wiele możliwości

Wydarzenie może mieć jeden z kilku stanów: wolne miejsca, ostatnie miejsca, brak miejsc, odwołane. Łańcuch @else if na cztery piętra robi się nieczytelny. Gdy porównujesz jedną wartość z listą możliwości, wygodniejszy jest @switch.

Angular 22 @switch @case @default nav-tabs 45 min
CEL LEKCJI

Czego się dziś nauczysz

  • Wybierzesz fragment szablonu na podstawie jednej wartości blokami @switch i @case.
  • Obsłużysz nieprzewidziane wartości blokiem @default.
  • Porównasz @switch z łańcuchem @else if i wybierzesz właściwy.
  • Użyjesz @switch wewnątrz pętli @for.
  • Porównasz zarówno teksty (w apostrofach), jak i liczby.
  • Zbudujesz zakładki Bootstrapa sterowane sygnałem, bez JavaScriptu Bootstrapa.

Przygotowanie: lekcje 12–13 — znasz @if, @else if i @for. Przewidywany czas: 45 min z zadaniami.

TEORIA

Jedna wartość, wiele odpowiedzi

Pole status może mieć wartość 'wolne', 'ostatnie', 'brak' albo 'odwolane'. Dla każdej chcesz pokazać inną etykietę. Z @if wyszłoby cztery razy to samo porównanie status() === …. @switch zapisuje to krócej: „spójrz na status i pokaż przypadek, który pasuje”. Znasz to z instrukcji switch w TypeScripcie.

składnia
@switch (status()) {
  @case ('wolne') { <span>…</span> }
  @case ('brak') { <span>…</span> }
  @default { <span>żaden przypadek nie pasuje</span> }
}
@END
<div class="sd-scroll">
<table class="sd-table">
          <tr><th style="width:22%">Część</th><th>Znaczenie</th></tr>
          <tr><td><code>@switch (x)</code></td><td>wartość, którą sprawdzamy — zwykle sygnał albo pole elementu w pętli</td></tr>
          <tr><td><code>@case (y)</code></td><td>fragment, gdy <code>x === y</code>. Wielkość liter ma znaczenie, typ też: <code>'1'</code> to nie <code>1</code></td></tr>
          <tr><td><code>@default</code></td><td>gdy żaden przypadek nie pasuje; nieobowiązkowy, ale łapie literówki w danych</td></tr>
          </table>
</div>
@@BOX info | Bez break
<p>W przeciwieństwie do <code>switch</code> w TypeScripcie nie piszesz tu <code>break</code>. Każdy <code>@case</code> ma własne klamry i Angular nigdy nie „przelatuje” do następnego. Wewnątrz <code>@switch</code> mogą stać <strong>tylko</strong> bloki <code>@case</code> i <code>@default</code>.</p>
PRZYKŁAD 1

Status wydarzenia

Cztery przyciski ustawiają status, a @switch wybiera etykietę w odpowiednim kolorze.

src/app/nowa-14-1/demo.ts — pełny plik
import { Component, signal } from '@angular/core';

@Component({
    selector: 'app-demo',
    imports: [],
    templateUrl: './demo.html'
})
export class Demo {
    readonly status = signal('wolne');

    ustaw(nowy: string): void {
        this.status.set(nowy);
    }
}
src/app/nowa-14-1/demo.html — pełny plik
<div class="p-3">
  <div class="mb-3">
    <button class="btn btn-sm btn-outline-secondary me-1" (click)="ustaw('wolne')">wolne</button>
    <button class="btn btn-sm btn-outline-secondary me-1" (click)="ustaw('ostatnie')">ostatnie</button>
    <button class="btn btn-sm btn-outline-secondary me-1" (click)="ustaw('brak')">brak</button>
    <button class="btn btn-sm btn-outline-secondary" (click)="ustaw('odwolane')">odwołane</button>
  </div>

  @switch (status()) {
    @case ('wolne') {
      <span class="badge text-bg-success fs-6">Są wolne miejsca</span>
    }
    @case ('ostatnie') {
      <span class="badge text-bg-warning fs-6">Ostatnie miejsca!</span>
    }
    @case ('brak') {
      <span class="badge text-bg-danger fs-6">Brak miejsc</span>
    }
    @case ('odwolane') {
      <span class="badge text-bg-secondary fs-6">Wydarzenie odwołane</span>
    }
  }
</div>
wynik po kliknięciu „ostatnie”
[wolne] [ostatnie] [brak] [odwołane]
[Ostatnie miejsca!]   (żółta etykieta)

Każdy przycisk pokazuje inną etykietę. Zawsze widać dokładnie jedną.

Zatrzymaj się

Porównaj ten zapis z łańcuchem @if / @else if z lekcji 12. Ile razy trzeba by tam napisać status()?

PRZYKŁAD 2

Nieznana wartość: @default

Dane często przychodzą z zewnątrz — z pliku, z serwera, od innego programisty. Ktoś wpisze 'Wolne' wielką literą albo 'zamkniete', którego nie przewidziałeś. Tu status wpisujesz ręcznie w pole.

src/app/nowa-14-2/demo.ts — pełny plik
import { Component, signal } from '@angular/core';

@Component({
    selector: 'app-demo',
    imports: [],
    templateUrl: './demo.html'
})
export class Demo {
    readonly status = signal('wolne');
}
src/app/nowa-14-2/demo.html — pełny plik
<div class="p-3">
  <input #pole class="form-control mb-2" style="width: 18rem" placeholder="wpisz status">
  <button class="btn btn-primary mb-3" (click)="status.set(pole.value)">Ustaw</button>

  <p>
    @switch (status()) {
      @case ('wolne') { <span class="badge text-bg-success">Są wolne miejsca</span> }
      @case ('brak') { <span class="badge text-bg-danger">Brak miejsc</span> }
      @default { <span class="badge text-bg-dark">Nieznany status: „{{ status() }}”</span> }
    }
  </p>
</div>
wynik dla wpisanego „Wolne”
[Nieznany status: „Wolne”]   (ciemna etykieta)

Tylko dokładne wolne i brak trafiają we właściwy przypadek — reszta ląduje w @default, który pokazuje, co przyszło. Zauważ (click)="status.set(pole.value)" — jedno proste wywołanie można zapisać wprost w szablonie; gdy kod robi się dłuższy, przenieś go do metody.

Zatrzymaj się

Jak poprawić program, żeby Wolne i WOLNE też trafiały w przypadek wolne? Wystarczy jedna metoda tekstowa z lekcji 05.

PRZYKŁAD 3

@switch wewnątrz @for

Każde wydarzenie na liście ma własny status, więc każdy wiersz pokazuje inną etykietę. To najczęstsze użycie @switch w prawdziwych aplikacjach.

src/app/nowa-14-3/demo.ts — pełny plik
import { Component, signal } from '@angular/core';

@Component({
    selector: 'app-demo',
    imports: [],
    templateUrl: './demo.html'
})
export class Demo {
    readonly wydarzenia = signal([
        { id: 1, tytul: 'Turniej retro', status: 'wolne' },
        { id: 2, tytul: 'Noc robotów', status: 'brak' },
        { id: 3, tytul: 'Warsztat druku 3D', status: 'ostatnie' },
        { id: 4, tytul: 'Hackathon', status: 'odwolane' },
    ]);
}
src/app/nowa-14-3/demo.html — pełny plik
<div class="p-3">
  <ul class="list-group" style="width: 28rem">
    @for (w of wydarzenia(); track w.id) {
      <li class="list-group-item d-flex justify-content-between">
        {{ w.tytul }}
        @switch (w.status) {
          @case ('wolne') { <span class="badge text-bg-success">wolne</span> }
          @case ('ostatnie') { <span class="badge text-bg-warning">ostatnie</span> }
          @case ('brak') { <span class="badge text-bg-danger">brak</span> }
          @default { <span class="badge text-bg-secondary">odwołane</span> }
        }
      </li>
    }
  </ul>
</div>
wynik w przeglądarce
Turniej retro        [wolne]
Noc robotów          [brak]
Warsztat druku 3D    [ostatnie]
Hackathon            [odwołane]

Wewnątrz pętli porównujemy w.status — bez nawiasów, bo w to zwykły obiekt, nie sygnał.

Zatrzymaj się

Status odwolane obsługuje tu @default. Czy to dobry pomysł? Co pokaże lista, jeśli ktoś wpisze w danych 'wolen'?

PRZYKŁAD 4

Zakładki sterowane sygnałem

Zakładki (nav nav-tabs) to klasyczny element stron. Sygnał pamięta, która zakładka jest aktywna; @switch pokazuje odpowiednią treść, a wiązanie [class.active] podświetla kliknięty nagłówek.

src/app/nowa-14-4/demo.ts — pełny plik
import { Component, signal } from '@angular/core';

@Component({
    selector: 'app-demo',
    imports: [],
    templateUrl: './demo.html'
})
export class Demo {
    readonly zakladka = signal('opis');
}
src/app/nowa-14-4/demo.html — pełny plik
<div class="p-3" style="width: 32rem">
  <ul class="nav nav-tabs">
    <li class="nav-item">
      <button class="nav-link" [class.active]="zakladka() === 'opis'" (click)="zakladka.set('opis')">Opis</button>
    </li>
    <li class="nav-item">
      <button class="nav-link" [class.active]="zakladka() === 'regulamin'" (click)="zakladka.set('regulamin')">Regulamin</button>
    </li>
    <li class="nav-item">
      <button class="nav-link" [class.active]="zakladka() === 'kontakt'" (click)="zakladka.set('kontakt')">Kontakt</button>
    </li>
  </ul>

  <div class="border border-top-0 p-3">
    @switch (zakladka()) {
      @case ('opis') { <p>Turniej gier retro dla uczniów wszystkich klas. Sala 12, piątek, 16:00.</p> }
      @case ('regulamin') { <p>Drużyny dwuosobowe. Gramy na sprzęcie szkolnym. Obowiązuje fair play.</p> }
      @case ('kontakt') { <p>Opiekun koła: sala 12, przerwa po 3. lekcji.</p> }
    }
  </div>
</div>
wynik po kliknięciu „Regulamin”
 Opis | [Regulamin] | Kontakt
 Drużyny dwuosobowe. Gramy na sprzęcie szkolnym. Obowiązuje fair play.

Nie potrzebujesz JavaScriptu Bootstrapa — wszystko robi jeden sygnał. border border-top-0 rysuje ramkę pod zakładkami bez górnej krawędzi.

Zatrzymaj się

Nazwa zakładki jest wpisana w szablonie aż dziewięć razy. Wpisz w jednym @case literówkę ('regulamn'). Co się stanie po kliknięciu i czy kompilator to wyłapie?

ELEMENTY WBUDOWANE

Zestawienie elementów

ElementZnaczenieUwagi
@switch (wartość) { … }wybór jednego przypadkuBez importu.
@case ('tekst'), @case (3)przypadek przy równości ===Tekst w apostrofach, liczba bez.
@default { … }wszystko inneNajwyżej jeden.
nav nav-tabs, nav-item, nav-linkzakładki Bootstrapaactive — podświetlona.
border, border-top-0ramka, ramka bez góryBootstrap.
btn-sm, me-1mały przycisk, mały odstępZ lekcji 03.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
@case 'wolne' { … }NG5002 — wartość w @case musi być w nawiasach okrągłych.
@case (wolne) bez apostrofówAngular szuka pola wolne w klasie i zgłasza błąd, że go nie ma. Teksty zawsze w apostrofach.
@case ('Wolne'), a w danych 'wolne'Nic się nie pokazuje (albo @default). Porównanie uwzględnia wielkość liter.
@case ('1') przy sygnale z liczbąBłąd typu — tekst nie jest porównywalny z liczbą. Liczby bez apostrofów: @case (1).
Treść między @switch a pierwszym @caseNG5002: @switch block can only contain @case and @default blocks.
Dwa bloki @defaultNG5002: @switch block can only have one @default block.
ZADANIA

Zadania

ZAD 1Dzień tygodnia★☆☆

Sygnał przechowuje numer dnia od 1 do 7. Przyciski „poprzedni” i „następny” zmieniają go w kółko (po 7 jest 1, przed 1 jest 7). @switch pokazuje nazwę dnia.

Kryterium sukcesu: wszystkie siedem nazw się wyświetla, a przejścia 7 → 1 i 1 → 7 działają.

Podpowiedź

Porównujesz liczby, więc @case (1) — bez apostrofów. „W kółko” zrobisz warunkiem w update.

ZAD 2Ikona pogody★☆☆

Cztery przyciski: „słońce”, „deszcz”, „śnieg”, „burza” ustawiają sygnał pogoda. @switch pokazuje duże emoji (☀️ 🌧️ ❄️ ⛈️) i jedno zdanie porady, np. „Weź parasol”.

Kryterium sukcesu: każdy przycisk zmienia emoji i poradę, zawsze widać jedną parę.

Podpowiedź

Emoji powiększysz klasą display-1.

ZAD 3Statusy zamówień w sklepiku★★☆

Tablica zamówień { id, produkt, status }, gdzie status to 'nowe', 'w realizacji', 'gotowe' albo 'odebrane'. Pokaż listę z etykietą w innym kolorze dla każdego statusu oraz przyciskiem „Dalej”, który przesuwa status zamówienia do następnego etapu (przy „odebrane” przycisk znika).

Kryterium sukcesu: etykiety mają cztery różne kolory, „Dalej” zmienia status tylko jednego zamówienia, a przy ostatnim etapie przycisku nie ma.

Podpowiedź

Metoda dalej(id) robi update z map; następny status wybierz w metodzie pomocniczej ze switch w TypeScripcie.

ZAD 4Kalkulator czterech działań★★☆

Dwa pola tekstowe (#a, #b) i cztery przyciski: + − × ÷. Kliknięcie zapisuje liczby i wybrane działanie w sygnałach. @switch pokazuje wynik odpowiedniego działania; przy dzieleniu przez zero — komunikat o błędzie zamiast Infinity.

Kryterium sukcesu: każde działanie daje poprawny wynik, a dzielenie przez zero pokazuje komunikat.

Podpowiedź

Liczby z pól: Number(a.value). W przypadku dzielenia użyj @if wewnątrz @case ('/').

ZAD 5Zakładki z licznikiem★★★

Rozbuduj przykład 4 o czwartą zakładkę „Zapisani”, która pokazuje listę uczestników (sygnał z tablicą, @for z @empty) i pole z przyciskiem do dopisania osoby. Na nagłówku zakładki pokaż liczbę zapisanych w odznace, np. „Zapisani [3]”.

Kryterium sukcesu: przełączanie zakładek działa, dopisanie osoby zwiększa liczbę w nagłówku, a pusta lista pokazuje komunikat.

Podpowiedź

Zawartość jednego @case może być dowolnie długa — także z @for. Odznaka: <span class="badge text-bg-secondary">{{ osoby().length }}</span>.

ZAD 6Sygnalizator★★★

Zrób sygnalizację świetlną: fazy 'czerwone', 'czerwone-zolte', 'zielone', 'zolte'. Przycisk „Dalej” przechodzi do następnej fazy w kółko. Narysuj trzy koła (div z klasą rounded-circle, szerokość i wysokość 60 px) i zapal właściwe — zgaszone są szare. Pod sygnalizatorem @switch pokazuje komunikat dla kierowcy („Stój”, „Przygotuj się”, „Jedź”, „Zwolnij”).

Kryterium sukcesu: kolejność faz zgadza się z prawdziwą sygnalizacją, po żółtym wraca czerwone, a komunikat pasuje do świateł.

Podpowiedź

Kolejność faz zapisz w tablicy, a w sygnale trzymaj numer fazy; nazwę fazy daje computed. Koła zapalasz wiązaniem [style.background] z operatorem ?:.

PODSUMOWANIE

Co trzeba zapamiętać

  • @switch (wartość) { @case (x) { … } } pokazuje przypadek, w którym wartość jest dokładnie równa x.
  • @default łapie wszystko, czego nie przewidziałeś — także literówki w danych.
  • Nie ma break i nie ma „przelatywania” do następnego przypadku.
  • Teksty w @case piszesz w apostrofach, liczby bez.
  • @switch sprawdza się przy statusach, zakładkach i wszędzie tam, gdzie jedna wartość ma kilka odpowiedzi; progi liczbowe („co najmniej”) zostają przy @if.

Sprawdź się: kiedy wybierzesz @switch, a kiedy łańcuch @if/@else if? Podaj po jednym przykładzie z lekcji 12 i 14.

Dokumentacja: angular.dev — blok @switch.

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