Zbuduj aplikację webową krok po kroku
Aplikacje webowe działają w przeglądarce bez instalacji. Uczysz się tutaj praktyki: budujesz komponenty Angular, łączysz je z logiką TypeScript, tworzysz REST API w ASP.NET Core i zapisujesz dane do bazy SQLite. Ten sam schemat przyda się w prostej aplikacji i w zadaniu egzaminacyjnym.
Wybierz etap i przejdź prosto do materiału
Możesz realizować etapy po kolei albo od razu skoczyć do tematu, który chcesz przećwiczyć przed lekcją lub egzaminem.
Każda aplikacja webowa składa się z trzech warstw
Najpierw budujesz widok w przeglądarce, potem reagujesz na akcję użytkownika, a serwer przetwarza dane i odpowiada.
Widok w przeglądarce
HTML, CSS, Angular — budujesz interfejs użytkownika. Komponenty, szablony, dyrektywy i Bootstrap tworzą to, co widzi użytkownik.
TypeScript i serwisy
@Component, @Input, @Output, EventEmitter. Logika aplikacji: obsługa zdarzeń, obliczenia, walidacja formularzy.
Serwer i API
ASP.NET Core (Visual Studio 2022) lub Node.js obsługuje żądania HTTP. Angular łączy się z backendem przez HttpClient. Dane zapisujesz do SQLite przez EF Core.
Od Angular do backendu w 14 krokach
Kroki 1–10 to Angular i TypeScript (VS Code). Kroki 11–14 to backend ASP.NET Core w Visual Studio 2022. Rozwiń krok żeby zobaczyć kluczowe komendy i kod.
Co instalujesz przed startem:
1. Node.js LTS — pobierz z nodejs.org. npm jest dołączony automatycznie.
2. Angular CLI — globalnie przez npm.
3. TypeScript — Angular zarządza nim przez package.json projektu, ale dla globalnego tsc instalujesz też globalnie.
4. VS Code — do pracy z Angular i Node.js. Zainstaluj rozszerzenie Angular Language Service.
node --version # sprawdź Node.js (wymagane 18+) npm --version # sprawdź npm npm install -g @angular/cli # Angular CLI globalnie npm install -g typescript # TypeScript globalnie (opcjonalnie) ng version # sprawdź Angular CLI tsc --version # sprawdź TypeScript
Tworzysz nowy projekt:
ng new NazwaProjektu # kreator: routing tak/nie, CSS/SCSS cd NazwaProjektu ng serve --open # http://localhost:4200
W VS Code otwierasz folder projektu: File → Open Folder. Terminal wbudowany: Ctrl+`.
Bootstrap to gotowy zestaw klas CSS — przyciski, siatka i formularze bez pisania własnych styli od zera.
npm install bootstrap npm install bootstrap-icons # opcjonalnie — zestaw ikon
Otwórz angular.json i dopisz w sekcji "styles":
"styles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css", "node_modules/bootstrap-icons/font/bootstrap-icons.css", "src/styles.css" ]
Zrestartuj serwer: Ctrl+C, następnie ng serve.
Najczęściej używane klasy Bootstrap: container, row, col-md-6, btn btn-primary, form-control, table table-striped, alert alert-danger, card.
W Angular 17+ każdy komponent może być standalone — nie potrzebuje NgModule.
ng generate component components/formularz --standalone ng generate component components/wynik --standalone ng generate service services/dane # serwis
Każdy komponent: .ts (logika) · .html (widok) · .css (style lokalne).
Struktura projektu: components/ — widoki, services/ — logika i HTTP, models/ — interfejsy TypeScript.
[(ngModel)] — dwukierunkowe wiązanie danych (Template-driven forms). Angular pamięta co wpisałeś w polu.
<!-- HTML -->
<input type="number" [(ngModel)]="waga" class="form-control" placeholder="Waga (kg)">
<input type="number" [(ngModel)]="wzrost" class="form-control" placeholder="Wzrost (cm)">
<button class="btn btn-primary" (click)="oblicz()">Oblicz BMI</button>
<p *ngIf="wynik">BMI: {{ wynik | number:'1.1-1' }}</p>
// TypeScript
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
waga = 0;
wzrost = 0;
wynik: number | null = null;
oblicz() {
this.wynik = this.waga / Math.pow(this.wzrost / 100, 2);
}
Zaimportuj FormsModule i CommonModule w tablicy imports komponentu standalone.
@Input() — komponent przyjmuje dane od rodzica.@Output() + EventEmitter — wysyła dane do rodzica.
// komponent-formularz.ts — wysyła dane w górę
@Output() wyslij = new EventEmitter<any>();
submit() {
this.wyslij.emit({ bmi: 22.5, kategoria: 'Norma' });
}
// app.component.ts — odbiera i przekazuje dalej
zapiszWynik(dane: any) { this.wynik = dane; }
// komponent-wynik.ts — wyświetla dane z góry
@Input() bmi: number = 0;
@Input() kategoria: string = '';
<!-- app.component.html --> <app-formularz (wyslij)="zapiszWynik($event)"></app-formularz> <app-wynik [bmi]="wynik?.bmi" [kategoria]="wynik?.kategoria"></app-wynik>
<div *ngFor="let item of menuItems; let i = index" class="card mb-2 p-3">
<h5>{{ i + 1 }}. {{ item.nazwa }}</h5>
<p>{{ item.cena | currency:'PLN':'symbol':'1.2-2' }}</p>
<button class="btn btn-sm btn-danger" (click)="usun(item.id)">Usuń</button>
</div>
<div *ngIf="menuItems.length === 0" class="alert alert-info">
Brak elementów na liście.
</div>
Angular 17+ nowa składnia @for / @if (zamiast *ngFor/*ngIf):
@for (item of menuItems; track item.id) {
<div class="card p-3">{{ item.nazwa }}</div>
} @empty {
<p>Brak elementów.</p>
}
@if (zalogowany) {
<p>Witaj, {{ uzytkownik }}!</p>
} @else {
<a routerLink="/login">Zaloguj się</a>
}
Do szybkiego prototypowania: JSON Server uruchamia fałszywe REST API jednym poleceniem.
npm install -g json-server
# utwórz db.json:
{
"produkty": [
{ "id": 1, "nazwa": "Laptop", "cena": 3500 },
{ "id": 2, "nazwa": "Mysz", "cena": 150 }
]
}
json-server --watch db.json --port 3000
# API → http://localhost:3000/produkty
HttpClient w Angular — serwis pobierający dane:
// produkt.service.ts
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface Produkt { id: number; nazwa: string; cena: number; }
@Injectable({ providedIn: 'root' })
export class ProduktService {
private apiUrl = 'http://localhost:3000/produkty';
constructor(private http: HttpClient) {}
getAll(): Observable<Produkt[]> {
return this.http.get<Produkt[]>(this.apiUrl);
}
dodaj(p: Omit<Produkt, 'id'>): Observable<Produkt> {
return this.http.post<Produkt>(this.apiUrl, p);
}
usun(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}
// w komponencie
ngOnInit(): void {
this.produktService.getAll().subscribe(dane => {
this.produkty = dane;
});
}
// w app.config.ts lub provideHttpClient() w bootstrapApplication
ng serve # tryb deweloperski → localhost:4200 ng build # produkcyjny build → folder dist/ ng build --base-href /sciezka/ # jeśli app nie jest w root
Gotowy folder dist/ wgrywasz na serwer lub integrujesz z projektem ASP.NET Core (kroki 11–14).
Reactive Forms to bardziej zaawansowany sposób pracy z formularzami niż ngModel. Dajesz pełną kontrolę nad walidacją i stanem każdego pola.
// TypeScript — importy i konfiguracja
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
formularz!: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.formularz = this.fb.group({
email: ['', [Validators.required, Validators.email]],
haslo: ['', [Validators.required, Validators.minLength(8)]],
wiek: [null, [Validators.required, Validators.min(18), Validators.max(120)]],
zgoda: [false, Validators.requiredTrue]
});
}
wyslij() {
if (this.formularz.valid) {
console.log(this.formularz.value);
// wyślij dane do API
}
}
// pomocnicze getter — skraca dostęp w HTML
get email() { return this.formularz.get('email'); }
get haslo() { return this.formularz.get('haslo'); }
<!-- HTML -->
<form [formGroup]="formularz" (ngSubmit)="wyslij()">
<div class="mb-3">
<label class="form-label">E-mail</label>
<input type="email" class="form-control" formControlName="email"
[class.is-invalid]="email?.invalid && email?.touched">
<div class="invalid-feedback">
<span *ngIf="email?.errors?.['required']">Pole jest wymagane.</span>
<span *ngIf="email?.errors?.['email']">Podaj poprawny adres e-mail.</span>
</div>
</div>
<div class="mb-3">
<label class="form-label">Hasło</label>
<input type="password" class="form-control" formControlName="haslo"
[class.is-invalid]="haslo?.invalid && haslo?.touched">
<div class="invalid-feedback">
<span *ngIf="haslo?.errors?.['minlength']">Minimum 8 znaków.</span>
</div>
</div>
<button type="submit" class="btn btn-primary"
[disabled]="formularz.invalid">
Zarejestruj
</button>
</form>
Nie zapomnij zaimportować ReactiveFormsModule w tablicy imports komponentu standalone.
Router Angular podmienia komponenty w miejscu <router-outlet> bez przeładowania strony. URL zmienia się — przeglądarka nie odświeża.
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { ProduktyComponent } from './produkty/produkty.component';
import { ProduktDetailComponent } from './produkt-detail/produkt-detail.component';
import { LoginComponent } from './login/login.component';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'produkty', component: ProduktyComponent },
{ path: 'produkty/:id', component: ProduktDetailComponent },
{ path: 'login', component: LoginComponent },
{ path: '**', redirectTo: '' } // catch-all → home
];
<!-- nawigacja w app.component.html -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" routerLink="/">Sklep</a>
<div class="navbar-nav">
<a class="nav-link" routerLink="/"
routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">
Home
</a>
<a class="nav-link" routerLink="/produkty"
routerLinkActive="active">
Produkty
</a>
</div>
</div>
</nav>
<main class="container mt-4">
<router-outlet></router-outlet> <!-- tu podmieniane są komponenty -->
</main>
// odczyt parametru :id w komponencie
import { ActivatedRoute } from '@angular/router';
constructor(private route: ActivatedRoute) {}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
this.zaladujProdukt(Number(id));
}
// programowa nawigacja
import { Router } from '@angular/router';
constructor(private router: Router) {}
przejdz() {
this.router.navigate(['/produkty', 5]);
}
Visual Studio 2022 ma gotowy szablon który łączy ASP.NET Core (backend C#) z Angular (frontend) w jednym projekcie.
Jak utworzyć projekt:
1. VS 2022 → Utwórz nowy projekt
2. Wyszukaj „ASP.NET Core with Angular”
3. Nazwa projektu, lokalizacja
4. Framework: .NET 8.0 (lub nowszy), Authentication: None
5. Kliknij Utwórz
Struktura projektu:
MojProjekt/ ├── Controllers/ ← kontrolery API (C#) │ └── WeatherForecastController.cs ├── ClientApp/ ← projekt Angular │ ├── src/app/ │ ├── angular.json │ └── package.json ├── Program.cs ← konfiguracja aplikacji └── MojProjekt.csproj
Uruchamianie: naciśnij F5 w VS 2022. Backend startuje na https://localhost:7xxx, Angular na localhost:4200. Proxy w pliku proxy.conf.js przekierowuje żądania /api/* do backendu automatycznie.
CORS — przy osobnym projekcie Angular musisz zezwolić na żądania z localhost:4200:
// Program.cs
builder.Services.AddCors(options => {
options.AddPolicy("Angular", policy => {
policy.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
app.UseCors("Angular");
Kontroler w ASP.NET Core obsługuje żądania HTTP z Angular. Każda metoda to jeden endpoint.
// Controllers/ProduktyController.cs
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class ProduktyController : ControllerBase
{
// statyczna lista do ćwiczeń — potem zastąpisz bazą danych
private static List<Produkt> _lista = new()
{
new Produkt { Id = 1, Nazwa = "Laptop", Cena = 3500m },
new Produkt { Id = 2, Nazwa = "Mysz", Cena = 150m }
};
// GET api/produkty
[HttpGet]
public ActionResult<IEnumerable<Produkt>> Get()
=> Ok(_lista);
// GET api/produkty/1
[HttpGet("{id}")]
public ActionResult<Produkt> Get(int id)
{
var p = _lista.FirstOrDefault(x => x.Id == id);
return p is null ? NotFound() : Ok(p);
}
// POST api/produkty
[HttpPost]
public ActionResult<Produkt> Post(Produkt produkt)
{
produkt.Id = _lista.Max(x => x.Id) + 1;
_lista.Add(produkt);
return CreatedAtAction(nameof(Get), new { id = produkt.Id }, produkt);
}
// PUT api/produkty/1
[HttpPut("{id}")]
public IActionResult Put(int id, Produkt produkt)
{
var istn = _lista.FirstOrDefault(x => x.Id == id);
if (istn is null) return NotFound();
istn.Nazwa = produkt.Nazwa;
istn.Cena = produkt.Cena;
return NoContent();
}
// DELETE api/produkty/1
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
var p = _lista.FirstOrDefault(x => x.Id == id);
if (p is null) return NotFound();
_lista.Remove(p);
return NoContent();
}
}
// Models/Produkt.cs
public class Produkt
{
public int Id { get; set; }
public string Nazwa { get; set; } = string.Empty;
public decimal Cena { get; set; }
}
Testowanie API — użyj Swaggera (wbudowany w szablon VS 2022) lub Postmana. W przeglądarce: https://localhost:7xxx/swagger.
W Angular zmieniasz tylko URL serwisu:
private apiUrl = 'https://localhost:7xxx/api/produkty'; // reszta kodu HttpClient bez zmian
Entity Framework Core (EF Core) to ORM — operujesz na danych przez klasy C# zamiast pisać SQL ręcznie. SQLite to lekka baza plikowa — idealna do nauki i egzaminu.
1. Zainstaluj pakiety NuGet (Tools → NuGet Package Manager lub Package Manager Console):
Install-Package Microsoft.EntityFrameworkCore.Sqlite Install-Package Microsoft.EntityFrameworkCore.Tools
2. Utwórz model i DbContext:
// Models/Produkt.cs
public class Produkt
{
public int Id { get; set; }
public string Nazwa { get; set; } = string.Empty;
public decimal Cena { get; set; }
public bool Aktywny { get; set; } = true;
}
// Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> opt) : base(opt) { }
public DbSet<Produkt> Produkty => Set<Produkt>();
}
3. Zarejestruj w Program.cs:
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlite("Data Source=app.db"));
4. Utwórz i zastosuj migrację (Package Manager Console → projekt backendu):
Add-Migration InitialCreate Update-Database # Plik app.db pojawi się w katalogu projektu
5. Zaktualizuj kontroler — zamiast statycznej listy używasz bazy:
public class ProduktyController : ControllerBase
{
private readonly AppDbContext _db;
public ProduktyController(AppDbContext db) { _db = db; }
[HttpGet]
public async Task<ActionResult<IEnumerable<Produkt>>> Get()
=> Ok(await _db.Produkty.Where(p => p.Aktywny).ToListAsync());
[HttpPost]
public async Task<ActionResult<Produkt>> Post(Produkt p)
{
_db.Produkty.Add(p);
await _db.SaveChangesAsync();
return CreatedAtAction(nameof(Get), new { id = p.Id }, p);
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var p = await _db.Produkty.FindAsync(id);
if (p is null) return NotFound();
_db.Produkty.Remove(p);
await _db.SaveChangesAsync();
return NoContent();
}
}
Sesje vs cookies — cookie przechowuje identyfikator sesji po stronie przeglądarki, a dane sesji trzyma serwer. JWT przechowuje zakodowany token po stronie klienta — serwer nie musi trzymać stanu.
Opcja A — Sesje (prostsze, dobre do nauki):
// Program.cs
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(opt => {
opt.IdleTimeout = TimeSpan.FromMinutes(30);
opt.Cookie.HttpOnly = true;
opt.Cookie.IsEssential = true;
});
app.UseSession(); // ← PRZED app.MapControllers()
// Controllers/LoginController.cs
[ApiController]
[Route("api/[controller]")]
public class LoginController : ControllerBase
{
[HttpPost]
public IActionResult Login([FromBody] LoginDto dto)
{
// w produkcji: porównaj hasz hasła, nie plaintext!
if (dto.Login == "admin" && dto.Haslo == "haslo123")
{
HttpContext.Session.SetString("user", dto.Login);
HttpContext.Session.SetString("rola", "admin");
return Ok(new { sukces = true, user = dto.Login });
}
return Unauthorized(new { blad = "Błędne dane logowania" });
}
[HttpPost("logout")]
public IActionResult Logout()
{
HttpContext.Session.Clear();
return Ok();
}
[HttpGet("whoami")]
public IActionResult WhoAmI()
{
var user = HttpContext.Session.GetString("user");
return user != null
? Ok(new { zalogowany = true, user })
: Ok(new { zalogowany = false });
}
}
public record LoginDto(string Login, string Haslo);
// Angular — auth.service.ts
login(login: string, haslo: string): Observable<any> {
return this.http.post('/api/login',
{ login, haslo },
{ withCredentials: true } // ← wysyła cookie z sesją!
);
}
sprawdzSesje(): Observable<any> {
return this.http.get('/api/login/whoami',
{ withCredentials: true }
);
}
logout(): Observable<any> {
return this.http.post('/api/login/logout', {},
{ withCredentials: true }
);
}
Opcja B — JWT (bardziej nowoczesne): serwer generuje token, Angular przechowuje go w localStorage i wysyła w nagłówku Authorization: Bearer <token>. Serwer weryfikuje podpis bez potrzeby pamiętania sesji.
// Instalacja pakietu JWT w VS 2022:
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
// Program.cs — konfiguracja JWT
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt => {
opt.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes("TwojSuperTajnyKlucz32ZnakowMinimum!")),
ValidateIssuer = false,
ValidateAudience = false
};
});
app.UseAuthentication();
app.UseAuthorization();
// Na kontrolerze — chronione endpointy:
[Authorize]
[HttpGet("chroniony")]
public IActionResult Chroniony() => Ok("Dostęp przyznany!");
Pamiętaj: nigdy nie przechowuj haseł jako tekst jawny. Używaj BCrypt.Net lub wbudowanego PasswordHasher z ASP.NET Core Identity.
Zbuduj aplikację Angular w jednym schemacie
Środowisko, komponenty, formularze, dane i uruchomienie — z gotowymi fragmentami kodu.
Projekty i ćwiczenia Angular / Web
Zacznij od prostych kalkulatorów, żeby oswoić się z Angular. Potem przejdź do projektów z API, routingiem i backendem.
Kalkulator BMI
Angular standalone, FormsModule, [(ngModel)], obliczenia w komponencie, pipe number, stylowanie Bootstrap.
Kolorowy zgadywacz
Prosta gra: generowanie pytań, obsługa kliknięć, wynik. Dyrektywy *ngFor, [ngStyle], zdarzenia (click).
MediMax — Medicos Web
Angular standalone, formularz z obliczaniem BMI, komponent podsumowania, @Input / @Output / EventEmitter.
System zamówień restauracji
Angular + Bootstrap + JSON Server jako mock backend. Komponenty menu, koszyk, nawigacja. Pobieranie danych przez HttpClient.
Routing w Angular
RouterModule, routerLink, router-outlet, parametry trasy :id, programowa nawigacja Router.navigate(). Nawigacja w SPA bez przeładowania.
Reactive Forms i walidacja
FormBuilder, FormGroup, FormControl, walidatory wbudowane (required, email, minLength), wyświetlanie błędów i blokada submit przy invalid form.
TODO App — Angular + ASP.NET Core + SQLite
Pełny stack: lista zadań w Angular, REST API w ASP.NET Core (VS 2022), baza SQLite przez EF Core. CRUD: dodaj, wyświetl, zakończ, usuń zadanie.
▸ treść w budowieAplikacja z logowaniem JWT
Rejestracja i logowanie użytkownika. Angular obsługuje token JWT, Angular Route Guards chronią strony. Backend ASP.NET Core z [Authorize] na endpointach.
Projekt egzaminacyjny — sklep / rezerwacje
Połącz wszystko: Angular front-end z routingiem i formularzami, ASP.NET Core backend, baza SQLite, sesja lub JWT. Zadanie w stylu egzaminu INF.04.
▸ treść w budowieTesty i powtórka INF.04
Teoria jest wspólna dla wszystkich typów aplikacji. Po praktyce Angular sprawdź dokumentację, UML, BHP i organizację pracy.
Algorytmy, OOP i aplikacje
Podstawowe pojęcia, rodzaje aplikacji, kontrolki, zdarzenia i programowanie obiektowe.
Uruchom test 1UML, testy i dokumentacja
Przypadki testowe, diagramy, dokumentowanie kodu i przygotowanie projektu do oddania.
Uruchom test 2BHP, Git, Scrum i bezpieczeństwo
Łatwe do przeoczenia zagadnienia z części pisemnej egzaminu.
Uruchom test 3Aplikacje webowe a egzamin INF.04
Co musisz umieć: frameworki, API, bazy danych, sesje, formularze, wdrożenie. Pełna lista umiejętności.
Otwórz materiał ›Potrafię samodzielnie wykonać pełną aplikację webową
FRONT-END · ANGULAR
- zainstalować Node.js, Angular CLI, TypeScript i Bootstrap
- stworzyć komponent standalone z szablonem HTML
- zbudować formularz z wiązaniem
[(ngModel)]i obsłużyć zdarzenia - walidować formularz przez Reactive Forms (required, email, minLength)
- wyświetlić listę danych przez
*ngFor/@for - przekazywać dane między komponentami (@Input / @Output)
- skonfigurować routing i przechodzić między widokami bez przeładowania
BACK-END · ASP.NET CORE + BAZA
- podłączyć JSON Server jako mock REST API do Angular
- pobrać i wysłać dane przez
HttpClient(withCredentials) - stworzyć projekt ASP.NET Core + Angular w Visual Studio 2022
- zbudować kontroler z metodami GET, POST, PUT, DELETE
- skonfigurować CORS dla lokalnego frontendu Angular
- zaimplementować EF Core + SQLite (model, DbContext, migracje)
- obsłużyć sesje lub JWT — logowanie i chroniące endpointy
- zbudować projekt produkcyjny (
ng build+dotnet publish)