Service, DI, dan routing di Angular, dilihat dari React
Angular Service, injector hierarkis vs React Context, Routes, lazy loading, dan guard (sintaks v16)
1. Angular Service: logika keluar dari component
Service adalah "typically a class with a narrow, well-defined purpose", dan component bisa "delegate certain tasks to services, such as fetching data from the server, validating user input, or logging directly to the console" [1]. Ini sama dengan pola Service di backend (lesson 0001): Controller tipis, logika di Service. Di React, padanannya custom hook + Context, atau store seperti Zustand.
@Injectable({ providedIn: 'root' }) // daftar ke root injector
export class ProductService {
constructor(private http: HttpClient) {} // service boleh bergantung pada service lain
getAll() { return this.http.get<Product[]>('/api/products'); }
}
@Component({ selector: 'app-product-list', templateUrl: './product-list.component.html' })
export class ProductListComponent {
constructor(private products: ProductService) {} // inject lewat tipe parameter
// atau: private products = inject(ProductService);
}
Angular membaca tipe parameter constructor untuk tahu apa yang dibutuhkan; cara lain adalah fungsi inject()
[2].
Tidak ada new ProductService() di mana pun: yang membuat instance adalah injector.
"When a dependency is requested, the injector checks its registry to see if there is an instance already available there. If not, a new instance is created and stored in the registry"
[2].
Ini mirip DI container ASP.NET Core yang Anda daftarkan di composition root.
2. Di mana service didaftarkan menentukan berapa instance-nya
| Tempat provider | Instance | Padanan React / .NET |
|---|---|---|
@Injectable({ providedIn: 'root' }) |
"a single, shared instance" untuk seluruh aplikasi, dan tree-shakable [2] | Satu Provider di sekitar <App />; mirip Singleton di .NET |
providers: [...] di @NgModule |
Satu instance untuk component di ModuleInjector yang sama [2] |
Cara lama (sebelum v6); masih banyak di codebase |
providers: [...] di @Component |
"a new instance of the service with each new instance of that component" [2] | <FormContext.Provider> di dalam component itu |
Dokumentasi v16 menyarankan providedIn: "Using the @Injectable() providedIn property is preferable to using the @NgModule() providers array",
karena service yang tidak dipakai bisa dibuang saat build (tree-shaking)
[3].
Service di level component ikut mati bersama component-nya: "When the component instance is destroyed, so is that service instance"
[3].
3. DI hierarkis vs React Context
Angular punya dua hierarki injector: ModuleInjector (dari @NgModule / providedIn) dan
ElementInjector, yang "Created implicitly at each DOM element" dan kosong kecuali Anda mengisi providers di component atau directive.
Urutan pencariannya: "If the component's injector lacks the provider, it passes the request up to its parent component's ElementInjector",
lalu ke ModuleInjector, dan "If Angular still doesn't find the provider, it throws an error"
[3].
Ini pola yang sama dengan React Context: component "will use the value of the nearest <LevelContext> in the UI tree above it"
[4].
Di Angular juga, "the first one Angular encounters is the one it uses to resolve the dependency"
[3],
jadi provider yang lebih dekat meng-override yang di atas.
| React Context | Angular DI | |
|---|---|---|
| Yang dibagi | Value apa saja di value={...} | Instance class Service (atau token lain) |
| Cara meminta | useContext(Ctx) | Tipe parameter constructor atau inject(Service) |
| Yang membuat instance | Anda sendiri, di Provider | Injector, sekali per injector |
| Kalau tidak ada provider | Default value dari createContext | Error NullInjector (kecuali @Optional()) |
Jebakan lazy loading. "Angular creates a lazy loaded module with its own injector, a child of the root injector"
[5].
Kalau service didaftarkan di providers sebuah module yang di-import juga oleh modul lazy, Anda bisa dapat dua instance.
Karena itu RouterModule.forRoot() hanya dipanggil di root dan modul fitur memakai forChild():
"If the RouterModule didn't have forRoot() then each feature module would instantiate a new Router instance, which would break the application as there can only be one Router"
[5].
Aman-nya: pakai providedIn: 'root'.
4. Routing: Routes, routerLink, <router-outlet>
Di aplikasi NgModule v16, ng new --routing membuat AppRoutingModule, "an NgModule where you can configure your routes".
Setiap route berisi path dan component; link memakai atribut routerLink, dan <router-outlet>
"informs Angular to update the application view with the component for the selected route"
[6].
const routes: Routes = [
{ path: 'products', component: ProductListComponent },
{ path: 'products/:id', component: ProductDetailComponent }, // /products/42
{ path: 'admin', canActivate: [authGuard], canMatch: [authGuard],
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) },
{ path: '', redirectTo: 'products', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }, // selalu paling akhir
];
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] })
export class AppRoutingModule {}
| Angular 16 | Padanan React Router |
|---|---|
Routes + RouterModule.forRoot(routes) | createBrowserRouter([...]) / <Routes> |
<a routerLink="/products"> | <Link to="/products"> |
<router-outlet> | <Outlet /> |
inject(ActivatedRoute).snapshot.paramMap.get('id') | useParams().id |
this.router.navigate(['/products', id]) | navigate(`/products/${id}`) |
Urutan route penting: "the Router uses a first-match wins strategy when matching routes, so more specific routes should be placed above less specific routes",
dan wildcard ** "comes last because it matches every URL"
[6].
5. Lazy loading: unduh fitur saat dibutuhkan
"By default, NgModules are eagerly loaded". Lazy loading "loads NgModules as needed" dan "helps keep initial bundle sizes smaller".
Caranya: "use loadChildren (instead of component)" dengan import() dinamis, lalu
"be sure to remove the ItemsModule from the AppModule"
[7].
Padanan React: lazy yang "lets you defer loading component's code until it is rendered for the first time"
[8].
Kalau modul tetap ada di imports AppModule, ia ikut bundle awal dan lazy loading-nya sia-sia.
6. Guard: siapa boleh masuk
Guard dipasang di konfigurasi route. Nilai kembaliannya mengatur router [9]:
true: "The navigation process continues"false: "The navigation process stops and the user stays put"UrlTree: "The current navigation cancels and a new navigation is initiated to theUrlTreereturned" (cara redirect ke/login)- Boleh juga
Observable<boolean>atauPromise<boolean>; router menunggu hasilnya.
// auth.guard.ts — functional guard, gaya v16 (dari contoh resmi router v16)
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const router = inject(Router);
return auth.isLoggedIn ? true : router.parseUrl('/login'); // UrlTree = redirect
};
| Guard | Fungsi (dokumentasi v16) |
|---|---|
canActivate | "To mediate navigation to a route" |
canActivateChild | "To mediate navigation to a child route" |
canDeactivate | "To mediate navigation away from the current route" (mis. form belum disimpan) |
resolve | "To perform route data retrieval before route activation" |
canMatch | "To control whether a Route should be used at all"; dicek sebelum modul lazy dimuat |
Sumber tabel: [9].
Dengan canActivate saja, "the router is still loading the AdminModule even if the user can't visit any of its components";
pasang guard yang sama di canMatch supaya modul hanya dimuat untuk user yang login
[9].
Class guard sudah deprecated. Di v16, class AuthGuard implements CanActivate ditandai
"Class-based Route guards are deprecated in favor of functional guards"
[10].
Anda akan menemukannya di codebase lama, jadi kenali, tapi tulis yang baru sebagai CanActivateFn.
Ingat juga: guard berjalan di browser, jadi ia hanya mengatur navigasi. Data tetap harus dilindungi otorisasi di API (lesson 0017).
Latihan 1 — Kuis campuran
Topiknya sengaja diselang-seling. Jawab tanpa scroll ke atas; kalau ragu, tebak dulu, lalu baca penjelasannya.
Kuis di lesson ini adalah feedback cepat, bukan bukti penguasaan; yang dicatat hanya hasil di mock interview 0018.
Latihan 2 — Ucapkan dengan suara keras
Pasang timer 60 detik. Jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.
"Anda dari React. Bagaimana Angular 16 berbagi logika antar component lewat Service dan DI, dan bagaimana Anda melindungi halaman admin dengan routing dan guard?"
Bandingkan dengan jawaban contoh dan checklist-nya
"Di Angular, logika dan state yang dibagi ditaruh di Angular Service, class @Injectable yang disuntikkan lewat constructor atau inject().
Kalau providedIn: 'root', Angular membuat satu instance bersama untuk seluruh aplikasi, dan service-nya tree-shakable.
Kalau saya taruh di providers sebuah component, setiap instance component itu dapat instance sendiri yang ikut hancur bersama component.
Injector-nya hierarkis: Angular mencari dari ElementInjector component ke parent, lalu ke root, mirip useContext yang membaca Provider terdekat,
bedanya yang dibagi instance class dan dipilih berdasarkan tipe.
Untuk halaman, saya definisikan Routes di AppRoutingModule dengan RouterModule.forRoot, pakai routerLink dan <router-outlet>,
dan wildcard ** di paling akhir karena first-match wins.
Fitur admin saya lazy load dengan loadChildren dan import(), seperti React.lazy.
Lalu saya pasang functional guard CanActivateFn yang meng-inject AuthService: kalau login kembalikan true, kalau tidak kembalikan UrlTree ke /login.
Supaya bundle admin tidak terunduh untuk user anonim, guard yang sama saya pasang di canMatch. Guard hanya UX; otorisasi sebenarnya tetap di API."
Checklist: apakah Anda menyebut…
- Angular Service sebagai class
@Injectableyang disuntikkan lewat constructor atauinject()? providedIn: 'root'= satu instance bersama (dan tree-shakable)?providersdi component = instance per component, resolusi naik ke parent lalu root, dibandingkan dengan React Context?Routes+routerLink+<router-outlet>, dan wildcard**paling akhir?- lazy loading dengan
loadChildren+import()(padananReact.lazy)? - functional guard dengan
inject(), returntrueatauUrlTree, pluscanMatch/ guard bukan pengganti otorisasi API?
Kalau kurang dari 4 poin, ulangi sebelum lesson 0015. Soal ini juga akan muncul lagi di mock interview 0018.
Ringkasan untuk dicetak: Reference: Angular 16 Service, DI, routing, dan guard · Sebelumnya: Reference Angular dasar · Istilah: GLOSSARY.md
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "kapan saya sengaja memakai providers di component, bukan providedIn: 'root'?" atau "apa bedanya canActivate dan canMatch untuk modul lazy?"