Lesson 0014 · Frontend · ±14 menit

Service, DI, dan routing di Angular, dilihat dari React

Angular Service, injector hierarkis vs React Context, Routes, lazy loading, dan guard (sintaks v16)

Target lesson ini: Anda bisa menjawab pertanyaan interview "Anda dari React. Bagaimana Angular 16 berbagi logika antar component lewat Service dan DI, dan bagaimana Anda melindungi halaman admin dengan routing dan guard?" dalam 60 detik, dengan padanan React untuk setiap bagian.

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 providerInstancePadanan 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 ContextAngular DI
Yang dibagiValue apa saja di value={...}Instance class Service (atau token lain)
Cara memintauseContext(Ctx)Tipe parameter constructor atau inject(Service)
Yang membuat instanceAnda sendiri, di ProviderInjector, sekali per injector
Kalau tidak ada providerDefault value dari createContextError 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 16Padanan 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]:

// 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
};
GuardFungsi (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 @Injectable yang disuntikkan lewat constructor atau inject()?
  • providedIn: 'root' = satu instance bersama (dan tree-shakable)?
  • providers di 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() (padanan React.lazy)?
  • functional guard dengan inject(), return true atau UrlTree, plus canMatch / guard bukan pengganti otorisasi API?

Kalau kurang dari 4 poin, ulangi sebelum lesson 0015. Soal ini juga akan muncul lagi di mock interview 0018.

Bacaan utama (±15 menit) Angular v16: Hierarchical injectors, dokumentasi resmi versi 16. Baca bagian "Types of injector hierarchies" dan "Resolution rules". Kalau masih ada waktu, lanjut ke Router tutorial: Milestone 5, Route guards.

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?"