Dependency Injection Lanjutan di Angular 2026: Providers, Tokens, dan Pertanyaan Interview
Pelajari sistem dependency injection Angular secara mendalam termasuk strategi provider, InjectionToken, injector hierarkis, dan pertanyaan interview umum untuk developer Angular berpengalaman.

Sistem dependency injection Angular memberikan kontrol yang presisi terhadap instansiasi service melalui providers, tokens, dan injector hierarkis. Penguasaan konsep-konsep ini membedakan developer Angular berpengalaman dari junior dalam technical interview maupun kode produksi.
Angular memelihara dua pohon injector paralel: pohon ModuleInjector untuk service yang disediakan di level module, dan pohon ElementInjector untuk dependensi yang di-scope ke komponen. Resolusi dimulai dari level elemen dan naik ke root.
Strategi Konfigurasi Provider di Angular DI
Angular menawarkan berbagai strategi konfigurasi provider, masing-masing cocok untuk use case yang berbeda. Konfigurasi paling umum menggunakan useClass, useValue, useFactory, dan useExisting.
import { ApplicationConfig, InjectionToken } from '@angular/core';
import { LoggerService } from './services/logger.service';
import { DebugLoggerService } from './services/debug-logger.service';
import { API_CONFIG, ApiConfig } from './config/api.config';
export const appConfig: ApplicationConfig = {
providers: [
// useClass: provide a different implementation
{ provide: LoggerService, useClass: DebugLoggerService },
// useValue: provide a static configuration object
{
provide: API_CONFIG,
useValue: { baseUrl: 'https://api.example.com', timeout: 5000 }
},
// useFactory: create dependency with runtime logic
{
provide: 'FEATURE_FLAGS',
useFactory: () => {
const env = import.meta.env.MODE;
return { debugMode: env === 'development', analytics: env === 'production' };
}
},
// useExisting: create an alias to another provider
{ provide: 'Logger', useExisting: LoggerService }
]
};Strategi useClass menukar implementasi tanpa mengubah kode consumer. Strategi useValue menyediakan objek statis seperti konfigurasi. Strategi useFactory menangani keputusan runtime, dan useExisting membuat alias untuk akses polimorfik.
InjectionToken untuk Dependensi Non-Class yang Type-Safe
Sementara @Injectable bekerja untuk service berbasis class, nilai non-class seperti objek konfigurasi, primitive, atau fungsi memerlukan InjectionToken. Token ini bertindak sebagai kunci unik di registry DI Angular.
import { InjectionToken } from '@angular/core';
export interface ApiConfig {
baseUrl: string;
timeout: number;
retryAttempts: number;
}
// Generic parameter ensures type safety at injection point
export const API_CONFIG = new InjectionToken<ApiConfig>('api.config', {
providedIn: 'root',
factory: () => ({
baseUrl: 'https://api.sharpskill.dev',
timeout: 30000,
retryAttempts: 3
})
});
// Token for primitive values
export const MAX_UPLOAD_SIZE = new InjectionToken<number>('max.upload.size', {
providedIn: 'root',
factory: () => 10 * 1024 * 1024 // 10MB
});Parameter tipe generic pada InjectionToken<ApiConfig> merambat ke pemanggilan inject(). TypeScript mengetahui nilai yang di-inject sesuai dengan tipe yang dideklarasikan, menangkap kesalahan penggunaan pada waktu kompilasi bukan runtime.
Fungsi inject() vs Constructor Injection
Angular 14 memperkenalkan fungsi inject() sebagai alternatif dari injection berbasis constructor. Di Angular 20+, inject() telah menjadi pendekatan yang lebih disukai, terutama di standalone component dan konteks fungsional.
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { API_CONFIG } from '../tokens/config.tokens';
@Injectable({ providedIn: 'root' })
export class UserService {
// Modern approach: inject() at field level
private readonly http = inject(HttpClient);
private readonly config = inject(API_CONFIG);
getUser(id: string) {
return this.http.get(`${this.config.baseUrl}/users/${id}`);
}
}
// Alternative: constructor injection (still valid)
@Injectable({ providedIn: 'root' })
export class UserServiceLegacy {
constructor(
private readonly http: HttpClient,
@Inject(API_CONFIG) private readonly config: ApiConfig
) {}
}Fungsi inject() menghilangkan boilerplate decorator untuk token dan memungkinkan dependency injection dalam konteks non-class seperti functional guard, resolver, dan interceptor.
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
// Functional guard using inject()
export const authGuard: CanActivateFn = () => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isAuthenticated()) {
return true;
}
return router.createUrlTree(['/login']);
};Fungsi inject() hanya bekerja dalam injection context: selama konstruksi class, dalam factory function, atau dalam konstruksi Angular fungsional. Memanggilnya di luar konteks ini akan melempar runtime error.
Siap menguasai wawancara Angular Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Injector Hierarkis: ElementInjector vs EnvironmentInjector
Angular memelihara dua hierarki injector paralel yang menentukan scope dan urutan resolusi service. Memahami arsitektur ini sangat penting untuk mengontrol lifetime dan visibilitas service.
import { Injectable } from '@angular/core';
// Root-level singleton: single instance across entire app
@Injectable({ providedIn: 'root' })
export class GlobalDataService {
private data = new Map<string, unknown>();
set(key: string, value: unknown) { this.data.set(key, value); }
get(key: string) { return this.data.get(key); }
}
// Component-scoped: new instance per component
@Injectable()
export class ComponentDataService {
private data = new Map<string, unknown>();
set(key: string, value: unknown) { this.data.set(key, value); }
get(key: string) { return this.data.get(key); }
}import { Component } from '@angular/core';
import { ComponentDataService } from './services/component-data.service';
@Component({
selector: 'app-dashboard',
standalone: true,
// This component and all children get the same instance
providers: [ComponentDataService],
template: `
<app-widget />
<app-stats />
`
})
export class DashboardComponent {}Ketika komponen mendeklarasikan provider, Angular membuat instance baru yang di-scope ke ElementInjector komponen tersebut. Komponen anak mewarisi akses ke provider parent kecuali mereka mendeklarasikan provider mereka sendiri.
Algoritma resolusi mengikuti jalur ini:
- Periksa ElementInjector komponen yang meminta
- Naik ke atas pohon ElementInjector ke ancestor
- Periksa EnvironmentInjector (module atau standalone provider)
- Naik ke root EnvironmentInjector
- Mencapai NullInjector dan throw error jika
@Optional()tidak digunakan
Modifier Resolusi: @Self, @SkipSelf, @Optional, @Host
Modifier resolusi mengubah cara Angular mencari hierarki injector. Decorator ini bekerja dengan constructor injection maupun fungsi inject().
import { Component, Optional, SkipSelf, Self, inject } from '@angular/core';
import { PanelService } from './panel.service';
@Component({
selector: 'app-panel',
standalone: true,
providers: [PanelService],
template: `<ng-content />`
})
export class PanelComponent {
// @Self: only look in this component's injector, fail otherwise
private readonly localService = inject(PanelService, { self: true });
// @SkipSelf: skip this component, start search from parent
private readonly parentService = inject(PanelService, {
skipSelf: true,
optional: true
});
// @Optional: return null instead of throwing if not found
private readonly optionalService = inject(PanelService, { optional: true });
constructor() {
// localService is always the component's own instance
// parentService is the parent's instance or null
console.log('Local:', this.localService);
console.log('Parent:', this.parentService);
}
}Modifier @Host() membatasi resolusi ke injector host element dan berhenti di batas komponen. Ini berguna ketika directive perlu mengakses service yang disediakan oleh host component-nya tetapi tidak boleh mencapai lebih tinggi di pohon.
import { Directive, inject, Host, Optional } from '@angular/core';
import { HighlightConfig } from './highlight.config';
@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
// Only look at the host component's providers
private readonly config = inject(HighlightConfig, {
host: true,
optional: true
});
constructor() {
// config is null if host component didn't provide HighlightConfig
const color = this.config?.color ?? 'yellow';
// Apply highlighting...
}
}Multi-Provider untuk Sistem yang Extensible
Multi-provider memungkinkan beberapa nilai didaftarkan di bawah satu token. Angular mengembalikan semua nilai terdaftar sebagai array, memungkinkan arsitektur plugin dan pola extensibility.
import { InjectionToken } from '@angular/core';
export interface Validator {
validate(value: string): string | null;
}
export const VALIDATORS = new InjectionToken<Validator[]>('validators');import { ApplicationConfig } from '@angular/core';
import { VALIDATORS } from './validators.tokens';
const requiredValidator = {
validate: (value: string) => value ? null : 'Field is required'
};
const minLengthValidator = {
validate: (value: string) => value.length >= 3 ? null : 'Minimum 3 characters'
};
const emailValidator = {
validate: (value: string) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? null : 'Invalid email format'
};
export const appConfig: ApplicationConfig = {
providers: [
{ provide: VALIDATORS, useValue: requiredValidator, multi: true },
{ provide: VALIDATORS, useValue: minLengthValidator, multi: true },
{ provide: VALIDATORS, useValue: emailValidator, multi: true }
]
};import { Injectable, inject } from '@angular/core';
import { VALIDATORS, Validator } from './validators.tokens';
@Injectable({ providedIn: 'root' })
export class ValidationService {
private readonly validators = inject(VALIDATORS);
validate(value: string): string[] {
// validators is an array of all registered validators
return this.validators
.map(v => v.validate(value))
.filter((error): error is string => error !== null);
}
}Flag multi: true memberitahu Angular untuk mengumpulkan semua provider untuk token ini ke dalam array. Tanpanya, provider selanjutnya akan menimpa yang sebelumnya.
Pertanyaan Interview tentang Angular Dependency Injection
Technical interview sering menguji pemahaman tentang sistem DI Angular. Berikut pertanyaan yang membedakan kandidat dengan pengalaman produksi.
Q: Apa yang terjadi ketika Anda menyediakan service yang sama di level module dan component?
Provider level komponen membuat instance terpisah yang di-scope ke subtree komponen tersebut. Service yang di-inject di subtree tersebut menerima instance komponen, bukan singleton level module. Ini memungkinkan isolasi state, misalnya ketika setiap tab memerlukan state form-nya sendiri.
Q: Mengapa menggunakan InjectionToken daripada string literal?
String token berisiko collision di seluruh library atau bagian aplikasi yang berbeda. InjectionToken membuat referensi runtime unik yang tidak bisa konflik. Parameter tipe generic juga menyediakan keamanan tipe compile-time yang tidak dimiliki string token.
Q: Kapan inject() throw vs return undefined?
Secara default, inject() throw ketika dependensi tidak ditemukan. Memberikan { optional: true } mengubah return type menjadi T | null dan mengembalikan null alih-alih throw. Ini sesuai dengan perilaku decorator @Optional().
Q: Jelaskan perbedaan antara providedIn: 'root' dan providing di array providers module.
Keduanya membuat singleton, tetapi providedIn: 'root' memungkinkan tree-shaking. Service hanya disertakan dalam bundle jika benar-benar di-inject di suatu tempat. Provider level module selalu disertakan terlepas dari penggunaan.
Q: Bagaimana lazy loading mempengaruhi scope service?
Lazy-loaded module mendapatkan child EnvironmentInjector mereka sendiri. Service yang disediakan di lazy module di-scope ke module tersebut dan children-nya. Service dengan providedIn: 'root' tetap menjadi singleton sejati di semua module, lazy maupun tidak.
Untuk latihan pertanyaan tentang service Angular dan pola DI, kunjungi pertanyaan interview Angular tentang service dan dependency injection.
Pola Praktis: Konfigurasi dan Feature Flag
Aplikasi nyata menggabungkan konsep DI ini untuk manajemen konfigurasi. Pola ini menggunakan InjectionToken, factory provider, dan kesadaran environment.
import { InjectionToken, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
export interface FeatureFlags {
newCheckout: boolean;
darkMode: boolean;
betaFeatures: boolean;
}
export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>('feature.flags', {
providedIn: 'root',
factory: () => {
const platformId = inject(PLATFORM_ID);
if (!isPlatformBrowser(platformId)) {
// SSR: return safe defaults
return { newCheckout: false, darkMode: false, betaFeatures: false };
}
// Browser: check localStorage or remote config
const stored = localStorage.getItem('featureFlags');
if (stored) {
return JSON.parse(stored);
}
return { newCheckout: true, darkMode: false, betaFeatures: false };
}
});import { Directive, Input, TemplateRef, ViewContainerRef, inject } from '@angular/core';
import { FEATURE_FLAGS } from './feature-flags.config';
@Directive({
selector: '[appFeatureFlag]',
standalone: true
})
export class FeatureFlagDirective {
private readonly flags = inject(FEATURE_FLAGS);
private readonly templateRef = inject(TemplateRef<unknown>);
private readonly viewContainer = inject(ViewContainerRef);
@Input() set appFeatureFlag(flag: keyof typeof this.flags) {
if (this.flags[flag]) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}Directive ini secara kondisional merender konten berdasarkan feature flag, dengan konfigurasi flag tersentralisasi dalam satu injectable token.
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Praktik Angular DI Production-Ready
- Gunakan
providedIn: 'root'untuk singleton application-wide yang mendapat manfaat dari tree-shaking - Lebih pilih
inject()daripada constructor injection di Angular 20+ untuk sintaks yang lebih bersih dan kompatibilitas fungsional - Scope stateful service ke komponen ketika isolasi diperlukan, bukan di level module
- Buat
InjectionTokenuntuk dependensi non-class untuk memastikan keamanan tipe dan menghindari collision - Terapkan
@Optional()ketika dependensi mungkin tidak ada, terutama untuk plugin atau fitur opsional - Test komponen dengan overridden provider menggunakan
TestBed.overrideComponent()untuk isolasi - Gunakan multi-provider untuk pola extensibility seperti validator, interceptor, dan handler
Bisakah kamu menemukan bug di Angular?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri SharpSkill
Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.
Diperbarui 13 September 2026
Tag
Bagikan
Artikel terkait

Sintaks Control Flow Angular di 2026: @if, @for, @switch dan Pertanyaan Wawancara
Pelajari sintaks control flow Angular (@if, @for, @switch) yang menggantikan directive struktural. Panduan lengkap dengan contoh kode, tips migrasi, dan pertanyaan wawancara teknis.

Angular Signals dan Computed di 2026: Reaktivitas Fine-Grained dan Pertanyaan Interview
Pelajari Angular Signals dan computed signals untuk reaktivitas fine-grained di Angular 20+. Panduan lengkap mencakup signal primitives, linkedSignal, interoperabilitas RxJS, dan pertanyaan interview teknis.

Angular HttpClient dan Interceptor di 2026: Penanganan Request dan Pertanyaan Interview
Pelajari cara menggunakan Angular HttpClient dan functional interceptor untuk menangani HTTP request, autentikasi token, retry logic, dan caching. Dilengkapi pertanyaan interview Angular yang sering muncul.