Własny komponent
Jeden wielki plik app.html szybko robi się nieczytelny. Podzielisz ekran na części, z których każda ma własną nazwę i własne pliki — i użyjesz tej samej części kilka razy.
Czego się dziś nauczysz
- Wygenerujesz komponent poleceniem
ng generate componenti wymienisz pliki, które powstały. - Użyjesz komponentu w szablonie innego komponentu.
- Wyjaśnisz, po co klasa komponentu pojawia się dwa razy: w
importi w tablicyimports. - Rozpoznasz błąd NG8001 i ostrzeżenie NG8113 i powiesz, co je wywołuje.
- Wstawisz ten sam komponent kilka razy i powiesz, dlaczego wszystkie wyglądają tak samo.
- Sprawdzisz, że style komponentu nie wychodzą poza jego szablon.
- Uruchomisz u siebie przykład z kursu, wstawiając jego komponent
demodoapp.html.
Przygotowanie: lekcje 01–03 — masz projekt z Bootstrapem i wiesz, czym jest selektor. Przewidywany czas: 45 min z zadaniami.
Po co dzielić ekran na części
Strona klubu ma pokazywać dziesięć wydarzeń. Możesz dziesięć razy skopiować ten sam kawałek HTML-a karty — i przy pierwszej poprawce zmieniać go w dziesięciu miejscach.
Komponent to taki kawałek opisany raz, z własną nazwą. Używasz go tyle razy, ile potrzeba, a poprawiasz w jednym pliku. Przy okazji nazwa mówi, co to jest: <app-karta /> czyta się lepiej niż <div class="card p-3">.
Znasz już jeden komponent — App z lekcji 02. Każdy nowy wygląda tak samo: klasa, szablon, dekorator @Component z selektorem.
Trzy kroki, zawsze te same
| Krok | Co robisz | Gdzie |
|---|---|---|
| 1. Wygeneruj | ng generate component karta | terminal w folderze projektu |
| 2. Zaimportuj | import { Karta } from './karta/karta'; i Karta w tablicy imports | plik .ts komponentu, który ma użyć karty |
| 3. Wstaw znacznik | <app-karta /> | szablon tego samego komponentu |
Krok 2 jest podwójny i to nie przypadek: import na górze pliku mówi TypeScriptowi, skąd wziąć klasę; wpis w tablicy imports mówi Angularowi, że wolno jej użyć w tym szablonie.
Znacznik krótki i długi
<app-karta /> i <app-karta></app-karta> znaczą w Angularze to samo. Krótsza forma działa tylko dla komponentów — zwykłego <div /> tak nie zapiszesz. W kursie używamy krótszej.
NG8001 — najczęstszy błąd tej lekcji
’app-karta’ is not a known element pojawia się, gdy pominiesz krok 2. Komunikat brzmi groźnie, a oznacza po prostu: „nie powiedziałeś mi, że wolno mi tego użyć”. Odwrotna sytuacja — klasa w imports, a znacznika brak w szablonie — daje tylko ostrzeżenie NG8113, znane z lekcji 01.
Wygeneruj komponent jednym poleceniem
CLI utworzy folder z kompletem plików. Nie twórz ich ręcznie — łatwo o literówkę.
ng generate component karta
CREATE src/app/karta/karta.css (0 bytes)
CREATE src/app/karta/karta.spec.ts (525 bytes)
CREATE src/app/karta/karta.ts (181 bytes)
CREATE src/app/karta/karta.html (20 bytes)
import { Component } from '@angular/core';
@Component({
imports: [],
selector: 'app-karta',
styleUrl: './karta.css',
templateUrl: './karta.html',
})
export class Karta {}
Skrót ng g c karta robi to samo. CLI dopisuje do selektora przedrostek app-, a klasie nadaje nazwę wielką literą. Plik karta.spec.ts to test jednostkowy — w tym kursie go nie używamy, ale nie przeszkadza. Kolejność ustawień w dekoratorze nie ma znaczenia.
Zatrzymaj się
Dlaczego selektory komponentów mają myślnik w nazwie? Podpowiedź: nazwy znaczników bez myślnika (card, header) są zarezerwowane dla HTML-a.
Użyj komponentu w innym komponencie
Od tej lekcji każdy przykład ma komponent Demo — to rodzic, w którym pokazujemy nowy mechanizm. Tutaj rodzic wstawia kartę.
import { Component } from '@angular/core';
@Component({
selector: 'app-karta',
imports: [],
templateUrl: './karta.html'
})
export class Karta {}
<div class="card p-3 mb-2" style="width: 20rem">
<h5>Turniej retro</h5>
<p class="mb-0">Piątek, sala 12.</p>
</div>
import { Component } from '@angular/core';
import { Karta } from './karta';
@Component({
selector: 'app-demo',
imports: [Karta],
templateUrl: './demo.html'
})
export class Demo {}
<div class="p-3">
<h4>Nasze wydarzenia</h4>
<app-karta />
</div>
Nasze wydarzenia
┌────────────────────────┐
│ Turniej retro │
│ Piątek, sala 12. │
└────────────────────────┘
Karta i rodzic leżą w tym samym folderze, dlatego ścieżka importu to './karta'. Komponent nie ma pliku .css, więc w dekoratorze nie ma styleUrl — to dozwolone.
Jak uruchomić przykład z kursu u siebie
- Skopiuj folder przykładu (tu:
nowa-04-1z wszystkimi plikami) dosrc/appswojego projektu. - W
src/app/app.tsdopiszimport { Demo } from './nowa-04-1/demo';i wpiszDemodo tablicyimports. - W
src/app/app.htmlzostaw tylko<app-demo />.
Przy następnym przykładzie zmieniasz tylko nazwę folderu w imporcie. To są dokładnie trzy kroki z tej lekcji — rodzicem Demo jest App.
Zatrzymaj się
Usuń Karta z tablicy imports (zostaw import na górze) i zapisz. Jaki kod błędu podał terminal? Potem zrób odwrotnie: przywróć wpis, ale usuń <app-karta /> z szablonu. Co się zmieniło — błąd czy ostrzeżenie?
Ten sam komponent trzy razy
Tu widać sens dzielenia ekranu na części: jeden opis, wiele wystąpień. Karta trafia do siatki z lekcji 03.
<div class="card p-3">
<h5>Wydarzenie</h5>
<p class="mb-0">Szczegóły wkrótce.</p>
</div>
<div class="container mt-3">
<div class="row g-3">
<div class="col-12 col-md-4"><app-karta /></div>
<div class="col-12 col-md-4"><app-karta /></div>
<div class="col-12 col-md-4"><app-karta /></div>
</div>
</div>
Pliki karta.ts i demo.ts są takie same jak w przykładzie 2 — zmieniają się tylko szablony.
[Wydarzenie ] [Wydarzenie ] [Wydarzenie ]
[Szczegóły wkrótce.] [Szczegóły wkrótce.] [Szczegóły wkrótce.]
Trzy identyczne karty. Zmień tekst w karta.html raz — zmieni się we wszystkich trzech naraz.
Zatrzymaj się
Wszystkie karty są identyczne, bo skąd miałyby wiedzieć, że mają się różnić? Zapamiętaj to pytanie — odpowiedzią jest input() z lekcji 29, gdzie rodzic powie każdej karcie, co ma pokazać.
Styl komponentu nie wycieka na zewnątrz
Każdy komponent może mieć własny CSS, który obowiązuje wyłącznie w jego szablonie. Krótki styl można wpisać wprost w dekoratorze, zamiast w osobnym pliku .css.
import { Component } from '@angular/core';
@Component({
selector: 'app-karta',
imports: [],
templateUrl: './karta.html',
styles: `h5 { color: #d63384; }`
})
export class Karta {}
<div class="card p-3 mb-2">
<h5>Nagłówek w karcie</h5>
</div>
<div class="p-3">
<h5>Nagłówek poza kartą</h5>
<app-karta />
</div>
Plik demo.ts jest taki sam jak w przykładzie 2.
Nagłówek poza kartą (czarny)
┌──────────────────────┐
│ Nagłówek w karcie │ (różowy)
└──────────────────────┘
Oba nagłówki to h5, ale różowy jest tylko ten w karcie. Reguła h5 { color: … } działa wyłącznie w szablonie komponentu, który ją zdefiniował. Styl dla całej aplikacji wpisujesz w src/styles.css.
Zatrzymaj się
To dlatego Bootstrapa podłączyliśmy w angular.json, a nie w komponencie. Przenieś regułę h5 { color: #d63384; } z dekoratora do src/styles.css. Ile nagłówków jest teraz różowych?
Zestawienie elementów
| Element | Znaczenie | Uwagi |
|---|---|---|
ng generate component nazwa | nowy komponent w folderze nazwa | Skrót: ng g c nazwa. Tworzy 4 pliki. |
import { Karta } from './karta/karta'; | skąd TypeScript ma wziąć klasę | Ścieżka względna, bez .ts. |
imports: [Karta] | czego wolno użyć w szablonie | Brak wpisu → NG8001. |
<app-karta /> | wstawienie komponentu | To samo co <app-karta></app-karta>. |
selector: 'app-karta' | nazwa znacznika | Małe litery i myślnik. |
styleUrl: './karta.css' | plik stylów komponentu | Można pominąć, gdy brak stylów. |
styles: `...` | krótki styl wpisany w dekoratorze | Zamiast osobnego pliku. |
src/styles.css | style całej aplikacji | Działa we wszystkich komponentach. |
| NG8001 | nieznany element | Brak w imports albo literówka w znaczniku. |
| NG8113 | nieużywany import | Ostrzeżenie, aplikacja działa. |
Zanim utkniesz
| Co widzisz | Przyczyna i naprawa |
|---|---|
| NG8001: 'app-karta’ is not a known element | Brak klasy w tablicy imports dekoratora rodzica. |
To samo NG8001 przy <app-Karta /> | Literówka w znaczniku — wielkość liter ma znaczenie. Selektor jest małymi literami. |
| TS2304: Cannot find name 'Karta’ | Wpisałeś Karta do imports, ale brak import { Karta } … na górze pliku. |
| Cannot find module ’./karta’ | Zła ścieżka w imporcie. Karta w podfolderze to './karta/karta', w tym samym folderze — './karta'. |
| CLI utworzyło pliki w złym miejscu | Terminal stał w innym folderze. Uruchamiaj ng g c z głównego folderu projektu. |
Komponent użyty w Demo, a dodany do imports w App | Importuje ten komponent, w którego szablonie znacznik naprawdę stoi. |
| Styl z komponentu nie działa w innym | Tak ma być. Wspólne style idą do src/styles.css. |
Zadania
Wygeneruj komponent stopka z nazwą szkoły i rokiem szkolnym w szarym, wyśrodkowanym tekście. Wstaw go w app.html pod dotychczasową treścią.
Kryterium sukcesu: stopka jest widoczna na dole strony, terminal nie pokazuje błędów ani ostrzeżeń.
Podpowiedź
Trzy kroki: generowanie, import w app.ts (dwa miejsca), znacznik w app.html. Klasy: text-center text-muted.
Dodaj komponent naglowek z nazwą klubu, ostylowany klasami Bootstrapa bg-dark text-white p-3. Umieść go nad całą resztą strony.
Kryterium sukcesu: ciemny pasek zajmuje całą szerokość i stoi nad pozostałą treścią.
Podpowiedź
Klasy Bootstrapa wpisujesz w szablonie komponentu (naglowek.html), nie w jego pliku .css.
Potrzebujesz trzech kart z różnymi wydarzeniami: „Turniej retro”, „Noc robotów”, „Warsztat druku 3D”. Zrób to jedynym sposobem, który dziś znasz: trzy osobne komponenty karta-turniej, karta-roboty, karta-druk, ustawione w siatce z lekcji 03. Potem zmień w każdej karcie kolor ramki na border-success i policz, ile plików musiałeś edytować. Zapisz w komentarzu na końcu app.html, co by się stało przy 20 wydarzeniach.
Kryterium sukcesu: trzy różne karty w rzędzie, a w komentarzu liczba edytowanych plików i Twój wniosek.
Podpowiedź
Selektor karta-turniej po wygenerowaniu to app-karta-turniej, a klasa — KartaTurniej. Rozwiązanie tego problemu poznasz w lekcji 29.
W pliku stopka.css (z zadania 1) dopisz regułę dla p: kursywa i rozmiar czcionki 0.9rem. Sprawdź, czy zmieniły się akapity poza stopką. Następnie przenieś tę samą regułę do src/styles.css i znów sprawdź. Zapisz w zeszycie oba wyniki i jednozdaniową zasadę: kiedy styl wpisać do komponentu, a kiedy do styles.css.
Kryterium sukcesu: w pierwszej wersji kursywa jest tylko w stopce, w drugiej — we wszystkich akapitach strony.
Podpowiedź
p { font-style: italic; font-size: 0.9rem; }
Zrób komponent lista-wydarzen, który w swoim szablonie zawiera siatkę z trzema komponentami karta. W app.html użyj tylko <app-lista-wydarzen />. Zapisz w komentarzu, w którym pliku musiała pojawić się klasa Karta w tablicy imports i dlaczego akurat tam, a nie w app.ts.
Kryterium sukcesu: ekran pokazuje trzy karty, a app.html zawiera jeden znacznik listy; terminal bez błędów i ostrzeżeń.
Podpowiedź
Importuje ten komponent, w którego szablonie znacznik naprawdę występuje. App importuje tylko ListaWydarzen.
Złóż całą stronę klubu z komponentów z zadań 1, 2 i 5: naglowek, lista-wydarzen (z kartami) i stopka. app.html ma zawierać tylko te trzy znaczniki, owinięte w jeden div. Narysuj w zeszycie drzewo komponentów: App na górze, pod nim jego dzieci, pod listą — karty. Przy każdym komponencie zapisz, co ma w tablicy imports.
Kryterium sukcesu: strona wygląda jak kompletna wizytówka klubu, terminal nie pokazuje ostrzeżeń, a drzewo w zeszycie zgadza się z tablicami imports w kodzie.
Podpowiedź
Każdy komponent w drzewie importuje tylko swoje bezpośrednie dzieci. Karta nie importuje niczego.
Co trzeba zapamiętać
ng generate component nazwa(ng g c) tworzy folder z klasą, szablonem, stylami i testem.- Użycie komponentu to trzy kroki: wygeneruj, zaimportuj (import + tablica
imports), wstaw znacznik. importna górze pliku jest dla TypeScriptu, tablicaimports— dla Angulara.- Brak wpisu w
importsto błąd NG8001; nieużywany wpis — ostrzeżenie NG8113. - Importuje ten komponent, w którego szablonie znacznik stoi.
- Ten sam komponent można wstawić wiele razy — na razie wszystkie wystąpienia wyglądają tak samo.
- Styl komponentu działa tylko w jego szablonie; wspólne style idą do
src/styles.css. - Przykład z kursu uruchamiasz, wstawiając jego
DemodoApp.
Sprawdź się: wstawiłeś <app-karta /> i terminal pokazuje NG8001. Wymień dwie rzeczy, które sprawdzisz w pierwszej kolejności.
Dokumentacja: angular.dev — komponenty.