Guard adalah fungsi yang ditanya router sebelum masuk ke sebuah rute (atau sebelum keluar darinya): "boleh lewat?". Resolver adalah fungsi yang menyiapkan data sebuah rute sebelum halamannya tampil. Bayangin warung Martabak Bang Jali punya dapur di belakang. Di pintunya ada penjaga yang memeriksa kartu karyawan sebelum kamu masuk (canActivate), dan menahanmu sebelum keluar kalau pesanan di tanganmu belum dikirim (canDeactivate). Sementara itu pelayan menyiapkan papan menu di meja sebelum kamu duduk (resolve).
🧪 Tebakan cepat:
authGuarddibuat mengembalikanfalse(bukanrouter.parseUrl('/login')) saat pembeli belum masuk. Pembeli yang belum masuk mengklik Pesanan. Apa yang tampil? Jawabannya ada di bagian "true, false, atau UrlTree".
CanActivateFn: penjaga sebelum masuk
// src/app/auth.service.ts
@Injectable({ providedIn: 'root' })
export class AuthService {
masuk = signal(false)
}
// src/app/auth.guard.ts
import { inject } from '@angular/core'
import { CanActivateFn, Router } from '@angular/router'
import { AuthService } from './auth.service'
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService)
const router = inject(Router)
console.log('authGuard jalan, menuju ' + state.url)
if (auth.masuk()) return true
return router.parseUrl('/login')
}
// src/app/app.routes.ts
export const routes: Routes = [
{ path: '', redirectTo: 'menu', pathMatch: 'full' },
{ path: 'menu', component: MenuPage },
{ path: 'pesanan', component: PesananPage, canActivate: [authGuard] },
{ path: 'login', component: LoginPage },
]
CanActivateFn(dari@angular/router) adalah tipe untuk guard berbentuk fungsi. Parameternya:route, keterangan rute tujuan, danstate, yangstate.url-nya berisi alamat tujuan,'/pesanan'.- Karena ini fungsi, bukan class, gak ada constructor. Service diambil dengan
inject(), dan itu boleh karena router menjalankan guard di dalam injection context. canActivate: [authGuard]memasang guard di rute. Isinya array, jadi boleh lebih dari satu guard; semuanya harus mengizinkan.
true, false, atau UrlTree
| Guard mengembalikan | Yang terjadi |
|---|---|
true |
navigasi lanjut, PesananPage tampil |
false |
navigasi batal: alamat dan halaman tetap, tanpa pesan, tanpa error |
router.parseUrl('/login') |
sebuah UrlTree: router membatalkan /pesanan dan pindah ke /login |
Jadi dengan return false, klik Pesanan gak menghasilkan apa-apa: pembeli tetap di Menu dan gak tahu kenapa. Itu jawaban tebakannya. Kembalikan UrlTree supaya dia diarahkan ke halaman masuk.
Ini log di Playground saat pembeli yang belum masuk mengklik Pesanan, lalu Masuk:
| Kejadian | Console | Halaman |
|---|---|---|
| Klik Pesanan | authGuard jalan, menuju /pesanan |
Masuk dulu (/login) |
Klik Masuk: masuk.set(true), lalu router.navigateByUrl('/pesanan') |
authGuard jalan, menuju /pesanan |
Pesanan |
router.createUrlTree(['/login'], { queryParams: { kembali: state.url } })juga sebuah UrlTree, dan menambahkan?kembali=%2Fpesanansupaya halaman login tahu harus kembali ke mana.router.navigate(['/login'])lalureturn falsejuga berhasil pindah, tapi itu dua langkah. UrlTree cukup satu.- Guard boleh mengembalikan Observable atau Promise dari nilai-nilai tadi; router menunggu jawabannya.
CanDeactivateFn: penjaga sebelum keluar
// src/app/belum-kirim.guard.ts
export const belumKirimGuard: CanDeactivateFn<PesananPage> = (page) => {
console.log('belumKirimGuard jalan, catatan: ' + JSON.stringify(page.catatan))
return page.catatan === '' || confirm('Pesanan belum dikirim. Tetap pindah?')
}
// di daftar rute
{ path: 'pesanan', component: PesananPage, canActivate: [authGuard], canDeactivate: [belumKirimGuard] },
CanDeactivateFn<PesananPage>menerima komponen yang sedang tampil sebagai parameter pertama, jadi guard bisa membaca isinya, di sinicatatan.- Catatan kosong: boleh keluar. Ada isinya:
confirm()bertanya, dan Batal membuat guard mengembalikanfalse. - Router menanyai rute yang ditinggal dulu (
canDeactivate), baru rute tujuan (canActivate). - Playground mematikan dialog, jadi di sana
confirm()selalufalse: dengan catatan"Pedas", console menulisbelumKirimGuard jalan, catatan: "Pedas"dan kamu tetap di Pesanan.
ResolveFn: data siap sebelum halaman tampil
// src/app/menu.resolver.ts
export const menuResolver: ResolveFn<Menu[]> = () => {
console.log('menuResolver jalan')
return inject(MenuApi).ambilMenu()
}
// di daftar rute
{ path: 'menu', component: MenuPage, resolve: { menu: menuResolver } },
// src/app/menu/menu.ts
export class MenuPage {
menu: Menu[] = inject(ActivatedRoute).snapshot.data['menu']
constructor() {
console.log('MenuPage dibuat, menu: ' + this.menu.length)
}
}
| Waktu | Console | Yang tampil |
|---|---|---|
| Klik Menu dari Pesanan | menuResolver jalan |
masih Pesanan |
| 600 ms kemudian, data sampai | MenuPage dibuat, menu: 3 |
Menu dengan 3 martabak |
resolve: { menu: menuResolver }menjalankan resolver dan menyimpan hasilnya dengan namamenu.- Router menunggu Observable dari
ambilMenu()selesai, baru membuat MenuPage. Jadi resolver memuat data sebelum navigasi selesai, dan komponennya langsung dapat data yang siap disnapshot.data['menu']. - Selama menunggu, halaman lama tetap tampil. Saat aplikasi baru dibuka, outlet-nya kosong sampai datanya sampai.
- Kalau request-nya gagal, navigasinya batal dan halaman lama tetap. Console menulis error-nya, misalnya
ERROR Error: Http failure response: 500.
📌 Kapan resolver, kapan di komponen
Resolver membuat pembeli menunggu tanpa tanda apa pun. Untuk data yang cepat dan wajib ada (detail satu menu), resolver pas. Untuk data yang lambat, ambil di komponen seperti di pelajaran HttpClient, lalu tampilkan "Memuat...". Bentuk lama guard berupa class dengan implements CanActivate masih ketemu di proyek lama; bentuk fungsi di atas yang dipakai sekarang.
⚠️ Jebakan yang sering ketemu
return falsetanpa pengalihan. Pembeli terjebak tanpa penjelasan. Kembalikanrouter.parseUrl('/login').- Mengira
canActivatejuga menjaga saat keluar. Dia cuma ditanya sebelum masuk. Form yang belum dikirim butuhcanDeactivate. inject()di dalam callback. Panggilinject()di baris-baris awal fungsi guard, bukan di dalamsubscribeatausetTimeoutyang jalan belakangan.- Detail latihan. Guard-nya
export const authGuard: CanActivateFn = (route, state) => { ... }, denganinjectdari@angular/coredanCanActivateFn, Routerdari@angular/router. Di dalamnya:const router = inject(Router), laluif (isLoggedIn) return truedanreturn router.parseUrl('/login'). Penilai mencari tulisanreturn true,=> true, atau? true :, jadireturn isLoggedIn ? true : router.parseUrl('/login')juga diterima.
TL;DR: Guard menjawab "boleh lewat?" untuk sebuah rute. CanActivateFn ditanya sebelum masuk (canActivate: [authGuard]), mengambil service dengan inject(), dan mengembalikan true untuk lanjut, false untuk membatalkan diam-diam, atau UrlTree seperti router.parseUrl('/login') untuk mengalihkan. CanDeactivateFn<PesananPage> ditanya sebelum keluar, menerima komponennya, dan cocok untuk form yang belum dikirim. ResolveFn di resolve: { menu: menuResolver } memuat data sebelum navigasi selesai, jadi komponen dibuat dengan data yang sudah siap dan halaman lama bertahan selama menunggu.