Lazy loading membuat kode sebuah halaman baru diunduh saat halaman itu pertama kali dibuka, bukan saat aplikasi start. Hasilnya: unduhan awal lebih kecil, dan halaman pertama tampil lebih cepat. Bayangin Bang Jali membuka gerobak tiap sore. Dia gak membawa semua bahan dari gudang; yang dibawa cuma bahan menu yang hampir pasti dipesan. Bahan martabak spesial baru diambil dari gudang saat ada yang memesannya, dan sekali sudah diambil, bahannya tetap di gerobak. Gerobak itu bundle awal, gudang itu file chunk di server, dan pesanan pertama itu kunjungan pertama ke rutenya.
🧪 Tebakan cepat: Rute Pesanan dibuat lazy. Kamu buka
/menu, klik Pesanan (chunk-nya diunduh), balik ke Menu, lalu klik Pesanan sekali lagi. Chunk Pesanan diunduh berapa kali? Jawabannya ada di bagian "Diunduh sekali, saat pertama dibuka".
Dari component ke loadComponent
Daftar rute dari pelajaran Routing memakai component, jadi semua halaman ikut di bundle awal:
// src/app/app.routes.ts, versi eager
import { Routes } from '@angular/router'
import { MenuPage } from './menu/menu'
import { PesananPage } from './pesanan/pesanan'
import { ADMIN_ROUTES } from './admin/admin.routes'
export const routes: Routes = [
{ path: '', redirectTo: 'menu', pathMatch: 'full' },
{ path: 'menu', component: MenuPage },
{ path: 'pesanan', component: PesananPage },
{ path: 'admin', children: ADMIN_ROUTES },
]
Versi lazy-nya:
// src/app/app.routes.ts, versi lazy
import { Routes } from '@angular/router'
import { MenuPage } from './menu/menu'
export const routes: Routes = [
{ path: '', redirectTo: 'menu', pathMatch: 'full' },
{ path: 'menu', component: MenuPage },
{
path: 'pesanan',
loadComponent: () => import('./pesanan/pesanan').then((m) => m.PesananPage),
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then((m) => m.ADMIN_ROUTES),
},
]
import('./pesanan/pesanan')adalah dynamic import, fungsi JavaScript bawaan. Beda denganimportdi atas file, dia baru jalan saat dipanggil, dan hasilnya Promise berisi modul file itu..then((m) => m.PesananPage)mengambil class komponennya dari modul tadi.loadComponentdiisi fungsi yang memanggilimport(). Router memanggil fungsi itu saat/pesanandibuka, gak sebelumnya.- Import biasa
PesananPagedi atas file dihapus. Kalau dibiarkan, komponennya tetap ikut bundle awal. - Menu tetap
component: halaman pertama memang dibutuhkan saat start.
Apa yang dihasilkan ng build
Ini output ng build (Angular 20) untuk aplikasi contoh Martabak Bang Jali, persis seperti dicetak. Versi eager:
Initial chunk files | Names | Raw size | Estimated transfer size
main-DJOC73FW.js | main | 239.57 kB | 63.52 kB
polyfills-5CFQRCPP.js | polyfills | 34.59 kB | 11.33 kB
| Initial total | 274.16 kB | 74.86 kB
Versi lazy:
Initial chunk files | Names | Raw size | Estimated transfer size
chunk-KN7FB5LX.js | - | 120.30 kB | 36.09 kB
main-FV3QO47T.js | main | 83.95 kB | 21.56 kB
polyfills-5CFQRCPP.js | polyfills | 34.59 kB | 11.33 kB
chunk-3CKZ4X2Z.js | - | 5.63 kB | 1.61 kB
| Initial total | 244.47 kB | 70.59 kB
Lazy chunk files | Names | Raw size | Estimated transfer size
chunk-M4JL3FZ7.js | pesanan | 31.18 kB | 7.12 kB
chunk-KQMY4PR3.js | admin-routes | 614 bytes | 614 bytes
- Unduhan awal turun dari 274,16 kB jadi 244,47 kB. Itu manfaat utamanya: bundle awal mengecil, jadi halaman pertama lebih cepat tampil.
chunk-M4JL3FZ7.js(31,18 kB) berisi PesananPage plusReactiveFormsModule, karena cuma halaman itu yang memakai form. Kode yang dipakai satu halaman ikut pindah bersamanya.- Di dalam
main,import('./pesanan/pesanan')sudah ditulis ulang jadiimport("./chunk-M4JL3FZ7.js"). chunk-KN7FB5LX.jsdanchunk-3CKZ4X2Z.jsadalah potongan bersama: kode Angular yang dipakaimaindan chunk lazy, dipisah supaya gak diunduh dua kali.
Diunduh sekali, saat pertama dibuka
Hasil build tadi dibuka di Chrome, dan ini file JavaScript yang diminta browser di tiap langkah:
| Langkah | File yang diminta |
|---|---|
Buka / (dialihkan ke /menu) |
chunk-3CKZ4X2Z.js, chunk-KN7FB5LX.js, polyfills-5CFQRCPP.js, main-FV3QO47T.js |
| Klik Pesanan | chunk-M4JL3FZ7.js |
| Klik Menu | gak ada |
| Klik Pesanan lagi | gak ada |
| Klik Admin | chunk-KQMY4PR3.js |
Chunk Pesanan diunduh sekali, saat /pesanan pertama dibuka. Itu jawaban tebakannya. Router menyimpan komponen yang sudah dimuat, jadi loadComponent gak dipanggil lagi. Selama chunk-nya masih diunduh, halaman lama tetap tampil; outlet baru berganti setelah chunk-nya sampai.
loadChildren: satu chunk untuk sekelompok rute
// src/app/admin/admin.routes.ts
import { Routes } from '@angular/router'
import { AdminStok } from './admin-stok'
import { AdminLaporan } from './admin-laporan'
export const ADMIN_ROUTES: Routes = [
{ path: '', component: AdminStok },
{ path: 'laporan', component: AdminLaporan },
]
loadChildren memuat daftar rute, bukan satu komponen. /admin dan /admin/laporan sama-sama ada di chunk-KQMY4PR3.js, jadi pindah ke laporan gak perlu unduhan lagi.
Di Playground: gak ada bundler
Playground menjalankan satu file langsung di browser, tanpa bundler dan tanpa file lain. Rute dengan import('./pesanan/pesanan') di sana gagal saat Pesanan diklik, halamannya tetap di Menu, dan console menulis:
ERROR TypeError: Failed to resolve module specifier './pesanan/pesanan'
Karena itu, kode Lab di Playground mengganti import() dengan Promise yang mengembalikan class dari file yang sama 600 ms kemudian. Router memperlakukannya sama: console menulis mengunduh chunk pesanan sekali, di kunjungan pertama saja.
Latihan juga berjalan di editor seperti ini. import() di dalam fungsi loadComponent gak pernah dipanggil, karena gak ada yang membuka /admin, jadi gak ada error. Import biasa di atas file malah langsung gagal saat kode dijalankan: Failed to resolve module specifier "./admin/admin.component". Invalid relative url or base scheme isn't hierarchical. Bedanya kelihatan jelas: import biasa jalan saat start, import() menunggu dipanggil.
📌 Preloading dan @defer
provideRouter(routes, withPreloading(PreloadAllModules))mengunduh semua chunk lazy di belakang layar setelah aplikasi tampil. Halaman pertama tetap cepat, dan klik pertama ke Pesanan biasanya gak perlu menunggu lagi.@deferadalah lazy loading di dalam template, bukan di rute:@defer (on viewport) { <app-ulasan /> } @placeholder { <p>Memuat ulasan...</p> }. Komponen standalone yang cuma dipakai di dalam@deferdipisah ke chunk sendiri dan diunduh saat bagian itu terlihat.
⚠️ Jebakan yang sering ketemu
- Masih meng-import komponennya di atas file. Satu import biasa di mana pun menarik komponen itu ke bundle awal. Di aplikasi contoh, chunk
pesanantinggal 94 bytes dan Initial total malah naik jadi 275,65 kB. - Mengisi class, bukan fungsi.
loadComponent: PesananPagemembuat navigasinya gagal:TypeError: Class constructor PesananPage cannot be invoked without 'new'. - Lupa
.then(...). Tanpa.then((m) => m.PesananPage), router menerima modul utuh. Itu cuma jalan kalau komponennyaexport default; kalau bukan, Angular 18 menolak denganNG04014: Invalid configuration of route 'pesanan'. The component must be standalone. - Deploy baru saat tab lama masih terbuka. Nama chunk berganti tiap build. Tab yang dibuka sebelum deploy meminta chunk lama yang sudah dihapus, navigasinya batal, dan console menulis
ERROR TypeError: Failed to fetch dynamically imported module: .../chunk-M4JL3FZ7.js. - Detail latihan. Tulis satu rute di dalam array
routes:path: 'admin'danloadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent). Penilai mencariloadComponentyang diisi fungsi panah yang langsung memanggilimport(, danpath: 'admin'di rute yang sama.
TL;DR: Lazy loading menunda unduhan kode sebuah halaman sampai rutenya pertama dibuka. loadComponent: () => import('./pesanan/pesanan').then((m) => m.PesananPage) memakai dynamic import, import(), yang baru jalan saat router memanggil fungsinya; loadChildren melakukan hal yang sama untuk sekelompok rute. ng build memisahkannya ke chunk sendiri (di contoh, bundle awal turun dari 274,16 kB jadi 244,47 kB), chunk itu diunduh sekali saat kunjungan pertama, dan halaman lama tetap tampil sampai chunk-nya sampai. Jangan meng-import komponen lazy secara biasa di file mana pun, isi loadComponent dengan fungsi, dan jangan lupa .then(...).