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?

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 strony, komunikację między komponentem rodzica i potomnym, oraz serwisy — miejsce, gdzie żyje logika i dane współdzielone przez wiele komponentów.

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:

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