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.

@Component standalone ng generate selector
01

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:

app-nawigacja
menu i logo
+
app-karta-produktu
zdjęcie + cena
+
app-koszyk
lista zakupów
+
app-stopka
kontakt, linki

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.

Porównanie do C# / WPF

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.

02

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.

ts/stary-sposob-app.module.ts TS
// ❌ 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.

ts/nowy-sposob-standalone.ts TS
// ✅ 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
Czy w Angular 21 w ogóle mogę użyć NgModules?

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.

03

Struktura komponentu

Każdy komponent Angular składa się z kilku plików. Razem tworzą kompletną, samodzielną jednostkę interfejsu.

Pliki komponentu

struktura/karta-produktu DIR
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

ts/karta-produktu.component.ts TS
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'
template vs templateUrl

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.

04

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.

PowerShell/generowanie PS
# 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
Konwencja nazewnictwa komponentów

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:

ts/karta-produktu.component.ts (wygenerowany) TS
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
}
05

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.

ts/app.component.ts TS
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 { }
html/app.component.html HTML
<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>
Trzy kroki – zawsze te same

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.

06

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

html/app.component.html (monolityczny) HTML
<!-- 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

PowerShell/generowanie-komponentów PS
ng g c nawigacja
ng g c baner
ng g c lista-klas
ng g c stopka
ts/app.component.ts TS
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 { }
html/app.component.html (po rozbici) HTML
<!-- 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>
Kiedy rozbijać na komponenty?

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.

07

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

Jak działa enkapsulacja styló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.

08

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 szkolny
  • karta-ucznia – karta z imieniem, nazwiskiem i średnią ocen jednego ucznia
  • stopka – 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)
Następny temat

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.