Angular od podstaw do praktyki

Kompletny, praktyczny przewodnik dla studentów, którzy chcą nauczyć się budować nowoczesne aplikacje webowe w Angularze.

⚡ Angular 17+ 📘 TypeScript 🧩 Standalone Components 🔁 Signals & RxJS

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.

1

Zrozum koncepcję

Najpierw teoria z czytelnym wyjaśnieniem „dlaczego".

2

Zobacz kod

Przykłady gotowe do skopiowania i uruchomienia.

3

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.

CechaCo to daje
TypeScriptTypowanie statyczne — mniej błędów, lepsze podpowiedzi w edytorze
KomponentyAplikacja zbudowana z wielokrotnie używalnych „klocków"
Dependency InjectionŁatwe zarządzanie zależnościami i testowanie
Angular CLIGenerowanie kodu, serwer dev, budowanie produkcyjne
RxJS / SignalsReaktywne 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

terminal
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.

terminal
npm install -g @angular/cli

# sprawdź wersję
ng version

3Utwórz nowy projekt

terminal
ng new moja-pierwsza-aplikacja

# CLI zada kilka pytań:
# - Which stylesheet format? -> CSS
# - Enable SSR? -> No (na początek)

4Uruchom serwer deweloperski

terminal
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ę.

struktura
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
PlikRola
main.tsStartuje aplikację — „włącznik" całości
index.htmlJedyna strona HTML; tu osadzany jest cały Angular
app.component.*Główny komponent — korzeń drzewa komponentów
app.config.tsGlobalna 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

typescript
let imie: string = 'Anna';
let wiek: number = 21;
let aktywny: boolean = true;
let oceny: number[] = [4, 5, 3];

Interfejsy — opis kształtu danych

typescript
interface Student {
  id: number;
  imie: string;
  oceny: number[];
  email?: string;   // ? = pole opcjonalne
}

const s: Student = {
  id: 1,
  imie: 'Jan',
  oceny: [5, 4]
};

Klasy

typescript
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

hello.component.ts
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 (lub templateUrl wskazujący osobny plik)
  • styles — style działające tylko w obrębie komponentu

Generowanie komponentu przez CLI

terminal
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.

karta.component.ts
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
}
użycie u rodzica
<app-karta
  [tytul]="'Mleko'"
  (usun)="usunProdukt()"></app-karta>
Ćwiczenie

Stwórz komponent app-powitanie, który przyjmuje przez @Input pole imie i wyświetla „Witaj, [imie]!".

Pokaż rozwiązanie
powitanie.component.ts
@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ładniaNazwaKierunek
{{ wartosc }}Interpolacjaklasa → widok
[wlasciwosc]Property bindingklasa → widok
(zdarzenie)Event bindingwidok → klasa
[(ngModel)]Two-way bindingw obie strony
szablon
<!-- 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.

control flow
<!-- 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.

Ćwiczenie

Wyświetl listę imion ['Ala', 'Ola', 'Ela'] w elemencie <ul> za pomocą @for.

Pokaż rozwiązanie
html
<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.

html
<!-- 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

highlight.directive.ts
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 |.

html
{{ '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

skroc.pipe.ts
@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.

licznik.service.ts
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)

komponent
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

app.routes.ts
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

html
<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

product.component.ts
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

login.component.ts
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);
    }
  }
}
login.component.html
<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

app.config.ts
import { provideHttpClient } from '@angular/common/http';

export const appConfig = {
  providers: [provideHttpClient()]
};

Serwis pobierający dane

uzytkownicy.service.ts
@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

komponent
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

typescript
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ń.

html
<ul>
  @for (user of uzytkownicy$ | async; track user.id) {
    <li>{{ user.imie }}</li>
  }
</ul>

// w klasie: uzytkownicy$ = this.serwis.pobierzWszystkich();
OperatorDziałanie
mapPrzekształca każdą wartość
filterPrzepuszcza tylko pasujące wartości
switchMapPrzełącza na nowy strumień (np. zapytania)
debounceTimeCzeka 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.

licznik.component.ts
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
  }
}
FunkcjaZastosowanie
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.

todo.component.ts
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));
  }
}
todo.component.html
<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>
todo.component.css
.zrobione span {
  text-decoration: line-through;
  color: #94a3b8;
}
Wyzwanie

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

ElementPlikKlasa
Komponentuser-list.component.tsUserListComponent
Serwisauth.service.tsAuthService
Pipetruncate.pipe.tsTruncatePipe
Dyrektywahighlight.directive.tsHighlightDirective

Słownik pojęć

PojęcieWyjaśnienie
ComponentKlocek aplikacji łączący logikę, szablon i style
TemplateSzablon HTML opisujący widok komponentu
Data bindingPołączenie danych klasy z widokiem
DirectiveInstrukcja zmieniająca element HTML
PipePrzekształcenie danych w szablonie
ServiceKlasa z logiką współdzieloną między komponentami
DIDependency Injection — automatyczne dostarczanie zależności
ObservableStrumień wartości w czasie (RxJS)
SignalReaktywna wartość powiadamiająca o zmianach
StandaloneKomponent niezależny od NgModules
CLINarzędzie wiersza poleceń Angulara (ng)

Dalsza nauka

Gratulacje! Znasz już fundamenty Angulara. Oto kierunki dalszego rozwoju:

  • Oficjalna dokumentacjaangular.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! 🚀