Angular od podstaw do praktyki
Kompletny, praktyczny przewodnik dla studentów, którzy chcą nauczyć się budować nowoczesne aplikacje webowe w Angularze.
Wprowadzenie
Ten podręcznik prowadzi Cię krok po kroku — od zera do pierwszej działającej aplikacji. Nie musisz znać Angulara; wystarczą podstawy HTML, CSS i JavaScriptu.
Każdy rozdział łączy teorię z przykładami kodu, które możesz uruchomić u siebie. Na końcu rozdziałów znajdziesz ćwiczenia oraz rozwijane rozwiązania, dzięki którym sprawdzisz wiedzę w praktyce.
Zrozum koncepcję
Najpierw teoria z czytelnym wyjaśnieniem „dlaczego".
Zobacz kod
Przykłady gotowe do skopiowania i uruchomienia.
Przećwicz
Zadania utrwalające po każdym rozdziale.
Jak korzystać z podręcznika? Czytaj po kolei i samodzielnie przepisuj przykłady. Przepisywanie kodu (zamiast kopiowania) wielokrotnie przyspiesza naukę programowania.
Czym jest Angular?
Angular to framework do budowy aplikacji webowych typu Single Page Application (SPA), rozwijany przez Google. Pozwala tworzyć duże, skalowalne aplikacje w sposób uporządkowany — z gotowymi rozwiązaniami dla routingu, formularzy, komunikacji z serwerem i testowania.
Framework czy biblioteka?
W odróżnieniu od biblioteki (np. React), Angular jest pełnym frameworkiem: narzuca strukturę, dostarcza komplet narzędzi „w pudełku" i ma własne CLI. To mniej decyzji do podjęcia na starcie i większa spójność w zespole.
| Cecha | Co to daje |
|---|---|
| TypeScript | Typowanie statyczne — mniej błędów, lepsze podpowiedzi w edytorze |
| Komponenty | Aplikacja zbudowana z wielokrotnie używalnych „klocków" |
| Dependency Injection | Łatwe zarządzanie zależnościami i testowanie |
| Angular CLI | Generowanie kodu, serwer dev, budowanie produkcyjne |
| RxJS / Signals | Reaktywne zarządzanie danymi i stanem |
Angular vs AngularJS. „AngularJS" (wersja 1.x) to przestarzała technologia. Współczesny „Angular" (wersja 2+) to zupełnie inny, napisany od nowa framework. W tym podręczniku mówimy wyłącznie o nowoczesnym Angularze.
Instalacja i pierwszy projekt
Do pracy z Angularem potrzebujesz Node.js (zalecana wersja LTS) oraz menedżera pakietów npm, który instaluje się razem z Node.js.
1Sprawdź środowisko
node --version # np. v20.11.0
npm --version # np. 10.2.4
2Zainstaluj Angular CLI
Angular CLI to narzędzie wiersza poleceń do tworzenia projektów i generowania kodu.
npm install -g @angular/cli
# sprawdź wersję
ng version
3Utwórz nowy projekt
ng new moja-pierwsza-aplikacja
# CLI zada kilka pytań:
# - Which stylesheet format? -> CSS
# - Enable SSR? -> No (na początek)
4Uruchom serwer deweloperski
cd moja-pierwsza-aplikacja
ng serve --open
Aplikacja otworzy się w przeglądarce pod adresem http://localhost:4200. Serwer automatycznie odświeża stronę po każdej zmianie w kodzie (hot reload).
Wskazówka. Jako edytor polecamy Visual Studio Code z rozszerzeniem Angular Language Service — daje autouzupełnianie i wykrywa błędy w szablonach.
Struktura projektu
Po wygenerowaniu projektu zobaczysz wiele plików. Na początku interesuje Cię głównie folder src/app — to tam tworzysz swoją aplikację.
moja-pierwsza-aplikacja/
├─ src/
│ ├─ app/
│ │ ├─ app.component.ts # logika głównego komponentu
│ │ ├─ app.component.html # szablon (widok)
│ │ ├─ app.component.css # style komponentu
│ │ └─ app.config.ts # konfiguracja aplikacji
│ ├─ index.html # główny plik HTML
│ ├─ main.ts # punkt startowy aplikacji
│ └─ styles.css # style globalne
├─ angular.json # konfiguracja Angulara
├─ package.json # zależności projektu
└─ tsconfig.json # konfiguracja TypeScript
| Plik | Rola |
|---|---|
main.ts | Startuje aplikację — „włącznik" całości |
index.html | Jedyna strona HTML; tu osadzany jest cały Angular |
app.component.* | Główny komponent — korzeń drzewa komponentów |
app.config.ts | Globalna konfiguracja (routing, HTTP itp.) |
TypeScript w pigułce
Angular jest napisany w TypeScript — nadzbiorze JavaScriptu, który dodaje typy. Typy pomagają wychwytywać błędy zanim uruchomisz aplikację.
Typy podstawowe
let imie: string = 'Anna';
let wiek: number = 21;
let aktywny: boolean = true;
let oceny: number[] = [4, 5, 3];
Interfejsy — opis kształtu danych
interface Student {
id: number;
imie: string;
oceny: number[];
email?: string; // ? = pole opcjonalne
}
const s: Student = {
id: 1,
imie: 'Jan',
oceny: [5, 4]
};
Klasy
class Konto {
private saldo = 0;
wplac(kwota: number): void {
this.saldo += kwota;
}
stan(): number {
return this.saldo;
}
}
Klasy i dekoratory (np. @Component) to fundament Angulara. Jeśli rozumiesz powyższe przykłady — jesteś gotów na komponenty.
Komponenty
Komponent to podstawowy klocek aplikacji Angular. Łączy trzy elementy: logikę (klasa TypeScript), widok (szablon HTML) oraz style (CSS). Cała aplikacja to drzewo komponentów.
Anatomia komponentu
import { Component } from '@angular/core';
@Component({
selector: 'app-hello', // nazwa znacznika HTML
standalone: true,
template: `<h1>Cześć, {{ imie }}!</h1>`,
styles: [`h1 { color: #dd0031; }`]
})
export class HelloComponent {
imie = 'Studencie'; // dane dostępne w szablonie
}
Przyjrzyjmy się dekoratorowi @Component:
selector— pod jaką nazwą używasz komponentu w HTML, np.<app-hello></app-hello>standalone: true— nowoczesny komponent niezależny od modułów (zalecane)template— szablon HTML (lubtemplateUrlwskazujący osobny plik)styles— style działające tylko w obrębie komponentu
Generowanie komponentu przez CLI
ng generate component produkty
# skrót:
ng g c produkty
Komunikacja: @Input i @Output
Komponenty przekazują dane między sobą. @Input przyjmuje dane od rodzica, a @Output wysyła zdarzenia do rodzica.
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-karta',
standalone: true,
template: `
<div class="karta">
<h3>{{ tytul }}</h3>
<button (click)="usun.emit()">Usuń</button>
</div>
`
})
export class KartaComponent {
@Input() tytul = ''; // dane OD rodzica
@Output() usun = new EventEmitter<void>(); // zdarzenie DO rodzica
}
<app-karta
[tytul]="'Mleko'"
(usun)="usunProdukt()"></app-karta>
Stwórz komponent app-powitanie, który przyjmuje przez @Input pole imie i wyświetla „Witaj, [imie]!".
Pokaż rozwiązanie
@Component({
selector: 'app-powitanie',
standalone: true,
template: `<p>Witaj, {{ imie }}!</p>`
})
export class PowitanieComponent {
@Input() imie = '';
}
Szablony i data binding
Data binding to mechanizm łączący dane z klasy komponentu z widokiem. To serce „reaktywności" Angulara — zmieniasz dane, a widok aktualizuje się sam.
Cztery rodzaje wiązań
| Składnia | Nazwa | Kierunek |
|---|---|---|
{{ wartosc }} | Interpolacja | klasa → widok |
[wlasciwosc] | Property binding | klasa → widok |
(zdarzenie) | Event binding | widok → klasa |
[(ngModel)] | Two-way binding | w obie strony |
<!-- Interpolacja -->
<h1>{{ tytul }}</h1>
<!-- Property binding -->
<img [src]="urlObrazka" [alt]="opis">
<!-- Event binding -->
<button (click)="zwieksz()">Kliknij</button>
<!-- Two-way binding -->
<input [(ngModel)]="nazwa">
<p>Wpisałeś: {{ nazwa }}</p>
Sterowanie przepływem (nowa składnia @)
Angular 17 wprowadził nową, czytelną składnię warunków i pętli wprost w szablonie.
<!-- Warunek -->
@if (zalogowany) {
<p>Witaj z powrotem!</p>
} @else {
<p>Zaloguj się.</p>
}
<!-- Pętla -->
<ul>
@for (produkt of produkty; track produkt.id) {
<li>{{ produkt.nazwa }}</li>
} @empty {
<li>Brak produktów</li>
}
</ul>
W starszych materiałach spotkasz *ngIf i *ngFor. Działają nadal, ale w nowych projektach zalecane są @if i @for — są szybsze i czytelniejsze.
Wyświetl listę imion ['Ala', 'Ola', 'Ela'] w elemencie <ul> za pomocą @for.
Pokaż rozwiązanie
<ul>
@for (imie of imiona; track imie) {
<li>{{ imie }}</li>
}
</ul>
// w klasie: imiona = ['Ala', 'Ola', 'Ela'];
Dyrektywy
Dyrektywy to instrukcje, które dodają zachowanie lub zmieniają wygląd elementów HTML. Wyróżniamy dwa główne typy.
Dyrektywy atrybutu
Zmieniają wygląd lub zachowanie istniejącego elementu — np. dynamiczne klasy i style.
<!-- ngClass: warunkowe klasy CSS -->
<div [ngClass]="{ aktywny: czyAktywny, blad: czyBlad }">...</div>
<!-- ngStyle: warunkowe style -->
<p [ngStyle]="{ color: kolor, 'font-size.px': rozmiar }">Tekst</p>
<!-- skrót dla jednej klasy -->
<div [class.zaznaczony]="czyZaznaczony">...</div>
Własna dyrektywa
import { Directive, ElementRef, HostListener } from '@angular/core';
@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onEnter() {
this.el.nativeElement.style.background = '#fff1f3';
}
@HostListener('mouseleave') onLeave() {
this.el.nativeElement.style.background = '';
}
}
Użycie: <p appHighlight>Najedź na mnie</p>
Pipes (potoki)
Pipe przekształca dane bezpośrednio w szablonie — np. formatuje datę, walutę czy zmienia wielkość liter. Używasz symbolu |.
{{ 'angular' | uppercase }} // ANGULAR
{{ cena | currency:'PLN' }} // 49,99 zł
{{ dzisiaj | date:'dd.MM.yyyy' }} // 26.06.2026
{{ 0.25 | percent }} // 25%
{{ obiekt | json }} // podgląd danych
Własny pipe
@Pipe({ name: 'skroc', standalone: true })
export class SkrocPipe implements PipeTransform {
transform(tekst: string, limit = 20): string {
return tekst.length > limit
? tekst.slice(0, limit) + '…'
: tekst;
}
}
// użycie: {{ dlugiTekst | skroc:30 }}
Serwisy i Dependency Injection
Serwis to klasa do logiki niezwiązanej z konkretnym widokiem — np. pobieranie danych, obliczenia, zarządzanie stanem. Komponenty powinny być „chude", a cała logika trafia do serwisów.
Dependency Injection (DI) to mechanizm, dzięki któremu Angular sam dostarcza komponentom potrzebne serwisy. Nie tworzysz ich ręcznie — po prostu o nie prosisz.
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' }) // dostępny w całej aplikacji
export class LicznikService {
private wartosc = 0;
zwieksz() { this.wartosc++; }
pobierz() { return this.wartosc; }
}
Wstrzykiwanie serwisu (funkcja inject)
import { Component, inject } from '@angular/core';
import { LicznikService } from './licznik.service';
@Component({ /* ... */ })
export class LicznikComponent {
private licznik = inject(LicznikService);
dodaj() { this.licznik.zwieksz(); }
}
Dzięki providedIn: 'root' istnieje jedna wspólna instancja serwisu w całej aplikacji (singleton). To idealne miejsce na współdzielenie danych między komponentami.
Routing — nawigacja między widokami
Routing pozwala wyświetlać różne komponenty w zależności od adresu URL — bez przeładowania strony. To podstawa aplikacji wielowidokowych.
Definicja tras
import { Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'o-nas', component: AboutComponent },
{ path: 'produkt/:id', component: ProductComponent },
{ path: '**', component: NotFoundComponent } // 404
];
Wyświetlanie i linki
<nav>
<a routerLink="/">Start</a>
<a routerLink="/o-nas" routerLinkActive="aktywny">O nas</a>
</nav>
<!-- tutaj renderuje się komponent dla danej trasy -->
<router-outlet></router-outlet>
Odczyt parametru z URL
const route = inject(ActivatedRoute);
const id = route.snapshot.paramMap.get('id');
Formularze
Angular oferuje dwa podejścia do formularzy. Dla prostych przypadków — Template-driven, dla złożonych — Reactive Forms (zalecane w większych aplikacjach).
Reactive Forms
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-login',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './login.component.html'
})
export class LoginComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
haslo: ['', [Validators.required, Validators.minLength(6)]]
});
wyslij() {
if (this.form.valid) {
console.log(this.form.value);
}
}
}
<form [formGroup]="form" (ngSubmit)="wyslij()">
<input formControlName="email" placeholder="E-mail">
@if (form.controls.email.invalid && form.controls.email.touched) {
<small>Podaj poprawny e-mail</small>
}
<input type="password" formControlName="haslo">
<button [disabled]="form.invalid">Zaloguj</button>
</form>
Walidatory wbudowane: required, email, minLength, maxLength, pattern. Możesz też pisać własne.
HTTP i komunikacja z API
Większość aplikacji pobiera dane z serwera. Angular dostarcza HttpClient do wykonywania zapytań HTTP.
Konfiguracja
import { provideHttpClient } from '@angular/common/http';
export const appConfig = {
providers: [provideHttpClient()]
};
Serwis pobierający dane
@Injectable({ providedIn: 'root' })
export class UzytkownicyService {
private http = inject(HttpClient);
private url = 'https://api.example.com/users';
pobierzWszystkich(): Observable<User[]> {
return this.http.get<User[]>(this.url);
}
dodaj(user: User): Observable<User> {
return this.http.post<User>(this.url, user);
}
}
Użycie w komponencie
export class ListaComponent implements OnInit {
private serwis = inject(UzytkownicyService);
uzytkownicy: User[] = [];
ngOnInit() {
this.serwis.pobierzWszystkich().subscribe(dane => {
this.uzytkownicy = dane;
});
}
}
Zapytania HTTP zwracają Observable — nic się nie wykona, dopóki nie wywołasz .subscribe() (lub nie użyjesz async pipe w szablonie).
RxJS i Observables
RxJS to biblioteka do programowania reaktywnego. Observable to „strumień" wartości w czasie — np. kliknięcia, odpowiedzi serwera czy zdarzenia. Angular używa go wszędzie tam, gdzie dane pojawiają się asynchronicznie.
Operatory — przekształcanie strumienia
import { map, filter } from 'rxjs';
this.serwis.pobierzWszystkich().pipe(
filter(users => users.length > 0),
map(users => users.map(u => u.imie))
).subscribe(imiona => console.log(imiona));
Async pipe — automatyczna subskrypcja
Najlepsza praktyka: zamiast ręcznie wywoływać subscribe(), użyj async pipe. Angular sam subskrybuje i odsubskrybuje strumień.
<ul>
@for (user of uzytkownicy$ | async; track user.id) {
<li>{{ user.imie }}</li>
}
</ul>
// w klasie: uzytkownicy$ = this.serwis.pobierzWszystkich();
| Operator | Działanie |
|---|---|
map | Przekształca każdą wartość |
filter | Przepuszcza tylko pasujące wartości |
switchMap | Przełącza na nowy strumień (np. zapytania) |
debounceTime | Czeka po ostatniej zmianie (np. wyszukiwarka) |
Signals — nowoczesna reaktywność
Signals (od Angular 16) to nowy, prostszy sposób zarządzania stanem reaktywnym. Signal przechowuje wartość i automatycznie powiadamia o jej zmianie — bez subskrypcji.
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-licznik',
standalone: true,
template: `
<p>Wartość: {{ licznik() }}</p>
<p>Podwojona: {{ podwojona() }}</p>
<button (click)="zwieksz()">+1</button>
`
})
export class LicznikComponent {
licznik = signal(0); // stan
podwojona = computed(() => this.licznik() * 2); // wartość pochodna
zwieksz() {
this.licznik.update(v => v + 1); // zmiana wartości
}
}
| Funkcja | Zastosowanie |
|---|---|
signal(x) | Tworzy reaktywny stan |
sig() | Odczyt wartości (w nawiasach!) |
sig.set(x) | Ustawia nową wartość |
sig.update(fn) | Aktualizuje na podstawie poprzedniej |
computed(fn) | Wartość wyliczana automatycznie |
effect(fn) | Efekt uboczny przy zmianie |
Signals i RxJS się uzupełniają. Signals świetnie sprawdzają się dla stanu lokalnego komponentu, RxJS — dla złożonych strumieni asynchronicznych. To kierunek, w którym rozwija się Angular.
Projekt: lista zadań (Todo)
Połączmy wiedzę w działającą mini-aplikację — klasyczną listę zadań z dodawaniem, oznaczaniem jako zrobione i usuwaniem.
import { Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
interface Zadanie {
id: number;
tytul: string;
zrobione: boolean;
}
@Component({
selector: 'app-todo',
standalone: true,
imports: [FormsModule],
templateUrl: './todo.component.html',
styleUrl: './todo.component.css'
})
export class TodoComponent {
zadania = signal<Zadanie[]>([]);
nowyTytul = '';
dodaj() {
const tytul = this.nowyTytul.trim();
if (!tytul) return;
this.zadania.update(lista => [
...lista,
{ id: Date.now(), tytul, zrobione: false }
]);
this.nowyTytul = '';
}
przelacz(id: number) {
this.zadania.update(lista =>
lista.map(z => z.id === id ? { ...z, zrobione: !z.zrobione } : z)
);
}
usun(id: number) {
this.zadania.update(lista => lista.filter(z => z.id !== id));
}
}
<h2>Moje zadania</h2>
<form (ngSubmit)="dodaj()">
<input [(ngModel)]="nowyTytul" name="t" placeholder="Co masz do zrobienia?">
<button>Dodaj</button>
</form>
<ul>
@for (z of zadania(); track z.id) {
<li [class.zrobione]="z.zrobione">
<input type="checkbox" [checked]="z.zrobione" (change)="przelacz(z.id)">
<span>{{ z.tytul }}</span>
<button (click)="usun(z.id)">✕</button>
</li>
} @empty {
<li>Brak zadań — dodaj pierwsze! 🎉</li>
}
</ul>
.zrobione span {
text-decoration: line-through;
color: #94a3b8;
}
Rozbuduj projekt: dodaj licznik nieukończonych zadań oraz przycisk „Usuń ukończone". Spróbuj użyć computed() do policzenia pozostałych zadań.
Dobre praktyki
🧩 Małe komponenty
Jeden komponent = jedna odpowiedzialność. Łatwiej je testować i ponownie wykorzystać.
🧠 Logika w serwisach
Komponenty zajmują się widokiem, serwisy — danymi i logiką biznesową.
🔒 Typuj wszystko
Korzystaj z interfejsów zamiast any. Typy to Twój sojusznik.
♻️ async pipe
Pozwól Angularowi zarządzać subskrypcjami zamiast ręcznego subscribe.
📁 Spójna struktura
Grupuj pliki według funkcji (feature), nie według typu.
🚀 Lazy loading
Ładuj duże części aplikacji dopiero gdy są potrzebne.
Konwencje nazewnictwa
| Element | Plik | Klasa |
|---|---|---|
| Komponent | user-list.component.ts | UserListComponent |
| Serwis | auth.service.ts | AuthService |
| Pipe | truncate.pipe.ts | TruncatePipe |
| Dyrektywa | highlight.directive.ts | HighlightDirective |
Słownik pojęć
| Pojęcie | Wyjaśnienie |
|---|---|
| Component | Klocek aplikacji łączący logikę, szablon i style |
| Template | Szablon HTML opisujący widok komponentu |
| Data binding | Połączenie danych klasy z widokiem |
| Directive | Instrukcja zmieniająca element HTML |
| Pipe | Przekształcenie danych w szablonie |
| Service | Klasa z logiką współdzieloną między komponentami |
| DI | Dependency Injection — automatyczne dostarczanie zależności |
| Observable | Strumień wartości w czasie (RxJS) |
| Signal | Reaktywna wartość powiadamiająca o zmianach |
| Standalone | Komponent niezależny od NgModules |
| CLI | Narzędzie wiersza poleceń Angulara (ng) |
Dalsza nauka
Gratulacje! Znasz już fundamenty Angulara. Oto kierunki dalszego rozwoju:
- Oficjalna dokumentacja — angular.dev (interaktywne tutoriale)
- Testowanie — Jasmine, Karma, testy komponentów i serwisów
- State management — NgRx lub Signals Store dla dużych aplikacji
- Angular Material — gotowa biblioteka komponentów UI
- SSR / SSG — renderowanie po stronie serwera (Angular Universal)
- PWA — aplikacje progresywne działające offline
Najlepsza rada. Buduj projekty. Po przeczytaniu tego podręcznika wybierz pomysł (lista filmów, pogoda, notatnik) i zaimplementuj go od zera. Praktyka uczy najszybciej.
Powodzenia w nauce Angulara! 🚀