1. Czym są Lifecycle Hooks?
Lifecycle Hooks, czyli haki życia komponentu, to specjalne metody w Angularze, które oferują możliwość interwencji w różne momenty cyklu życia komponentów i dyrektyw. Dzięki nim, deweloperzy mogą wykonywać określone akcje w kluczowych momentach, takich jak inicjalizacja komponentu, detekcja zmian, czy też jego zniszczenie.
Analogia z życia:
Komponent = Człowiek 🧑
- Narodziny (constructor) – pierwsze chwile życia
- Pierwsze kroki (ngOnInit) – gotowość do działania
- Dorastanie (ngOnChanges) – reagowanie na zmiany
- Śmierć (ngOnDestroy) – pożegnanie i porządki
W aplikacji filmowej:
Film Component zostaje utworzony → ładuje dane → reaguje na zmiany → zostaje usunięty2. Podstawowe Lifecycle Hooks
W Angularze istnieje kilka głównych haków życia komponentu, które pomagają zarządzać różnymi aspektami działania komponentu:
constructor– tworzenie komponentu (nie jest lifecycle hook, ale ważny)ngOnChanges– wykrywanie zmian w danych wejściowych (@Input)ngOnInit– inicjalizacja komponentu (uruchamia się raz)ngDoCheck– ręczne sprawdzanie zmianngAfterContentInit– po wstawieniu treści (ng-content)ngAfterContentChecked– po sprawdzeniu treścingAfterViewInit– po inicjalizacji widokungAfterViewChecked– po sprawdzeniu widokungOnDestroy– przed usunięciem komponentu
3. Przykład z profilem użytkownika (Twój kod)
Definicja komponentu
// user-profile.component.ts
import { Component, Input, OnInit, OnDestroy, SimpleChanges, OnChanges } from '@angular/core';
@Component({
selector: 'app-user-profile',
standalone: true,
template: `<p>Profil użytkownika: {{ userData.name }}</p>`,
styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent implements OnInit, OnChanges, OnDestroy {
@Input() userData: { name: string, age: number };
constructor() {
console.log('UserProfileComponent constructed');
}
ngOnChanges(changes: SimpleChanges): void {
console.log('NgOnChanges triggered:', changes);
}
ngOnInit(): void {
console.log('Component initialized with data:', this.userData);
}
ngOnDestroy(): void {
console.log('Component is being destroyed');
}
}Użycie komponentu
<!-- Some parent component template -->
<app-user-profile [userData]="currentUser"></app-user-profile>Wyjaśnienie kodu
- Konstruktor (
constructor): Używany do inicjalizacji podstawowych wartości, serwisów lub innych zależności. Nie jest to lifecycle hook, ale jest ważnym elementem inicjalizacji klasy w Angularze. ngOnChanges: Reaguje na zmiany w@Input()properties. W przykładzie rejestruje zmiany w danych wejściowychuserData.ngOnInit: Idealne miejsce do inicjalizacji komponentu, np. pobrania danych z serwera. W przykładzie loguje dane użytkownika po ich inicjalizacji.ngOnDestroy: Umożliwia wykonanie sprzątania, takiego jak anulowanie subskrypcji obserwabli. W przykładzie loguje informacje o zniszczeniu komponentu.
4. Praktyczne przykłady z aplikacją filmową
Przykład 1: Komponent karty filmu z ngOnInit i ngOnDestroy
// film-card.component.ts
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-film-card',
standalone: true,
template: `
<div class="film-card">
<h3>{{ film.title }}</h3>
<p>Ocena: {{ film.rating }}/10</p>
<p>Wyświetleń: {{ viewCount }}</p>
</div>
`,
styles: [`
.film-card {
border: 1px solid #ddd;
padding: 15px;
margin: 10px;
border-radius: 8px;
}
`]
})
export class FilmCardComponent implements OnInit, OnDestroy {
@Input() film: any;
viewCount = 0;
private timer: any;
ngOnInit() {
console.log('🎬 Film Card created for:', this.film.title);
// Symulacja licznika wyświetleń
this.timer = setInterval(() => {
this.viewCount++;
}, 1000);
}
ngOnDestroy() {
console.log('💀 Film Card destroyed for:', this.film.title);
// WAŻNE: Zatrzymanie timera przed zniszczeniem
if (this.timer) {
clearInterval(this.timer);
}
}
}Przykład 2: Komponent z ngOnChanges – reakcja na zmiany
// film-details.component.ts
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-film-details',
standalone: true,
template: `
<div class="film-details">
<h2>{{ film.title }}</h2>
<p>{{ changeMessage }}</p>
<p>Ostatnia zmiana: {{ lastChangeTime }}</p>
</div>
`,
styles: [`
.film-details {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
}
`]
})
export class FilmDetailsComponent implements OnChanges {
@Input() film: any;
changeMessage = '';
lastChangeTime = '';
ngOnChanges(changes: SimpleChanges) {
console.log('🔄 Changes detected:', changes);
if (changes['film']) {
const change = changes['film'];
if (change.firstChange) {
this.changeMessage = 'Film został załadowany po raz pierwszy';
} else {
this.changeMessage = `Film zmieniony z "${change.previousValue?.title}" na "${change.currentValue?.title}"`;
}
this.lastChangeTime = new Date().toLocaleTimeString();
}
}
}Przykład 3: Komponent z ngAfterViewInit – dostęp do elementów DOM
// film-player.component.ts
import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-film-player',
standalone: true,
template: `
<div class="player">
<video #videoPlayer width="400" height="300" controls>
<source src="assets/videos/trailer.mp4" type="video/mp4">
</video>
<p>Status: {{ playerStatus }}</p>
</div>
`,
styles: [`
.player {
text-align: center;
padding: 20px;
}
`]
})
export class FilmPlayerComponent implements AfterViewInit {
@ViewChild('videoPlayer') videoElement!: ElementRef;
playerStatus = 'Inicjalizacja...';
ngAfterViewInit() {
console.log('🎥 Video player view initialized');
// Teraz mamy dostęp do elementu video
const video = this.videoElement.nativeElement;
// Dodawanie event listenerów
video.addEventListener('loadeddata', () => {
this.playerStatus = 'Gotowy do odtwarzania';
});
video.addEventListener('play', () => {
this.playerStatus = 'Odtwarzanie...';
});
video.addEventListener('pause', () => {
this.playerStatus = 'Wstrzymano';
});
}
}Przykład 4: Kompletny przykład z wieloma hooks
// film-manager.component.ts
import { Component, Input, OnInit, OnChanges, OnDestroy, AfterViewInit, SimpleChanges } from '@angular/core';
import { Subscription, interval } from 'rxjs';
@Component({
selector: 'app-film-manager',
standalone: true,
template: `
<div class="film-manager">
<h3>{{ film.title }}</h3>
<p>{{ statusMessage }}</p>
<p>Uptime: {{ uptime }} sekund</p>
</div>
`,
styles: [`
.film-manager {
border: 2px solid #007bff;
padding: 15px;
margin: 10px;
border-radius: 8px;
}
`]
})
export class FilmManagerComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {
@Input() film: any;
statusMessage = '';
uptime = 0;
private subscription?: Subscription;
constructor() {
console.log('🏗️ Constructor: Komponent tworzony');
this.statusMessage = 'Konstruktor wykonany';
}
ngOnChanges(changes: SimpleChanges) {
console.log('🔄 ngOnChanges: Wykryto zmiany');
this.statusMessage = 'Dane zostały zmienione';
}
ngOnInit() {
console.log('🚀 ngOnInit: Komponent inicjalizowany');
this.statusMessage = 'Komponent zainicjalizowany';
// Rozpocznij licznik czasu
this.subscription = interval(1000).subscribe(() => {
this.uptime++;
});
}
ngAfterViewInit() {
console.log('👀 ngAfterViewInit: Widok zainicjalizowany');
this.statusMessage = 'Widok gotowy';
}
ngOnDestroy() {
console.log('💀 ngOnDestroy: Komponent niszczony');
// WAŻNE: Wyczyść subskrypcje
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}5. Kolejność wykonywania hooks
Podczas tworzenia komponentu:
1. constructor() // Tworzenie instancji
2. ngOnChanges() // Jeśli są @Input properties
3. ngOnInit() // Inicjalizacja (tylko raz)
4. ngDoCheck() // Sprawdzanie zmian
5. ngAfterContentInit() // Treść załadowana (tylko raz)
6. ngAfterContentChecked() // Treść sprawdzona
7. ngAfterViewInit() // Widok załadowany (tylko raz)
8. ngAfterViewChecked() // Widok sprawdzonyPodczas aktualizacji:
1. ngOnChanges() // Gdy @Input się zmieni
2. ngDoCheck() // Sprawdzanie zmian
3. ngAfterContentChecked() // Treść sprawdzona
4. ngAfterViewChecked() // Widok sprawdzonyPodczas niszczenia:
1. ngOnDestroy() // Sprzątanie przed zniszczeniem6. Najczęstsze zastosowania
ngOnInit – Inicjalizacja
ngOnInit() {
// ✅ Pobieranie danych z API
this.loadFilmData();
// ✅ Konfiguracja subskrypcji
this.subscription = this.service.getData().subscribe();
// ✅ Inicjalizacja zmiennych
this.setupDefaults();
}ngOnDestroy – Sprzątanie
ngOnDestroy() {
// ✅ Anulowanie subskrypcji
if (this.subscription) {
this.subscription.unsubscribe();
}
// ✅ Zatrzymanie timerów
if (this.timer) {
clearInterval(this.timer);
}
// ✅ Usuwanie event listenerów
window.removeEventListener('resize', this.onResize);
}ngOnChanges – Reakcja na zmiany
ngOnChanges(changes: SimpleChanges) {
if (changes['filmId']) {
// ✅ Reaguj na zmianę ID filmu
this.loadNewFilm(changes['filmId'].currentValue);
}
}7. Najlepsze praktyki
✅ Co robić:
- Zawsze implementuj interfejsy –
implements OnInit, OnDestroy - Czyść subskrypcje w ngOnDestroy – unikaj wycieków pamięci
- Używaj ngOnInit do inicjalizacji – nie constructor
- Sprawdzaj zmiany w ngOnChanges – użyj
if (changes['propertyName'])
❌ Czego unikać:
- Ciężkie operacje w constructor – użyj ngOnInit
- Zapominanie o unsubscribe – wyciek pamięci!
- Manipulacja DOM w ngOnInit – użyj ngAfterViewInit
- Ignorowanie SimpleChanges – sprawdzaj co się zmieniło
Przykład dobrej praktyki:
export class GoodPracticeComponent implements OnInit, OnDestroy {
private subscriptions = new Subscription();
ngOnInit() {
// Grupowanie subskrypcji
this.subscriptions.add(
this.service1.getData().subscribe()
);
this.subscriptions.add(
this.service2.getData().subscribe()
);
}
ngOnDestroy() {
// Jedna linijka czyści wszystkie subskrypcje
this.subscriptions.unsubscribe();
}
}8. Ćwiczenia do wykonania
- Timer komponentu – stwórz komponent z licznikiem używając ngOnInit/ngOnDestroy
- Dynamiczne szczegóły – komponent reagujący na zmiany ID filmu (ngOnChanges)
- Chat komponent – subskrypcje na wiadomości z proper cleanup