1. Wprowadzenie – rodzina komponentów
Wyobraź sobie komponenty jako rodzinę:
- Rodzic (komponenta główna) – ma dzieci
- Dzieci (komponenty potomne) – należą do rodzica
- Rodzeństwo (komponenty na tym samym poziomie) – dzieci tego samego rodzica
Komunikacja w aplikacji filmowej:
- Rodzic: Lista filmów
- Dziecko: Karta pojedynczego filmu
- Rodzeństwo: Różne karty filmów
2. Typy komunikacji
🔽 RODZIC → DZIECKO (@Input)
- Rodzic przekazuje dane do dziecka
- Np. Lista filmów przekazuje dane o filmie do karty filmu
🔼 DZIECKO → RODZIC (@Output + EventEmitter)
- Dziecko wysyła informację do rodzica
- Np. Karta filmu informuje listę o kliknięciu „Usuń”
↔️ RODZEŃSTWO ↔ RODZEŃSTWO (Service)
- Komponenty na tym samym poziomie wymieniają dane
- Np. Karta filmu informuje inne karty o zmianie
3. Komunikacja RODZIC → DZIECKO (@Input)
Jak to działa?
- Rodzic ma dane
- Dziecko deklaruje @Input
- Rodzic przekazuje dane przez HTML
Przykład: Lista filmów → Karta filmu
Krok 1: Dziecko przyjmuje dane (@Input)
// film-card.component.ts (DZIECKO)
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-film-card',
standalone: true,
template: `
<div class="card">
<h3>{{ film.title }}</h3>
<p>Rok: {{ film.year }}</p>
<p>Ocena: {{ film.rating }}/10</p>
</div>
`,
styles: [`
.card {
border: 1px solid #ddd;
padding: 15px;
margin: 10px;
border-radius: 8px;
}
`]
})
export class FilmCardComponent {
@Input() film!: any; // ! oznacza "na pewno otrzymam te dane"
}Krok 2: Rodzic przekazuje dane
// film-list.component.ts (RODZIC)
import { Component } from '@angular/core';
import { FilmCardComponent } from './film-card.component';
@Component({
selector: 'app-film-list',
standalone: true,
imports: [FilmCardComponent],
template: `
<h2>Lista filmów</h2>
@for (film of films; track film.id) {
<app-film-card [film]="film"></app-film-card>
}
`
})
export class FilmListComponent {
films = [
{ id: 1, title: 'Avengers', year: 2019, rating: 8.4 },
{ id: 2, title: 'Batman', year: 2022, rating: 7.8 },
{ id: 3, title: 'Spider-Man', year: 2021, rating: 8.2 }
];
}Wyjaśnienie:
@Input() film!: any– dziecko mówi: „Oczekuję danych o nazwie 'film'”[film]="film"– rodzic mówi: „Przekazuję ci dane o filmie”!oznacza „na pewno otrzymam te dane od rodzica”
4. Komunikacja DZIECKO → RODZIC (@Output + EventEmitter)
Jak to działa?
- Dziecko tworzy wydarzenie (@Output + EventEmitter)
- Dziecko wysyła wydarzenie (.emit())
- Rodzic słucha wydarzenia w HTML
Przykład: Karta filmu → Lista filmów
Krok 1: Dziecko tworzy wydarzenie
// film-card.component.ts (DZIECKO)
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-film-card',
standalone: true,
template: `
<div class="card">
<h3>{{ film.title }}</h3>
<p>Rok: {{ film.year }}</p>
<p>Ocena: {{ film.rating }}/10</p>
<button (click)="likeFilm()" class="btn-like">
❤️ Polub
</button>
<button (click)="deleteFilm()" class="btn-delete">
🗑️ Usuń
</button>
</div>
`,
styles: [`
.card { border: 1px solid #ddd; padding: 15px; margin: 10px; }
button { margin: 5px; padding: 5px 10px; cursor: pointer; }
.btn-like { background: #28a745; color: white; }
.btn-delete { background: #dc3545; color: white; }
`]
})
export class FilmCardComponent {
@Input() film!: any;
// Tworzę wydarzenia, które mogę wysłać do rodzica
@Output() filmLiked = new EventEmitter<any>();
@Output() filmDeleted = new EventEmitter<number>();
likeFilm() {
// Wysyłam cały obiekt filmu do rodzica
this.filmLiked.emit(this.film);
}
deleteFilm() {
// Wysyłam tylko ID filmu do rodzica
this.filmDeleted.emit(this.film.id);
}
}Krok 2: Rodzic słucha wydarzeń
// film-list.component.ts (RODZIC)
import { Component } from '@angular/core';
import { FilmCardComponent } from './film-card.component';
@Component({
selector: 'app-film-list',
standalone: true,
imports: [FilmCardComponent],
template: `
<h2>Lista filmów</h2>
<p>Polubionych filmów: {{ likedFilms.length }}</p>
@for (film of films; track film.id) {
<app-film-card
[film]="film"
(filmLiked)="onFilmLiked($event)"
(filmDeleted)="onFilmDeleted($event)">
</app-film-card>
}
<!-- Lista polubionych filmów -->
@if (likedFilms.length > 0) {
<div class="liked-films">
<h3>Polubione filmy:</h3>
@for (film of likedFilms; track film.id) {
<p>{{ film.title }}</p>
}
</div>
}
`,
styles: [`
.liked-films {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin-top: 20px;
}
`]
})
export class FilmListComponent {
films = [
{ id: 1, title: 'Avengers', year: 2019, rating: 8.4 },
{ id: 2, title: 'Batman', year: 2022, rating: 7.8 },
{ id: 3, title: 'Spider-Man', year: 2021, rating: 8.2 }
];
likedFilms: any[] = [];
// Gdy dziecko wysyła wydarzenie "filmLiked"
onFilmLiked(film: any) {
console.log('Film polubiony:', film.title);
// Dodaj do listy polubionych (jeśli jeszcze nie ma)
if (!this.likedFilms.find(f => f.id === film.id)) {
this.likedFilms.push(film);
}
}
// Gdy dziecko wysyła wydarzenie "filmDeleted"
onFilmDeleted(filmId: number) {
console.log('Usuwam film o ID:', filmId);
// Usuń z listy filmów
this.films = this.films.filter(f => f.id !== filmId);
// Usuń z listy polubionych
this.likedFilms = this.likedFilms.filter(f => f.id !== filmId);
}
}Wyjaśnienie:
@Output() filmLiked = new EventEmitter<any>()– tworzę wydarzeniethis.filmLiked.emit(this.film)– wysyłam dane do rodzica(filmLiked)="onFilmLiked($event)"– rodzic słucha i reaguje$event– to dane wysłane przez dziecko
Porównanie sposobów komunikacji
| Typ komunikacji | Kiedy używać | Jak to działa | Przykład |
|---|---|---|---|
| @Input | Rodzic → Dziecko | Przekazanie danych | Lista przekazuje film do karty |
| @Output | Dziecko → Rodzic | Wysłanie wydarzeń | Karta informuje o kliknięciu |
| Service | Rodzeństwo ↔ Rodzeństwo | Wspólny „posłaniec” | Karty komunikują się między sobą |
7. Najważniejsze zasady
✅ Co robić:
- @Input dla danych – gdy rodzic ma dane dla dziecka
- @Output dla wydarzeń – gdy dziecko chce coś przekazać rodzicom
- Service dla rodzeństwa – gdy komponenty na tym samym poziomie muszą się komunikować
- Unsubscribe w ngOnDestroy – zawsze przy Service
❌ Czego unikać:
- Przekazywania danych „w górę” przez @Input – to nie działa
- Zapominania o unsubscribe – wyciek pamięci
- Używania Service do prostej komunikacji rodzic-dziecko
8. Praktyczne wskazówki
Debugowanie komunikacji:
// Dodaj console.log aby zobaczyć co się dzieje
@Input() set film(value: any) {
console.log('Otrzymuję nowe dane:', value);
this._film = value;
}
onFilmLiked(film: any) {
console.log('Otrzymałem wydarzenie:', film);
// reszta logiki...
}Typowanie dla bezpieczeństwa:
// Zamiast 'any' używaj konkretnych typów
interface Film {
id: number;
title: string;
year: number;
rating: number;
}
@Input() film!: Film;
@Output() filmLiked = new EventEmitter<Film>();9. Ćwiczenia do wykonania
- Sklep filmowy: Lista filmów → Koszyk (dodawanie filmów)
- System ocen: Karta filmu → Lista (aktualizacja średniej oceny)