Dataniedziela, 9 sierpnia 2026 Czas07:50:47
← Aplikacje webowe

Lekcja 9. Angular I — architektura komponentowa i szablony

Trudny / egzaminacyjny

Po co się tego uczymy?

Czas na główny framework tego działu. Angular to sposób budowania interfejsu z małych, niezależnych "klocków" (komponentów), które można łączyć w większe całości — dokładnie ta sama idea, co komponenty Blazor, do których być może zajrzysz w dodatkowym bloku na końcu tego działu, tylko tutaj realizowana w TypeScript. Ta lekcja pokazuje anatomię pojedynczego komponentu i podstawową składnię szablonów HTML rozszerzoną przez Angulara.

Teoria

Komponent — podstawowa jednostka budowy w Angularze. Każdy fragment interfejsu (przycisk, formularz, cała strona) to komponent — trójka powiązanych plików: .ts (logika w TypeScript), .html (szablon — widok), .css (style TYLKO dla tego komponentu, nie wyciekające do reszty aplikacji). Widziałeś to już pobieżnie w lekcji 4 — teraz czas zrozumieć to dokładnie.

Dekorator @Component. Nad klasą komponentu w TypeScript umieszcza się dekorator (specjalną adnotację) opisującą metadane komponentu:

Właściwość dekoratora Znaczenie
selector nazwa znacznika HTML, pod którym ten komponent można wstawić w INNYM komponencie, np. <app-produkt></app-produkt>
standalone: true nowoczesny styl komponentu — nie wymaga rejestracji w dodatkowym pliku "modułu" (starszy styl Angulara)
imports: [...] lista innych komponentów/modułów, z których TEN komponent korzysta w swoim szablonie
templateUrl / template ścieżka do pliku .html (albo szablon wpisany bezpośrednio jako string)
styleUrl / styleUrls ścieżka do pliku(ów) .css z lokalnymi stylami

Interpolacja — wypisywanie wartości w szablonie. Podwójne klamry {{ wyrazenie }} wypisują wartość pola/właściwości z klasy TypeScript bezpośrednio w HTML — bezpośredni odpowiednik @Model.Właściwość z Razor albo @zmienna z Blazor: <h1>{{ tytul }}</h1>.

Dyrektywy strukturalne — @if i @for. Nowoczesny Angular (od wersji 17+) wprowadza własną, wbudowaną składnię sterowania renderowaniem, bardzo podobną do Razor/Blazor:

Składnia Działanie
@if (warunek) { ... } @else { ... } warunkowe renderowanie fragmentu szablonu
@for (item of lista; track item.id) { ... } pętla renderująca fragment DLA KAŻDEGO elementu tablicy — track wskazuje unikalny identyfikator, pomagający Angularowi efektywnie odświeżać listę
@switch (wartosc) { @case (x) { ... } @default { ... } } wybór jednego z kilku wariantów renderowania

(Starsze projekty Angular używają zamiast tego dyrektyw *ngIf i *ngFor — działają identycznie, ale wymagają importu CommonModule; nowa składnia @if/@for jest wbudowana i zalecana w nowych projektach).

Komponenty potomne — komunikacja między "klockami". Duży komponent (np. lista produktów) może w swoim szablonie użyć MNIEJSZYCH komponentów (np. pojedynczej karty produktu) — <app-karta-produktu></app-karta-produktu> — dokładnie ta sama idea, co komponenty potomne w Blazor, tylko w składni Angulara; pełne przekazywanie danych między nimi (@Input/@Output) poznasz w kolejnej lekcji.

Schemat

Plik .ts (logika, TypeScript)          Plik .html (szablon, widok)
@Component({                            <h1>{{ tytul }}</h1>
  selector: "app-produkty",                    │
  templateUrl: "./produkty.html"          @if (produkty.length === 0) {
})                                          <p>Brak produktów.</p>
export class ProduktyComponent {        } @else {
  tytul = "Lista produktów";               <ul>
  produkty = [...];                       @for (p of produkty; track p.id) {
}                                              <li>{{ p.nazwa }} - {{ p.cena }} zł</li>
        │                                   }
        │  łączy dane z widokiem           </ul>
        └──────────────────────────────► }

Komponent nadrzędny:                    Komponent potomny:
<app-lista-produktow>                   <app-karta-produktu>
   używa wewnątrz szablonu:                (osobny plik .ts + .html + .css)
   <app-karta-produktu></app-karta-produktu>

Przykład z życia

Strona sklepu internetowego zbudowana w Angularze to dziesiątki małych komponentów: nagłówek, stopka, karta produktu (powtórzona wielokrotnie na liście), formularz wyszukiwania — każdy z nich to osobny, niezależny "klocek", który można rozwijać i testować oddzielnie, a potem złożyć w całość, dokładnie tak jak klocki LEGO składają się w większą konstrukcję.

produkty.component.html

<!-- produkty.component.html -->
<div class="container mt-4">
  <h1>{{ tytul }}</h1>

  @if (produkty.length === ) {
    <p>Brak produktów do wyświetlenia.</p>
  } @else {
    <ul class="list-group">
      @for (produkt of produkty; track produkt.id) {
        <li class="list-group-item d-flex justify-content-between">
          <span>{{ produkt.nazwa }}</span>
          <strong>{{ produkt.cena.toFixed(2) }} zł</strong>
        </li>
      }
    </ul>
    <p class="mt-2">Łącznie produktów: {{ produkty.length }}</p>
  }
</div>

produkty.component.ts

// produkty.component.ts
import { Component } from '@angular/core';

interface Produkt {
  id: number;
  nazwa: string;
  cena: number;
}

@Component({
  selector: 'app-produkty',
  standalone: true,
  templateUrl: './produkty.component.html',
  styleUrl: './produkty.component.css'
})
export class ProduktyComponent {
  tytul = 'Lista produktów';

  produkty: Produkt[] = [
    { id: 1, nazwa: 'Klawiatura', cena: 89.99 },
    { id: 2, nazwa: 'Monitor', cena: 799.00 },
    { id: 3, nazwa: 'Myszka', cena: 45.50 }
  ];
}

Komentarz i wyjaśnienie kodu

interface Produkt opisuje kształt pojedynczego produktu (poznane w lekcji 6), a produkty: Produkt[] deklaruje TABLICĘ obiektów tego typu — TypeScript teraz "wie", że każdy element tej tablicy ma właściwości id/nazwa/cena, i podpowie/zabezpieczy przed literówką przy odwołaniu się do nieistniejącej właściwości w szablonie.

@for (produkt of produkty; track produkt.id) — składnia track produkt.id jest OBOWIĄZKOWA w nowym Angularze (w przeciwieństwie do starszego *ngFor, gdzie trackBy było opcjonalne) — informuje mechanizm renderowania, jak rozpoznać, KTÓRY konkretnie element listy się zmienił, żeby zaktualizować tylko jego fragment DOM, a nie przerysowywać całej listy od nowa przy każdej zmianie.

Wyrażenie produkt.cena.toFixed(2) wewnątrz interpolacji {{ }} pokazuje, że wewnątrz podwójnych klamer można wywoływać zwykłe metody TypeScript/JavaScript (tu: formatowanie liczby do dwóch miejsc po przecinku) — nie tylko wypisywać "gołe" wartości pól.

Ćwiczenie samodzielne

Utwórz nowy komponent standalone (ng generate component produkty --standalone) z tablicą co najmniej 3 obiektów (dowolnych, np. filmów albo książek) i wyświetl je w pętli @for, z warunkowym komunikatem @if pokazywanym, gdy tablica jest pusta.

Zadania do pracy własnej

  1. Dodaj do listy produktów licznik pokazujący, ile produktów kosztuje więcej niż 100 zł, wyliczony bezpośrednio w interpolacji (podpowiedź: metoda tablicy filter, np. {{ produkty.filter(p => p.cena > 100).length }}).

  2. Rozbuduj przykład o @switch nadający każdemu produktowi etykietę cenową ("Tani" dla ceny poniżej 50 zł, "Średni" dla 50-200 zł, "Drogi" powyżej 200 zł), wyświetlaną obok nazwy produktu.

  3. Podziel przykład na DWA komponenty: ProduktyComponent (rodzic, trzyma tablicę danych) i nowy KartaProduktuComponent (potomny, na razie BEZ przekazywania danych z rodzica — po prostu wyświetlający wpisany na sztywno przykładowy produkt) — użyj komponentu potomnego wewnątrz szablonu rodzica przez jego selektor, i sprawdź w przeglądarce, że oba renderują się poprawnie razem.

Typowe błędy

Zapomnienie o track w pętli @for — nowoczesny Angular WYMAGA tej klauzuli (w przeciwieństwie do starszego *ngFor), bez niej kod się nie skompiluje.

Próba wywołania metod modyfikujących dane (np. dodających element do tablicy) BEZPOŚREDNIO w interpolacji {{ }} — interpolacja powinna być używana WYŁĄCZNIE do odczytu/wyświetlania danych; logika zmieniająca stan aplikacji powinna być w metodach klasy, wywoływanych przez zdarzenia (pełne omówienie w kolejnej lekcji).

Mylenie selector komponentu ze zwykłą nazwą klasy TypeScriptselector: "app-produkty" to nazwa ZNACZNIKA HTML używanego do wstawienia komponentu w innym miejscu, zupełnie inna sprawa niż nazwa klasy ProduktyComponent.

Nawiązanie do egzaminu zawodowego

To realizacja INF.04.7.2 i INF.04.7.3 (framework Angular, budowa interfejsu z komponentów i kontrolek) — architektura komponentowa poznana tutaj jest sercem WSZYSTKICH kolejnych projektów w tym dziale. Kolejna lekcja rozszerza komponenty o przekazywanie danych (data binding), zdarzenia i serwisy do komunikacji z backendem.