Komponenty – budulec aplikacji
Komponent to podstawowy budulec każdej aplikacji Angular. Nauczysz się tworzyć własne komponenty, rozumieć ideę standalone i składać z komponentów większe aplikacje – jak z klocków LEGO.
Czym jest komponent?
Każda aplikacja Angular składa się z komponentów. Komponent to samodzielny fragment interfejsu – ma własny wygląd, własne dane i własną logikę. Można go użyć wielokrotnie w różnych miejscach aplikacji.
Pomyśl o stronie sklepu internetowego. Nie piszesz jej jako jeden gigantyczny plik HTML. Rozbijasz ją na mniejsze, niezależne kawałki:
Każdy z tych komponentów jest niezależny – możesz go modyfikować
bez dotykania pozostałych. Komponent app-karta-produktu
możesz użyć 100 razy na stronie z listą produktów i 100 razy
na stronie wyników wyszukiwania.
W WPF dzielisz interfejs na UserControls – każdy
ma swój plik .xaml i .xaml.cs.
Komponent Angular działa identycznie: plik .html
to widok (XAML), plik .ts to logika (code-behind),
plik .css to style.
Idea jest dokładnie ta sama – modularność i wielokrotne użycie.
Standalone – nowe podejście do komponentów
W Angular 21 każdy komponent tworzony przez ng generate
jest domyślnie standalone (samodzielny).
To ważna zmiana względem starszych wersji Angulara – warto zrozumieć
na czym polega.
Stary model – NgModules
Przed Angular 17 każdy komponent musiał być zarejestrowany
w module (NgModule). Moduł był
jak „lista obecności” – dopiero wpisanie komponentu do modułu
pozwalało go użyć w aplikacji. Przy dziesiątkach komponentów
zarządzanie modułami było uciążliwe.
// ❌ Stary sposób – każdy komponent trzeba zarejestrować w module @NgModule({ declarations: [ AppComponent, NawigacjaComponent, // ← ręczna rejestracja KartaProduktComponent, // ← ręczna rejestracja KoszykComponent, // ← ręczna rejestracja ], imports: [BrowserModule], bootstrap: [AppComponent] }) export class AppModule {} // Dodatkowy plik, dodatkowa konfiguracja – niepotrzebna złożoność
Nowy model – Standalone (Angular 17+)
Standalone component sam deklaruje czego potrzebuje – importuje tylko te zależności, z których korzysta. Nie potrzebuje żadnego modułu. Każdy komponent jest w pełni autonomiczny.
// ✅ Nowy sposób – komponent standalone @Component({ selector: 'app-karta-produktu', standalone: true, // ← to sprawia, że jest standalone imports: [NgOptimizedImage], // ← importuje tylko to, czego potrzebuje templateUrl: './karta-produktu.component.html', styleUrl: './karta-produktu.component.css' }) export class KartaProduktComponent { // logika komponentu }
| NgModule (stary) | Standalone (Angular 21) | |
|---|---|---|
Plik app.module.ts |
Wymagany | Nie istnieje |
| Rejestracja komponentu | W declarations[] modułu |
Brak – komponent sam się deklaruje |
| Importowanie zależności | W module | Bezpośrednio w @Component({ imports: [] }) |
| Złożoność | Wysoka przy wielu komponentach | Niska – każdy komponent niezależny |
Tak – NgModules nadal działają i są wspierane.
Angular jest wstecznie kompatybilny. Jednak w każdym nowym projekcie
tworzonym przez ng new domyślnie używa się standalone.
My uczymy się tylko standalone – to obecny standard i zalecane podejście.
Struktura komponentu
Każdy komponent Angular składa się z kilku plików. Razem tworzą kompletną, samodzielną jednostkę interfejsu.
Pliki komponentu
src/app/karta-produktu/ ├── karta-produktu.component.ts ← logika i dane (TypeScript) ├── karta-produktu.component.html ← szablon HTML (wygląd) ├── karta-produktu.component.css ← style CSS (tylko dla tego komponentu) └── karta-produktu.component.spec.ts ← testy (na razie pomijamy)
Dekorator @Component – szczegóły
import { Component } from '@angular/core'; @Component({ // Jak używamy komponentu w HTML – nazwa znacznika selector: 'app-karta-produktu', // Komponent standalone – nie potrzebuje NgModule standalone: true, // Lista importowanych zależności (inne komponenty, dyrektywy) imports: [], // Plik HTML z szablonem templateUrl: './karta-produktu.component.html', // Plik CSS ze stylami (lokalne – nie wychodzą poza komponent) styleUrl: './karta-produktu.component.css' }) export class KartaProduktComponent { // Zmienne i metody komponentu nazwa = 'Laptop Dell XPS'; cena = 3499; zdjecie = 'images/laptop.jpg'; }
| Właściwość | Do czego służy? | Przykład |
|---|---|---|
selector |
Nazwa znacznika HTML, przez który używamy komponentu | 'app-karta-produktu' |
standalone |
Czy komponent jest samodzielny (nie potrzebuje NgModule) | true |
imports |
Lista zależności używanych w tym komponencie | [NgOptimizedImage, KoszykComponent] |
templateUrl |
Ścieżka do pliku HTML z szablonem | './nazwa.component.html' |
styleUrl |
Ścieżka do pliku CSS ze stylami komponentu | './nazwa.component.css' |
Zamiast templateUrl możesz użyć template
i wpisać HTML bezpośrednio w dekoratorze jako string. Przy krótkich
szablonach (1–3 linii) to wygodne. Przy dłuższych zdecydowanie lepiej
mieć osobny plik .html – edytor lepiej go obsługuje
i kod jest czytelniejszy.
Tworzenie komponentu – ng generate
Komponentu nigdy nie tworzymy ręcznie – od tworzenia plików
jest Angular CLI. Komenda ng generate component
(w skrócie ng g c) tworzy wszystkie potrzebne pliki
i wypełnia je poprawnym kodem startowym.
# Pełna komenda ng generate component karta-produktu # Skrócona forma (dokładnie to samo) ng g c karta-produktu # Komponent w podfolderze (dobra praktyka dla większych aplikacji) ng g c komponenty/karta-produktu # Po wykonaniu komendy CLI informuje co stworzył: # CREATE src/app/karta-produktu/karta-produktu.component.ts # CREATE src/app/karta-produktu/karta-produktu.component.html # CREATE src/app/karta-produktu/karta-produktu.component.css # CREATE src/app/karta-produktu/karta-produktu.component.spec.ts
Nazwy komponentów piszemy z myślnikami, małymi literami:
karta-produktu, lista-uczniow,
panel-logowania. Angular CLI automatycznie zamieni
to na właściwą nazwę klasy TypeScript (KartaProduktComponent)
i selector HTML (app-karta-produktu).
Przedrostek app- w selectorze jest dodawany automatycznie
i chroni przed kolizjami z wbudowanymi znacznikami HTML.
Wygenerowany plik .ts wygląda tak:
import { Component } from '@angular/core'; @Component({ selector: 'app-karta-produktu', standalone: true, imports: [], templateUrl: './karta-produktu.component.html', styleUrl: './karta-produktu.component.css' }) export class KartaProduktComponent { // tutaj dodajesz zmienne i metody }
Używanie komponentu w innym komponencie
Żeby użyć komponentu w szablonie innego komponentu, musisz wykonać dwa kroki: zaimportować klasę komponentu i użyć jego selektora w HTML.
import { Component } from '@angular/core'; // Krok 1 – importujemy klasę naszego komponentu import { KartaProduktComponent } from './karta-produktu/karta-produktu.component'; @Component({ selector: 'app-root', standalone: true, // Krok 2 – dodajemy go do tablicy imports imports: [KartaProduktComponent], templateUrl: './app.component.html', styleUrl: './app.component.css' }) export class AppComponent { }
<div class="container mt-4"> <h1>Sklep internetowy</h1> <div class="row"> <!-- Krok 3 – używamy selektora jako znacznika HTML --> <!-- Angular zastąpi ten znacznik szablonem KartaProduktComponent --> <div class="col-md-4"> <app-karta-produktu></app-karta-produktu> </div> <div class="col-md-4"> <app-karta-produktu></app-karta-produktu> </div> <div class="col-md-4"> <app-karta-produktu></app-karta-produktu> </div> </div> </div>
Zapamiętaj ten schemat na zawsze:
1) wygeneruj komponent przez ng g c,
2) zaimportuj jego klasę w komponencie rodzica i dodaj do imports[],
3) użyj selektora jako znacznika HTML w szablonie rodzica.
Ten wzorzec powtarza się przy każdym komponencie.
Przykład – rozbicie aplikacji na komponenty
Zobaczmy jak wygląda realna aplikacja rozbita na komponenty. Weźmiemy prostą stronę szkoły i podzielimy ją na logiczne części.
Przed rozbiciem – jeden monolityczny plik
<!-- Wszystko w jednym pliku – trudne do utrzymania --> <nav class="navbar navbar-dark bg-dark"> <a class="navbar-brand">ZSMI Lębork</a> <!-- ... 30 linii nawigacji --> </nav> <section class="hero"> <!-- ... 20 linii bannera --> </section> <main> <!-- ... 100 linii listy klas --> </main> <footer> <!-- ... 25 linii stopki --> </footer>
Po rozbici – czytelna struktura komponentów
ng g c nawigacja ng g c baner ng g c lista-klas ng g c stopka
import { Component } from '@angular/core'; import { NawigacjaComponent } from './nawigacja/nawigacja.component'; import { BanerComponent } from './baner/baner.component'; import { ListaKlasComponent } from './lista-klas/lista-klas.component'; import { StopkaComponent } from './stopka/stopka.component'; @Component({ selector: 'app-root', standalone: true, imports: [ NawigacjaComponent, BanerComponent, ListaKlasComponent, StopkaComponent ], templateUrl: './app.component.html' }) export class AppComponent { }
<!-- Główny szablon jest teraz krótki i czytelny --> <app-nawigacja></app-nawigacja> <main class="container"> <app-baner></app-baner> <app-lista-klas></app-lista-klas> </main> <app-stopka></app-stopka>
Dobra zasada: gdy fragment kodu HTML ma więcej niż 30–40 linii lub gdy ten sam fragment pojawia się w kilku miejscach – czas na nowy komponent. Nie ma sensu rozbijać na mikrokomponenty każdego przycisku, ale np. karta produktu, wiersz tabeli czy panel użytkownika to naturalne kandydaty.
Style lokalne vs globalne
Każdy komponent ma własny plik .css. Style zdefiniowane
w tym pliku działają tylko wewnątrz tego komponentu
i nie wpływają na resztę aplikacji. Nazywamy to enkapsulacją stylów.
🌍 Style globalne – styles.css
/* src/styles.css */ /* Działa w CAŁEJ aplikacji */ body { font-family: 'Segoe UI', sans-serif; } .tekst-szkolny { color: #003366; }
Tutaj: importowanie Bootstrap, resetowanie stylów, czcionki, zmienne CSS
🔒 Style lokalne – komponent.css
/* karta-produktu.component.css */ /* Działa TYLKO w KartaProduktComponent */ .karta { border-radius: 12px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); } h5 { /* Ten h5 dotyczy TYLKO kart produktu */ color: #1a73e8; }
Tutaj: style specyficzne dla tego komponentu – nie ma ryzyka nadpisania innych elementów
Angular automatycznie dodaje do każdego elementu komponentu
unikalny atrybut (np. _ngcontent-abc-c123) i opakowuje
nim style CSS. Dzięki temu selektor h5 w pliku
karta-produktu.component.css dotyczy tylko
elementów h5 wewnątrz KartaProduktComponent –
nie wpływa na inne h5 na stronie.
Częste błędy
❌ Błąd 1 – Brak importu komponentu w rodzicu
❌ Komponent nieznany – błąd w konsoli
// app.component.ts – brak importu! @Component({ standalone: true, imports: [], // ← KartaProduktComponent nie jest tutaj! ... }) <!-- app.component.html --> <!-- Angular nie wie co to jest: --> <app-karta-produktu></app-karta-produktu>
✅ Zaimportowany w tablicy imports
import { KartaProduktComponent } from './...'; @Component({ standalone: true, imports: [KartaProduktComponent], ... })
❌ Błąd 2 – Literówka w selectorze
❌ Selector i znacznik muszą się zgadzać
// .ts – selector zdefiniowany jako: selector: 'app-karta-produktu' <!-- .html – literówka w nazwie --> <app-karta-produku></app-karta-produku> <!-- Nic się nie wyświetli! -->
✅ Identyczna nazwa w obu miejscach
selector: 'app-karta-produktu' <!-- Identycznie jak selector: --> <app-karta-produktu></app-karta-produktu>
❌ Błąd 3 – Tworzenie komponentu ręcznie zamiast przez CLI
❌ Ręczne tworzenie – brakujące elementy
// Ręcznie tworzysz plik i zapominasz o: // - pliku .spec.ts // - poprawnej nazwie klasy (CamelCase) // - słowie standalone: true // - poprawnym selectorze z app-
✅ Zawsze przez ng g c
# CLI zrobi wszystko poprawnie automatycznie ng g c nazwa-komponentu
Zadanie – strona klasy szkolnej
Zbuduj prostą aplikację „Strona klasy” rozbijając ją na co najmniej trzy komponenty.
Stwórz następujące komponenty przez ng g c:
naglowek– wyświetla nazwę szkoły, klasę i rok szkolnykarta-ucznia– karta z imieniem, nazwiskiem i średnią ocen jednego uczniastopka– wyświetla wychowawcę i kontakt
W AppComponent:
- Zaimportuj wszystkie trzy komponenty do tablicy
imports[] - W szablonie użyj
<app-naglowek>, trzech kart uczniów (<app-karta-ucznia>) w układzie Bootstrap grid i<app-stopka>
W komponencie karta-ucznia:
- Zdefiniuj zmienne:
imie,nazwisko,srednia,zdjecie - Wyświetl dane przez interpolację, zdjęcie przez
[src] - Dodaj odznakę koloru zależną od średniej (
@if/@else if)
Na razie wszystkie trzy karty uczniów pokazują te same dane
(twarde w klasie). W następnym materiale poznamy
komunikację między komponentami przez
@Input – dzięki temu każda karta będzie mogła
otrzymać inne dane od rodzica.