Trudny / egzaminacyjny · lekcja 10
Angular II — data binding, zdarzenia własne i serwisy
W poprzedniej lekcji dane w komponencie były "na sztywno" wpisane w kodzie. Prawdziwe aplikacje muszą reagować na działania użytkownika (kliknięcia, wpisywanie tekstu) i pobierać dane z zewnątrz (z backendu). Ta lekcja pokazuje mechanizm wiązania danych w obie...
Zacznij tutaj
Po co się tego uczymy?
Wprowadzenie
Teoria
Interpolacja jednokierunkowa a [property binding]. {{ wartosc }} (poznane w lekcji 9) wypisuje wartość jako TEKST. Żeby powiązać wartość TypeScript z WŁAŚCIWOŚCIĄ znacznika HTML (np. czy przycisk jest wyłączony), używa się nawiasów kwadratowych: [disabled]="czyZapisywanie" — wiązanie TYLKO w jedną stronę (z TypeScript do HTML).
Wiązanie zdarzeń — (event). Nawiasy okrągłe podpinają zdarzenie HTML do metody w klasie: (click)="zapisz()" wywołuje metodę zapisz() po kliknięciu — bezpośredni odpowiednik @onclick z Blazor albo Clicked="..." z XAML w MAUI. Inne częste zdarzenia: (input) (każde naciśnięcie klawisza), (submit) (wysłanie formularza), (change) (zmiana wartości i utrata fokusu).
Dwukierunkowe wiązanie — [(ngModel)]. Łączy nawiasy kwadratowe I okrągłe naraz (stąd nazwa "banana in a box" — kwadratowe na zewnątrz, okrągłe w środku): <input [(ngModel)]="imie" /> — wpisany tekst automatycznie aktualizuje pole imie w klasie, a programowa zmiana imie automatycznie aktualizuje pole na ekranie. Wymaga zaimportowania FormsModule (widziane już w lekcji 4).
@Input() i @Output() — komunikacja komponent rodzic ↔ potomny. Dokładnie ten sam problem, który w Blazor rozwiązywał [Parameter] i EventCallback:
| Dekorator | Kierunek | Zastosowanie |
|---|---|---|
@Input() nazwa!: string; | rodzic → potomny (dane W DÓŁ) | komponent potomny odbiera dane przekazane z zewnątrz: <app-karta [nazwa]="produkt.nazwa"></app-karta> |
@Output() kliknieto = new EventEmitter<string>(); | potomny → rodzic (zdarzenie W GÓRĘ) | komponent potomny informuje rodzica: this.kliknieto.emit(wartosc);, a rodzic nasłuchuje: <app-karta (kliknieto)="obsluz($event)"></app-karta> |
Serwisy (services) — logika i dane współdzielone. Gdy KILKA komponentów potrzebuje tych samych danych albo tej samej logiki (np. listy produktów, komunikacji z backendem), umieszcza się je w OSOBNEJ klasie — serwisie, oznaczonym dekoratorem @Injectable({ providedIn: 'root' }). Komponent "prosi" o dostęp do serwisu przez KONSTRUKTOR (mechanizm zwany Dependency Injection — ta sama koncepcja, którą poznałeś przy SignInManager/DbContext w lekcjach o ASP.NET Core). Serwis to bezpośredni odpowiednik oddzielenia logiki od UI z lekcji o MVVM w dziale MAUI — komponent zajmuje się TYLKO wyświetlaniem, serwis TYLKO danymi/logiką.
Zobacz zależności
Schemat
Komponent RODZICA (lista-produktow.component.ts)
│
│ <app-karta-produktu [produkt]="p" (dodano)="naDodanie($event)">
▼
Komponent POTOMNY (karta-produktu.component.ts)
@Input() produkt!: Produkt; ← DANE W DÓŁ
@Output() dodano = new EventEmitter<Produkt>();
│
│ użytkownik klika "Dodaj do koszyka"
▼
this.dodano.emit(this.produkt); ← ZDARZENIE W GÓRĘ
│
▼
Komponent RODZICA: naDodanie(produkt: Produkt) { ... }
SERWIS (produkt.service.ts) Komponent A Komponent B
@Injectable({providedIn: "root"}) │ │
export class ProduktService { │ constructor( │ constructor(
pobierzWszystkie() { ... } ◄──────────┘ private ps: │ private ps:
} ProduktService) │ ProduktService)
(JEDNA instancja współdzielona przez WSZYSTKIE komponenty, które o nią poproszą)
Połącz z praktyką
Przykład z życia
Lista produktów w sklepie internetowym renderuje wiele komponentów "Karta produktu" (potomne), a kliknięcie przycisku "Dodaj do koszyka" na KONKRETNEJ karcie musi zaktualizować licznik koszyka widoczny w NAGŁÓWKU strony (zupełnie inny komponent) — to właśnie zadanie dla @Output() (karta informuje rodzica) połączonego z serwisem koszyka (współdzielonym między listą a nagłówkiem).