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

Zdarzenia ( ) i zmienne szablonowe #

Do tej pory dane płynęły w jedną stronę: z klasy na ekran. Teraz odwrócisz kierunek — kliknięcie i pisanie w polu uruchomią metodę w klasie. Poznasz też zmienną szablonową, która daje szablonowi dostęp do konkretnego pola formularza.

Angular 22 (click) (input) $event (keyup.enter) #zmienna 45 min
CEL LEKCJI

Czego się dziś nauczysz

  • Obsłużysz kliknięcie zapisem (click)="metoda()" i zmienisz pole klasy w metodzie.
  • Przekażesz metodzie własny argument, żeby jedna metoda obsłużyła kilka przycisków.
  • Odczytasz wpisany tekst z obiektu $event w zdarzeniu (input).
  • Odróżnisz (input) od (change).
  • Nadasz elementowi nazwę #pole i użyjesz jej, żeby odczytać wartość, wyczyścić pole i ustawić w nim kursor.
  • Zareagujesz tylko na klawisz Enter zapisem (keyup.enter).
  • Rozpoznasz ostrzeżenie NG8111 przy metodzie bez nawiasów.

Przygotowanie: lekcje 05–07 — umiesz pokazać dane i przekazać wartość do właściwości. Przewidywany czas: 45 min z zadaniami.

TEORIA

Ekran odpowiada użytkownikowi

Wiązanie [ ] prowadzi dane z klasy do szablonu. Zdarzenia prowadzą je w przeciwną stronę: użytkownik coś robi, a klasa się o tym dowiaduje. Zapamiętaj tę symetrię: nawias kwadratowy to wejście do elementu, nawias okrągły to wyjście z niego. Dwukierunkowe [(ngModel)] z lekcji 17 to po prostu oba naraz.

schemat
<button (click)="zapisz()">Zapisz</button>
<!--     ↑ zdarzenie   ↑ co zrobić — wywołanie metody z klasy -->

W nawiasach okrągłych stoi nazwa zdarzenia bez przedrostka on (w czystym HTML-u byłoby onclick). W cudzysłowie — zwykle wywołanie metody, z nawiasami. Po każdym obsłużonym zdarzeniu Angular sam odświeża szablon, więc nowa wartość pola od razu pojawia się na ekranie.

ZdarzenieKiedy zachodzi
(click)kliknięcie myszą; na przycisku także naciśnięcie Enter lub spacji
(input)po każdej zmianie zawartości pola, także przy wklejeniu
(change)dopiero po wyjściu z pola po zmianie (przy polach tekstowych)
(keyup.enter)puszczenie klawisza Enter — Angular sam sprawdza, który to klawisz
(blur), (focus)wyjście z pola, wejście do pola

Decyzje trzymamy w klasie

W obsłudze zdarzenia wolno napisać krótką instrukcję, a kilka oddzielić średnikiem. Ale wszystko, co ma warunek albo więcej niż jedną czynność na danych, przenieś do metody. Szablon ma pokazywać, nie decydować.

TEORIA

Skąd wziąć wpisany tekst: $event i #zmienna

$event

Nazwa $event oznacza obiekt opisujący zdarzenie. Przy kliknięciu rzadko jest potrzebny, przy polu tekstowym — prawie zawsze, bo przez $event.target dochodzisz do pola i jego wartości.

Zmienna szablonowa #nazwa

Zapis #pole przy elemencie HTML nadaje mu nazwę, przez którą sięgasz do niego w tym samym szablonie: do wartości (pole.value), do metody ustawiającej kursor (pole.focus()). To najprostsza droga, gdy potrzebujesz wartości jednego pola w chwili kliknięcia — bez budowania całego formularza.

zmienna szablonowa
<input #pole class="form-control">
<button (click)="pokaz(pole.value)">Pokaż</button>
<!-- pole.value — tekst wpisany w polu w chwili kliknięcia -->

Zmienna szablonowa żyje tylko w szablonie — w klasie jej nie ma. Do klasy trafia tylko to, co przekażesz jako argument metody.

PRZYKŁAD 1

Pierwsze kliknięcie

Nawias okrągły przy nazwie zdarzenia, a w cudzysłowie — co ma się wtedy stać. Pole wiadomosc nie jest readonly, bo metoda je zmienia.

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

@Component({
    selector: 'app-demo',
    imports: [],
    templateUrl: './demo.html'
})
export class Demo {
    wiadomosc = 'Jeszcze nic nie kliknięto.';

    zapisz(): void {
        this.wiadomosc = 'Dziękujemy za zapis!';
    }
}
src/app/nowa-08-1/demo.html — pełny plik
<div class="p-3">
  <button class="btn btn-primary" (click)="zapisz()">Zapisz się</button>
  <p class="mt-3">{{ wiadomosc }}</p>
</div>
wynik w przeglądarce
przed kliknięciem:  Jeszcze nic nie kliknięto.
po kliknięciu:      Dziękujemy za zapis!

Metoda nazywa się zapisz, a w szablonie piszemy zapisz() — z nawiasami, bo chcemy ją wywołać.

Zatrzymaj się

Kliknij drugi raz. Dlaczego nic się już nie zmienia, chociaż metoda wykonała się ponownie? Potem usuń nawiasy ((click)="zapisz") i sprawdź terminal — jaki kod ma ostrzeżenie i co się dzieje po kliknięciu?

PRZYKŁAD 2

Metoda z argumentem

Jedna metoda obsługuje trzy przyciski, bo w argumencie dostaje informację, który to był.

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

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

    wybierz(nazwa: string): void {
        this.wybrane = nazwa;
    }
}
src/app/nowa-08-2/demo.html — pełny plik
<div class="p-3">
  <button class="btn btn-outline-primary me-2" (click)="wybierz('Turniej retro')">Turniej retro</button>
  <button class="btn btn-outline-primary me-2" (click)="wybierz('Noc robotów')">Noc robotów</button>
  <button class="btn btn-outline-primary" (click)="wybierz('Druk 3D')">Druk 3D</button>
  <p class="mt-3">Wybrano: {{ wybrane }}</p>
</div>
wynik po kliknięciu „Noc robotów”
Wybrano: Noc robotów

Tekst w apostrofach to zwykły argument — taki sam, jaki przekazałbyś w TypeScripcie. Apostrofy, bo cały atrybut stoi już w cudzysłowie.

Zatrzymaj się

Gdyby przycisków było dwadzieścia, ten szablon miałby dwadzieścia niemal identycznych wierszy. Rozwiązanie czeka w lekcji 13, przy @for.

PRZYKŁAD 3

$event — tekst wpisywany na bieżąco

Zdarzenie (input) zachodzi po każdej zmianie zawartości pola. Metoda dostaje obiekt zdarzenia i wyciąga z niego wpisany tekst.

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

@Component({
    selector: 'app-demo',
    imports: [],
    templateUrl: './demo.html'
})
export class Demo {
    tekst = '';
    znakow = 0;

    przepisz(zdarzenie: Event): void {
        const pole = zdarzenie.target as HTMLInputElement;
        this.tekst = pole.value;
        this.znakow = pole.value.length;
    }
}
src/app/nowa-08-3/demo.html — pełny plik
<div class="p-3">
  <input class="form-control" style="width: 22rem"
         placeholder="Nazwa wydarzenia" (input)="przepisz($event)">
  <p class="mt-2">Wpisano: {{ tekst }} ({{ znakow }} znaków)</p>
</div>
wynik po wpisaniu „Turniej”
Wpisano: Turniej (7 znaków)

zdarzenie.target to element, który wywołał zdarzenie. TypeScript nie wie, że to pole tekstowe, więc mówimy mu to zapisem as HTMLInputElement — dopiero wtedy wolno odczytać .value. Klasa form-control to pole tekstowe w stylu Bootstrapa.

Zatrzymaj się

Zmień (input) na (change) i wpisz kilka liter. Kiedy teraz zmienia się napis pod polem? Wróć do (input).

PRZYKŁAD 4

Zmienna szablonowa i klawisz Enter

Pole ma nazwę #pole. Przycisk i klawisz Enter przekazują jego wartość do metody, a potem czyszczą pole. Drugi przycisk ustawia kursor w polu, nie dotykając klasy.

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

@Component({
    selector: 'app-demo',
    imports: [],
    templateUrl: './demo.html'
})
export class Demo {
    lista: string[] = [];
    komunikat = '';

    dodaj(nazwa: string): void {
        const czysta = nazwa.trim();
        if (czysta.length === 0) {
            this.komunikat = 'Wpisz nazwę wydarzenia.';
            return;
        }
        this.lista = [...this.lista, czysta];
        this.komunikat = 'Dodano: ' + czysta;
    }
}
src/app/nowa-08-4/demo.html — pełny plik
<div class="p-3" style="width: 26rem">
  <input #pole class="form-control mb-2" placeholder="Nazwa wydarzenia"
         (keyup.enter)="dodaj(pole.value); pole.value = ''">
  <button class="btn btn-primary me-2" (click)="dodaj(pole.value); pole.value = ''; pole.focus()">Dodaj</button>
  <button class="btn btn-outline-secondary" (click)="pole.focus()">Kursor do pola</button>
  <p class="mt-3 mb-1">{{ komunikat }}</p>
  <p class="text-muted">Pozycji: {{ lista.length }} — {{ lista.join(', ') }}</p>
</div>
wynik po dodaniu dwóch nazw i próbie dodania pustej
Wpisz nazwę wydarzenia.
Pozycji: 2 — Turniej retro, Noc robotów

W obsłudze zdarzenia stoją trzy instrukcje oddzielone średnikiem: wywołanie metody, wyczyszczenie pola i ustawienie kursora. Decyzja (czy nazwa nie jest pusta) jest w klasie. trim() obcina spacje z początku i końca, więc same spacje też liczą się jako pusta nazwa. Nową pozycję dokładamy, tworząc nową tablicę [...this.lista, czysta] — dlaczego tak, wyjaśni się w lekcji 10.

Zatrzymaj się

lista.join(', ') skleja elementy tablicy w jeden tekst. To prowizorka — porządną listę, z każdą pozycją w osobnym wierszu, zrobisz w lekcji 13 blokiem @for.

ELEMENTY WBUDOWANE

Zestawienie elementów

ElementZnaczenieUwagi
(click)="metoda()"obsługa kliknięciaBez nawiasów — ostrzeżenie NG8111.
(click)="metoda('tekst')"wywołanie z argumentemTekst w apostrofach.
(input)="metoda($event)"reakcja na każdą zmianę pola$event trzeba przekazać jawnie.
(change)reakcja po wyjściu z polaPrzy polu tekstowym.
(keyup.enter)tylko klawisz EnterTakże (keyup.escape).
zdarzenie.target as HTMLInputElementelement, który wywołał zdarzeniePotem .value.
#polenazwa elementu w szablonieTylko w tym szablonie.
pole.valuetekst w poluPrzypisanie pole.value = '' czyści pole.
pole.focus()kursor do polaMetoda elementu HTML.
tekst.trim()tekst bez spacji na brzegachDo sprawdzania pustych wpisów.
[...lista, x]nowa tablica z dodanym elementemSzczegóły w lekcji 10.
lista.join(', ')tablica sklejona w tekstProwizorka do lekcji 13.
form-controlpole tekstowe BootstrapaNa całą szerokość rodzica.
CZĘSTE BŁĘDY

Zanim utkniesz

ZapisProblem
(click)="zapisz" bez nawiasówKliknięcie nic nie robi, w terminalu ostrzeżenie NG8111. Bez nawiasów podajesz funkcję, ale nikt jej nie wywołuje.
(onclick)="zapisz()"Kompiluje się bez błędu, ale kliknięcie nic nie robi — zdarzenia o nazwie onclick nie ma. W szablonach nazwy są bez przedrostka on.
(input)="przepisz()" bez $eventBłąd kompilacji: metoda oczekuje argumentu. Obiekt zdarzenia trzeba przekazać jawnie.
zdarzenie.target.value bez as HTMLInputElementBłąd TypeScriptu: target może być czymkolwiek, więc nie ma pewności, że ma value.
(change) zamiast (input) przy pisaniu na bieżącoReakcja dopiero po wyjściu z pola.
this.pole.value w metodzie klasyTS2339 — zmienna szablonowa nie istnieje w klasie. Przekaż pole.value jako argument.
(click)="licznik = licznik + 1; if (…)"Instrukcji if nie wolno w szablonie. Warunki przenieś do metody.
ZADANIA

Zadania

ZAD 1Zapisano czy anulowano★☆☆

Zrób dwa przyciski: zielony „Potwierdź” ustawia napis „zapisano”, szary „Anuluj” — „anulowano”. Użyj jednej metody z argumentem.

Kryterium sukcesu: oba przyciski zmieniają ten sam napis na ekranie, a w klasie jest jedna metoda.

Podpowiedź

Wzoruj się na przykładzie 2.

ZAD 2Lubię to★☆☆

Zrób przycisk „👍 Lubię to” i przycisk „Wyzeruj”. Pod spodem napis „Polubień: 0”, który rośnie o 1 po każdym kliknięciu pierwszego przycisku i wraca do zera po kliknięciu drugiego.

Kryterium sukcesu: licznik rośnie po każdym kliknięciu i wraca do 0 po wyzerowaniu.

Podpowiedź

Pole polubienia = 0 i dwie metody: polub() z this.polubienia = this.polubienia + 1 oraz wyzeruj().

ZAD 3Licznik znaków z limitem★★☆

Zrób pole na krótki opis wydarzenia, pod którym jest napis „Znaków: 12 / 40”. Gdy tekst przekroczy 40 znaków, pole ma dostać czerwoną ramkę, a pod nim ma się pojawić komunikat „Za długi opis”.

Kryterium sukcesu: ramka i komunikat pojawiają się dokładnie od 41. znaku i znikają po skróceniu tekstu.

Podpowiedź

(input) i $event jak w przykładzie 3. Ramka: [class.is-invalid]="znakow > 40" (klasa Bootstrapa dla błędnego pola), komunikat: [hidden] z lekcji 06.

ZAD 4Kalkulator biletów★★☆

Zrób kartę biletu za 12.50 zł z przyciskami „−” i „+” zmieniającymi liczbę biletów od 0 do 10. Pokaż liczbę biletów i kwotę do zapłaty z dwoma miejscami po kropce. Przycisk „−” ma być zablokowany przy 0, a „+” przy 10.

Kryterium sukcesu: liczba nie spada poniżej 0 ani nie rośnie ponad 10, kwota zgadza się z rachunkiem, a przyciski blokują się na granicach.

Podpowiedź

Granice sprawdź w metodach dodaj() i odejmij() instrukcją if. Blokada: [disabled]="ilosc === 0".

ZAD 5Lista, która sprząta po sobie★★★

Rozbuduj przykład 4 o trzy rzeczy: (a) komunikat o pustej nazwie ma być czerwony, a o dodaniu — zielony; (b) nie da się dodać nazwy, która już jest na liście (komunikat „Takie wydarzenie już jest”); (c) przycisk „Wyczyść listę” usuwa wszystkie pozycje i ustawia kursor w polu.

Kryterium sukcesu: duplikat i pusta nazwa dają czerwone komunikaty, poprawna nazwa — zielony, a po wyczyszczeniu licznik pokazuje 0 i kursor jest w polu.

Podpowiedź

Duplikat sprawdzisz w metodzie: this.lista.includes(czysta). Kolor: pole blad (true/false) ustawiane w metodzie i [class.text-danger]/[class.text-success]. Czyszczenie: this.lista = [].

ZAD 6Przelicznik punktów na ocenę★★★

Zrób pole, w które uczeń wpisuje liczbę punktów z testu (0–50). Na bieżąco, przy każdej zmianie, pokaż ocenę według progów: 0–19 → 1, 20–29 → 2, 30–37 → 3, 38–44 → 4, 45–48 → 5, 49–50 → 6. Gdy wpisano coś, co nie jest liczbą z zakresu 0–50, zamiast oceny pokaż „Podaj liczbę od 0 do 50”. Ocena 1 ma być czerwona, a 5 i 6 — zielone.

Kryterium sukcesu: dla 19, 20, 44, 49 i 51 oraz dla tekstu „abc” widać właściwe oceny albo komunikat.

Podpowiedź

Tekst z pola zamień na liczbę: Number(pole.value); nie-liczba daje NaN, co sprawdzisz przez Number.isNaN(x). Progi — kolejne if w metodzie ocena() albo w metodzie obsługującej (input).

PODSUMOWANIE

Co trzeba zapamiętać

  • (zdarzenie)="metoda()" — nawiasy okrągłe przy zdarzeniu i wywołanie metody z nawiasami.
  • Po obsłużonym zdarzeniu Angular sam odświeża ekran.
  • Metodzie można przekazać własny argument albo $event.
  • (input) reaguje na każdą literę, (change) dopiero po wyjściu z pola, (keyup.enter) — na Enter.
  • #pole daje dostęp do elementu w szablonie: pole.value, pole.focus().
  • Zmienna szablonowa nie istnieje w klasie — wartość przekazujesz argumentem.
  • Warunki i obliczenia należą do klasy, nie do szablonu.

Sprawdź się: masz pole tekstowe i chcesz reagować na każdą literę. Które zdarzenie wybierzesz i co musisz przekazać metodzie? A jeśli wystarczy Ci wartość w chwili kliknięcia przycisku obok?

Dokumentacja: angular.dev — obsługa zdarzeń.

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