Lifecycle Hooks Angular — Angular

Lifecycle hooks memungkinkan kamu menjalankan kode pada tahap tertentu dari kehidupan komponen. Hooks Utama export class MyComponent implements OnInit…

Lifecycle hook adalah method yang dipanggil Angular sendiri pada saat-saat tertentu dalam hidup sebuah komponen: saat input dari induknya datang atau berubah, sesudah komponennya siap, sesudah tampilannya ada di DOM, dan tepat sebelum komponennya dihapus. Bayangin satu shift juru masak di dapur Bang Jali. Dia datang dan absen (constructor), lalu menerima kertas pesanan (input masuk), mulai memasak (ngOnInit), martabaknya sudah di piring dan bisa dipegang (ngAfterViewInit), pesanannya diganti di tengah jalan (ngOnChanges lagi), lalu pulang dan mematikan kompor (ngOnDestroy). Lupa mematikan kompor, apinya tetap menyala walau orangnya sudah pulang.

🧪 Tebakan cepat: Pesanan punya @Input() menu = '', dan App menulis <app-pesanan [menu]="menu" /> dengan menu = 'Martabak Telur Spesial'. Di constructor Pesanan ada console.log('constructor: menu =', JSON.stringify(this.menu)). Apa yang tercetak? Jawabannya ada di bagian "constructor atau ngOnInit".

Urutan hook

import { AfterViewInit, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core'

@Component({
  selector: 'app-pesanan',
  standalone: true,
  template: `
    <p>{{ judul }}</p>
    <input #catatan placeholder="Catatan" />
  `,
})
export class Pesanan implements OnChanges, OnInit, AfterViewInit, OnDestroy {
  @Input() menu = ''
  @ViewChild('catatan') catatan!: ElementRef<HTMLInputElement>
  judul = ''
  private timer = 0

  constructor() {
    console.log('constructor: menu =', JSON.stringify(this.menu))
  }

  ngOnChanges(changes: SimpleChanges) {
    const c = changes['menu']
    console.log('ngOnChanges:', JSON.stringify(c.previousValue), '->', JSON.stringify(c.currentValue))
  }

  ngOnInit() {
    console.log('ngOnInit: menu =', JSON.stringify(this.menu))
    this.judul = 'Pesanan: ' + this.menu
    this.timer = setInterval(() => console.log('cek status', this.menu), 2000)
  }

  ngAfterViewInit() {
    console.log('ngAfterViewInit: catatan =', this.catatan.nativeElement.tagName)
  }

  ngOnDestroy() {
    clearInterval(this.timer)
    console.log('ngOnDestroy')
  }
}

App menampilkannya dengan @if (buka) { <app-pesanan [menu]="menu" /> }, plus tombol Ganti menu dan Tutup. Console-nya, persis seperti yang dicetak Angular 18:

constructor: menu = ""
ngOnChanges: undefined -> "Martabak Telur Spesial"
ngOnInit: menu = "Martabak Telur Spesial"
ngAfterViewInit: catatan = INPUT
(klik Ganti menu)
ngOnChanges: "Martabak Telur Spesial" -> "Martabak Manis Cokelat Keju"
(klik Tutup)
ngOnDestroy
Hook Kapan dipanggil Biasanya dipakai untuk
constructor saat class-nya dibuat, sebelum input diisi inject() dan nilai awal properti
ngOnChanges sebelum ngOnInit, lalu tiap kali input dari induk berganti nilai menghitung ulang dari input
ngOnInit sekali, sesudah ngOnChanges pertama kerja awal yang butuh input: ambil data, mulai langganan
ngAfterViewInit sekali, sesudah template komponen ini ada di DOM @ViewChild, fokus ke elemen
ngOnDestroy sekali, tepat sebelum komponennya dihapus mematikan timer dan langganan

implements OnInit gak wajib buat Angular, yang memanggil method-nya berdasarkan nama. Gunanya untuk TypeScript: di proyek CLI, salah ketik seperti ngOnInt() langsung ditolak karena class-nya gak lagi memenuhi OnInit.

constructor atau ngOnInit

Angular membuat class-nya dulu, baru mengisi input dari induknya. Saat constructor jalan, menu masih berisi nilai awalnya sendiri, ''. Jadi yang tercetak constructor: menu = "", bukan nama martabaknya. Itu jawaban tebakannya. Input tanpa nilai awal (@Input() menu!: string) malah berisi undefined.

ngOnInit dipanggil sesudah input pertama diisi, jadi this.menu di sana sudah 'Martabak Telur Spesial'. Aturannya: constructor untuk inject() dan nilai awal, ngOnInit untuk kerja yang butuh input. Latihan memakai pola yang sama: message = 'Loading...' diganti jadi 'Komponen sudah diinisialisasi!' di dalam ngOnInit(), dan <p>{{ message }}</p> menampilkannya.

Tapi ngOnInit cuma jalan sekali. Sesudah Ganti menu, judul tetap Pesanan: Martabak Telur Spesial, walau menu sudah Martabak Manis Cokelat Keju. Yang perlu mengikuti input setiap kali, taruh di ngOnChanges, atau baca {{ menu }} langsung di template.

ngOnChanges dan SimpleChanges

ngOnChanges(changes: SimpleChanges) {
  if (changes['menu']) {
    this.judul = 'Pesanan: ' + changes['menu'].currentValue
  }
}

ngOnDestroy: matikan yang kamu nyalakan

setInterval di ngOnInit terus jalan sampai dimatikan. Tanpa clearInterval di ngOnDestroy, ini yang terjadi sesudah Tutup:

Waktu Dengan clearInterval Tanpa clearInterval
Pesanan terbuka cek status tiap 2 detik cek status tiap 2 detik
Sesudah Tutup sepi cek status tetap tercetak tiap 2 detik
Buka lagi satu timer dua timer, dua baris tiap 2 detik

Ini memory leak yang sama dengan langganan timer di pelajaran RxJS: yang kamu nyalakan sendiri (interval, langganan, event listener di window), kamu matikan di ngOnDestroy. Latihan kedua memakai dua hook: ngOnInit mengisi status = 'Active' sehingga <p> berbunyi Status: Active, dan ngOnDestroy mencetak Component destroyed.

ngAfterViewInit dan @ViewChild

@ViewChild('catatan') baru terisi sesudah template komponennya dibuat. Di constructor dan ngOnInit isinya undefined; di ngAfterViewInit sudah ElementRef berisi <input>, jadi this.catatan.nativeElement.focus() aman ditulis di sana.

Jangan mengubah nilai yang sedang ditampilkan template di dalam ngAfterViewInit. Misalnya App menampilkan {{ judul }} dengan judul = 'awal', lalu ngAfterViewInit-nya menulis this.judul = 'Pesanan untuk ' + this.pesanan.menu. Tampilannya sudah selesai diperiksa. Di komponen Eager (bawaan sampai Angular 21), Angular dalam mode development memprotes: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'awal'. Current value: 'Pesanan untuk Martabak Telur Spesial'. Expression location: App component. Di Angular 22, yang komponennya OnPush secara bawaan, gak ada error sama sekali, dan judulnya tetap awal (dicoba di editor latihan). Dua-duanya tanda nilainya ditulis di tempat yang salah: hitung di ngOnInit, atau simpan di signal.

⚠️ Jebakan yang sering ketemu

TL;DR: Urutan hook komponen: constructor, ngOnChanges, ngOnInit, ngAfterViewInit, dan di akhir ngOnDestroy. Input belum terisi di constructor (yang tercetak nilai awalnya sendiri), jadi kerja yang butuh input ditaruh di ngOnInit, yang cuma jalan sekali. ngOnChanges(changes: SimpleChanges) jalan tiap kali induk mengirim nilai input yang baru, dengan previousValue dan currentValue; objek yang isinya diubah gak dihitung. @ViewChild baru terisi di ngAfterViewInit. Interval dan langganan yang dinyalakan sendiri dimatikan di ngOnDestroy, atau mereka terus jalan sesudah komponennya hilang. implements OnInit membuat TypeScript memeriksa nama method-nya.

Yang akan kamu pelajari