# Top 25 Pertanyaan Wawancara Angular: Panduan Lengkap untuk Sukses > 25 pertanyaan wawancara Angular paling sering ditanyakan pada 2026. Jawaban detail, contoh kode, dan kiat untuk meraih posisi developer Angular. - Published: 2026-02-04 - Updated: 2026-04-27 - Author: SharpSkill - Tags: angular interview, frontend interview, angular questions, typescript, technical interview - Reading time: 16 min --- Wawancara teknis Angular mengukur pemahaman terhadap arsitektur framework, penguasaan TypeScript, dan praktik terbaik pengembangan frontend. Panduan ini menyajikan 25 pertanyaan yang paling sering ditanyakan beserta jawaban detail dan contoh kode untuk persiapan yang optimal. > **Tips persiapan** > > Pertanyaan-pertanyaan ini mencakup versi Angular terbaru (16+), termasuk Signals, komponen standalone, dan control flow baru. Menguasai konsep modern ini menunjukkan pemantauan teknologi yang aktif. ## Dasar Angular ### 1. Apa perbedaan antara Angular dan AngularJS? Angular (versi 2+) adalah penulisan ulang lengkap dari AngularJS. Perbedaan utama terletak pada arsitektur, bahasa, dan performa. AngularJS menggunakan JavaScript dan pola MVC dengan sistem two-way binding yang dapat menimbulkan masalah performa. Angular menggunakan TypeScript, arsitektur berbasis komponen, dan sistem change detection yang dioptimalkan. ```typescript // AngularJS (1.x) - Controller-based // angular.module('app').controller('UserController', function($scope) { // $scope.user = { name: 'Alice' }; // }); // Angular (2+) - Component-based // user.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-user', standalone: true, template: `

{{ user.name }}

{{ user.email }}

` }) export class UserComponent { // Strong typing with TypeScript user = { name: 'Alice', email: 'alice@example.com' }; } ``` Angular juga menghadirkan dukungan native untuk modul ES6, tooling yang lebih baik melalui Angular CLI, serta arsitektur yang lebih cocok untuk aplikasi enterprise. ### 2. Apa itu komponen Angular dan bagaimana cara membuatnya? Komponen Angular adalah kelas TypeScript yang dihias dengan `@Component`. Komponen ini membungkus logika, template, dan style dari sebagian antarmuka pengguna. ```typescript // product-card.component.ts import { Component, Input, Output, EventEmitter } from '@angular/core'; import { CommonModule } from '@angular/common'; // Interface for strong typing interface Product { id: number; name: string; price: number; inStock: boolean; } @Component({ // CSS selector to use the component selector: 'app-product-card', // Standalone = no NgModule needed standalone: true, // Dependency imports imports: [CommonModule], // Inline or external template (templateUrl) template: `

{{ product.name }}

{{ product.price | currency:'USD' }}

@if (product.inStock) { } @else { Out of Stock }
`, // Encapsulated styles by default styles: [` .product-card { padding: 1rem; border: 1px solid #e0e0e0; } .price { font-weight: bold; color: #2563eb; } .out-of-stock { color: #dc2626; } `] }) export class ProductCardComponent { // Input: data from parent @Input({ required: true }) product!: Product; // Output: events to parent @Output() addToCart = new EventEmitter(); onAddToCart() { this.addToCart.emit(this.product.id); } } ``` Komponen standalone (Angular 14+) menyederhanakan pembuatan dengan menghilangkan kebutuhan mendeklarasikan komponen di dalam NgModule. ### 3. Jelaskan siklus hidup komponen Angular Angular menyediakan lifecycle hook yang memungkinkan eksekusi kode pada momen tertentu dalam siklus hidup komponen. ```typescript // lifecycle-demo.component.ts import { Component, OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy, Input, SimpleChanges } from '@angular/core'; @Component({ selector: 'app-lifecycle-demo', standalone: true, template: `

{{ data }}

` }) export class LifecycleDemoComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy { @Input() data = ''; // 1. Called when an @Input changes (before ngOnInit) ngOnChanges(changes: SimpleChanges) { console.log('ngOnChanges', changes); } // 2. Called once after the first ngOnChanges // Ideal for initializations ngOnInit() { console.log('ngOnInit - Component initialization'); } // 3. Called on every change detection cycle // Use with caution (performance) ngDoCheck() { console.log('ngDoCheck'); } // 4. After content projection (ng-content) ngAfterContentInit() { console.log('ngAfterContentInit'); } // 5. After each projected content check ngAfterContentChecked() { console.log('ngAfterContentChecked'); } // 6. After component view initialization // @ViewChild references are available here ngAfterViewInit() { console.log('ngAfterViewInit - View initialized'); } // 7. After each view check ngAfterViewChecked() { console.log('ngAfterViewChecked'); } // 8. Just before component destruction // Cleanup: unsubscribe, clearInterval, etc. ngOnDestroy() { console.log('ngOnDestroy - Cleanup'); } } ``` Hook yang paling sering dipakai adalah `ngOnInit` untuk inisialisasi, `ngOnChanges` untuk merespons perubahan input, dan `ngOnDestroy` untuk membersihkan resource. ### 4. Apa itu Data Binding di Angular? Data binding menghubungkan data komponen dengan template. Angular menyediakan empat bentuk binding. ```typescript // data-binding.component.ts import { Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; @Component({ selector: 'app-data-binding', standalone: true, imports: [FormsModule], template: `

{{ title }}

{{ getFullName() }}

Hello, {{ username }}

` }) export class DataBindingComponent { // Properties for interpolation title = 'My Application'; firstName = 'John'; lastName = 'Doe'; // Properties for property binding imageUrl = '/assets/logo.png'; imageAlt = 'Application logo'; isLoading = false; // Property for two-way binding username = ''; getFullName(): string { return `${this.firstName} ${this.lastName}`; } handleClick(): void { console.log('Button clicked'); } onEnter(event: KeyboardEvent): void { const target = event.target as HTMLInputElement; console.log('Entered value:', target.value); } } ``` Two-way binding `[(ngModel)]` adalah kombinasi dari property binding dan event binding yang memungkinkan sinkronisasi otomatis antara model dan view. ### 5. Apa perbedaan antara Module dan Standalone Component? NgModules mengelompokkan komponen, directive, dan service yang saling terkait. Komponen standalone (Angular 14+) memungkinkan pembuatan komponen mandiri tanpa modul. ```typescript // Traditional approach with NgModule // products.module.ts import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ProductListComponent } from './product-list.component'; import { ProductCardComponent } from './product-card.component'; import { ProductService } from './product.service'; @NgModule({ // Components belonging to this module declarations: [ ProductListComponent, ProductCardComponent ], // Modules needed imports: [CommonModule], // Components usable outside exports: [ProductListComponent], // Services with module scope providers: [ProductService] }) export class ProductsModule {} // Modern approach with Standalone Components // product-list.component.ts import { Component, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ProductCardComponent } from './product-card.component'; import { ProductService } from './product.service'; @Component({ selector: 'app-product-list', // No NgModule needed standalone: true, // Direct dependency imports imports: [CommonModule, ProductCardComponent], template: ` @for (product of products(); track product.id) { } ` }) export class ProductListComponent { // Modern injection with inject() private productService = inject(ProductService); products = this.productService.getProducts(); } ``` Komponen standalone mengurangi kompleksitas, memperbaiki tree-shaking, dan menyederhanakan lazy loading. Pendekatan ini direkomendasikan untuk proyek baru. ## Service dan Dependency Injection ### 6. Bagaimana cara kerja dependency injection di Angular? Dependency injection (DI) adalah pola desain inti di Angular. Framework mengelola pembuatan dan penyediaan instance service. ```typescript // user.service.ts import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, BehaviorSubject } from 'rxjs'; interface User { id: number; name: string; email: string; } // providedIn: 'root' = singleton at application level @Injectable({ providedIn: 'root' }) export class UserService { // Modern injection with inject() private http = inject(HttpClient); // Shared reactive state private currentUser$ = new BehaviorSubject(null); // Public API getUsers(): Observable { return this.http.get('/api/users'); } getUserById(id: number): Observable { return this.http.get(`/api/users/${id}`); } setCurrentUser(user: User): void { this.currentUser$.next(user); } getCurrentUser(): Observable { return this.currentUser$.asObservable(); } } // Usage in a component // user-profile.component.ts @Component({ selector: 'app-user-profile', standalone: true, template: ` @if (user$ | async; as user) {

{{ user.name }}

{{ user.email }}

} ` }) export class UserProfileComponent implements OnInit { // Injection via inject() (recommended) private userService = inject(UserService); user$!: Observable; ngOnInit() { this.user$ = this.userService.getCurrentUser(); } } ``` Berbagai level provisioning (`providedIn: 'root'`, level modul, atau level komponen) memungkinkan pengaturan scope dan siklus hidup service. ### 7. Apa perbedaan antara providedIn root, any, dan platform? Opsi `providedIn` mengontrol cara Angular membuat dan membagikan instance service. ```typescript // 1. providedIn: 'root' - Application-level singleton // Single instance shared across the entire application @Injectable({ providedIn: 'root' }) export class AuthService { private isAuthenticated = false; login() { this.isAuthenticated = true; } logout() { this.isAuthenticated = false; } isLoggedIn() { return this.isAuthenticated; } } // 2. providedIn: 'any' - Instance per lazy-loaded module // Each lazy-loaded module gets its own instance @Injectable({ providedIn: 'any' }) export class FeatureLoggerService { private logs: string[] = []; log(message: string) { this.logs.push(`[${new Date().toISOString()}] ${message}`); } } // 3. providedIn: 'platform' - Shared between applications // Useful for micro-frontends or multi-app setups @Injectable({ providedIn: 'platform' }) export class SharedConfigService { readonly apiUrl = 'https://api.example.com'; } // 4. Component-level provision - Instance per component @Component({ selector: 'app-editor', standalone: true, // Each component instance has its own service instance providers: [EditorStateService], template: `...` }) export class EditorComponent { private editorState = inject(EditorStateService); } ``` Untuk sebagian besar kasus, `providedIn: 'root'` sudah cukup dan optimal untuk tree-shaking. > **Tree-shaking service** > > Dengan `providedIn: 'root'`, Angular dapat menghapus service yang tidak terpakai dari bundle final. Hal ini meningkatkan performa pemuatan. ### 8. Bagaimana cara menggunakan Observable dengan RxJS di Angular? RxJS adalah library pemrograman reaktif yang digunakan Angular untuk menangani aliran data asinkron. ```typescript // search.service.ts import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, Subject, debounceTime, distinctUntilChanged, switchMap, catchError, of, map, tap } from 'rxjs'; interface SearchResult { id: number; title: string; description: string; } @Injectable({ providedIn: 'root' }) export class SearchService { private http = inject(HttpClient); search(term: string): Observable { return this.http.get(`/api/search?q=${term}`); } } // search.component.ts @Component({ selector: 'app-search', standalone: true, imports: [CommonModule, ReactiveFormsModule], template: ` @if (isLoading) {
Loading...
}
    @for (result of results$ | async; track result.id) {
  • {{ result.title }}
  • }
` }) export class SearchComponent implements OnInit, OnDestroy { private searchService = inject(SearchService); private destroy$ = new Subject(); searchControl = new FormControl(''); results$!: Observable; isLoading = false; ngOnInit() { this.results$ = this.searchControl.valueChanges.pipe( // Wait 300ms after the last keystroke debounceTime(300), // Ignore if value hasn't changed distinctUntilChanged(), // Show loading tap(() => this.isLoading = true), // Cancel previous request and launch new one switchMap(term => term ? this.searchService.search(term) : of([]) ), // Hide loading tap(() => this.isLoading = false), // Handle errors catchError(error => { console.error('Search error:', error); this.isLoading = false; return of([]); }) ); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } } ``` Operator RxJS seperti `debounceTime`, `distinctUntilChanged`, dan `switchMap` sangat penting untuk mengoptimalkan pencarian dan menghindari request yang tidak perlu. ## Angular modern (16+) ### 9. Apa itu Signals di Angular dan bagaimana cara menggunakannya? Signals (Angular 16+) memperkenalkan primitif reaktif baru yang lebih sederhana dan lebih cepat dibanding Observable untuk state lokal. ```typescript // counter.component.ts import { Component, signal, computed, effect } from '@angular/core'; @Component({ selector: 'app-counter', standalone: true, template: `

Counter: {{ count() }}

Double: {{ doubleCount() }}

Message: {{ message() }}

` }) export class CounterComponent { // Writable signal - modifiable value count = signal(0); // Computed signal - automatically derived // Recalculated only when count() changes doubleCount = computed(() => this.count() * 2); // Computed with conditional logic message = computed(() => { const value = this.count(); if (value < 0) return 'Negative value'; if (value === 0) return 'Zero'; if (value < 10) return 'Small number'; return 'Large number'; }); constructor() { // Effect - executed on every change of used signals // Useful for side effects (logs, localStorage, etc.) effect(() => { console.log(`New value: ${this.count()}`); localStorage.setItem('counter', String(this.count())); }); } increment() { // update() to modify based on previous value this.count.update(value => value + 1); } decrement() { this.count.update(value => value - 1); } reset() { // set() to directly set a value this.count.set(0); } } ``` Signals menawarkan performa lebih baik berkat change detection yang lebih granular dan API yang lebih intuitif dibanding RxJS untuk state lokal. ### 10. Bagaimana Signal Inputs dan Outputs bekerja? Angular 17+ menghadirkan `input()` dan `output()` sebagai alternatif berbasis signal untuk decorator `@Input()` dan `@Output()`. ```typescript // task-item.component.ts import { Component, input, output, computed } from '@angular/core'; interface Task { id: number; title: string; completed: boolean; priority: 'low' | 'medium' | 'high'; } @Component({ selector: 'app-task-item', standalone: true, template: `
{{ task().title }} {{ task().priority }} @if (showActions()) { }
` }) export class TaskItemComponent { // Required input - must be provided by parent task = input.required(); // Optional input with default value showActions = input(true); // Signal-based output toggle = output(); delete = output(); // Computed based on input priorityClass = computed(() => `priority-${this.task().priority}`); onToggle() { this.toggle.emit(this.task().id); } onDelete() { this.delete.emit(this.task().id); } } // Usage in parent // task-list.component.ts @Component({ selector: 'app-task-list', standalone: true, imports: [TaskItemComponent], template: ` @for (task of tasks(); track task.id) { } ` }) export class TaskListComponent { tasks = signal([ { id: 1, title: 'Learn Angular', completed: false, priority: 'high' }, { id: 2, title: 'Build a project', completed: false, priority: 'medium' } ]); canEdit = signal(true); onToggleTask(id: number) { this.tasks.update(tasks => tasks.map(t => t.id === id ? { ...t, completed: !t.completed } : t) ); } onDeleteTask(id: number) { this.tasks.update(tasks => tasks.filter(t => t.id !== id)); } } ``` Signal input memberikan typing yang lebih baik, API yang lebih konsisten dengan signal lainnya, dan mempersiapkan transisi ke zoneless change detection. ### 11. Jelaskan Control Flow baru di Angular 17+ Angular 17 memperkenalkan sintaks control flow baru yang terintegrasi pada template, menggantikan directive struktural `*ngIf`, `*ngFor`, dan `*ngSwitch`. ```typescript // modern-control-flow.component.ts import { Component, signal } from '@angular/core'; interface User { id: number; name: string; role: 'admin' | 'user' | 'guest'; status: 'active' | 'inactive' | 'pending'; } @Component({ selector: 'app-modern-control-flow', standalone: true, template: ` @if (isLoading()) {
Loading...
} @else if (error()) {
{{ error() }}
} @else {

Users ({{ users().length }})

@for (user of users(); track user.id; let i = $index, first = $first, last = $last) {
{{ i + 1 }}. {{ user.name }} @switch (user.role) { @case ('admin') { Administrator } @case ('user') { User } @default { Guest } }
} @empty {

No users found

}
} ` }) export class ModernControlFlowComponent { isLoading = signal(false); error = signal(null); users = signal([ { id: 1, name: 'Alice', role: 'admin', status: 'active' }, { id: 2, name: 'Bob', role: 'user', status: 'active' }, { id: 3, name: 'Charlie', role: 'guest', status: 'pending' } ]); } ``` Sintaks baru ini memberikan performa yang lebih baik berkat kompilasi yang dioptimalkan, keterbacaan yang lebih baik, dan fitur seperti `@empty` untuk `@for`. ### 12. Bagaimana cara mengonfigurasi Lazy Loading dengan komponen standalone? Lazy loading memuat modul atau komponen sesuai kebutuhan, sehingga waktu pemuatan awal menjadi lebih singkat. ```typescript // app.routes.ts import { Routes } from '@angular/router'; export const routes: Routes = [ { path: '', // Component loaded immediately loadComponent: () => import('./home/home.component') .then(m => m.HomeComponent) }, { path: 'products', // Lazy loading a standalone component loadComponent: () => import('./products/product-list.component') .then(m => m.ProductListComponent) }, { path: 'admin', // Lazy loading child routes loadChildren: () => import('./admin/admin.routes') .then(m => m.adminRoutes), // Guard for authentication canActivate: [authGuard] }, { path: 'dashboard', loadComponent: () => import('./dashboard/dashboard.component') .then(m => m.DashboardComponent), // Inline child routes children: [ { path: 'stats', loadComponent: () => import('./dashboard/stats/stats.component') .then(m => m.StatsComponent) }, { path: 'settings', loadComponent: () => import('./dashboard/settings/settings.component') .then(m => m.SettingsComponent) } ] } ]; // admin/admin.routes.ts import { Routes } from '@angular/router'; export const adminRoutes: Routes = [ { path: '', loadComponent: () => import('./admin-layout.component') .then(m => m.AdminLayoutComponent), children: [ { path: 'users', loadComponent: () => import('./users/users.component') .then(m => m.UsersComponent) }, { path: 'reports', loadComponent: () => import('./reports/reports.component') .then(m => m.ReportsComponent) } ] } ]; // main.ts - Bootstrap with routes import { bootstrapApplication } from '@angular/platform-browser'; import { provideRouter } from '@angular/router'; import { AppComponent } from './app/app.component'; import { routes } from './app/app.routes'; bootstrapApplication(AppComponent, { providers: [ provideRouter(routes) ] }); ``` Dengan komponen standalone, lazy loading menjadi lebih sederhana dan lebih granular dibanding dengan NgModules. ## Form Angular ### 13. Apa perbedaan antara Template-driven dan Reactive Forms? Angular menyediakan dua pendekatan untuk menangani form, masing-masing cocok untuk kasus penggunaan yang berbeda. ```typescript // TEMPLATE-DRIVEN FORMS // Simple, declarative, suited for basic forms // template-form.component.ts import { Component } from '@angular/core'; import { FormsModule, NgForm } from '@angular/forms'; @Component({ selector: 'app-template-form', standalone: true, imports: [FormsModule], template: `
@if (emailField.invalid && emailField.touched) { Invalid email } @if (passwordField.errors?.['minlength']) { Minimum 8 characters }
` }) export class TemplateFormComponent { user = { email: '', password: '' }; onSubmit(form: NgForm) { if (form.valid) { console.log('Form data:', this.user); } } } // REACTIVE FORMS // More control, testable, suited for complex forms // reactive-form.component.ts import { Component, inject } from '@angular/core'; import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms'; @Component({ selector: 'app-reactive-form', standalone: true, imports: [ReactiveFormsModule], template: `
@if (loginForm.get('email')?.hasError('required') && loginForm.get('email')?.touched) { Email required } @if (loginForm.get('email')?.hasError('email')) { Invalid email format } @if (loginForm.get('password')?.hasError('minlength')) { Minimum 8 characters }
` }) export class ReactiveFormComponent { private fb = inject(FormBuilder); // Programmatic form definition loginForm = this.fb.group({ email: ['', [Validators.required, Validators.email]], password: ['', [Validators.required, Validators.minLength(8)]] }); onSubmit() { if (this.loginForm.valid) { console.log('Form data:', this.loginForm.value); } } } ``` Reactive Forms direkomendasikan untuk form yang kompleks: lebih fleksibel, lebih mudah diuji, dan memungkinkan reuse logika validasi yang lebih baik. ### 14. Bagaimana cara membuat validator kustom? Validator kustom memungkinkan penerapan aturan validasi bisnis yang spesifik. ```typescript // validators/custom.validators.ts import { AbstractControl, ValidationErrors, ValidatorFn, AsyncValidatorFn } from '@angular/forms'; import { Observable, of, map, delay } from 'rxjs'; // Synchronous validator - checks immediately export function forbiddenNameValidator(forbiddenName: RegExp): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const forbidden = forbiddenName.test(control.value); return forbidden ? { forbiddenName: { value: control.value } } : null; }; } // Password confirmation validator export function passwordMatchValidator(): ValidatorFn { return (group: AbstractControl): ValidationErrors | null => { const password = group.get('password')?.value; const confirmPassword = group.get('confirmPassword')?.value; return password === confirmPassword ? null : { passwordMismatch: true }; }; } // Asynchronous validator - checks via API export function uniqueEmailValidator(userService: UserService): AsyncValidatorFn { return (control: AbstractControl): Observable => { if (!control.value) { return of(null); } return userService.checkEmailExists(control.value).pipe( map(exists => exists ? { emailTaken: true } : null) ); }; } // Usage in a component // registration.component.ts import { Component, inject } from '@angular/core'; import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms'; import { forbiddenNameValidator, passwordMatchValidator, uniqueEmailValidator } from './validators/custom.validators'; import { UserService } from './user.service'; @Component({ selector: 'app-registration', standalone: true, imports: [ReactiveFormsModule], template: `
@if (registrationForm.get('username')?.hasError('forbiddenName')) { This name is not allowed }
@if (registrationForm.get('email')?.pending) { Checking... } @if (registrationForm.get('email')?.hasError('emailTaken')) { This email is already in use }
@if (registrationForm.get('passwords')?.hasError('passwordMismatch')) { Passwords do not match }
` }) export class RegistrationComponent { private fb = inject(FormBuilder); private userService = inject(UserService); registrationForm = this.fb.group({ username: ['', [ Validators.required, forbiddenNameValidator(/admin/i) ]], email: ['', [Validators.required, Validators.email], [uniqueEmailValidator(this.userService)] ], passwords: this.fb.group({ password: ['', [Validators.required, Validators.minLength(8)]], confirmPassword: ['', Validators.required] }, { validators: passwordMatchValidator() }) }); onSubmit() { if (this.registrationForm.valid) { console.log(this.registrationForm.value); } } } ``` Validator asinkron sangat berguna untuk pengecekan di sisi server, seperti memastikan keunikan email atau username. ## Routing dan Navigasi ### 15. Bagaimana cara melindungi route dengan Guards? Guards mengontrol akses ke route berdasarkan kondisi seperti autentikasi atau hak akses. ```typescript // guards/auth.guard.ts import { inject } from '@angular/core'; import { Router, CanActivateFn, CanMatchFn } from '@angular/router'; import { AuthService } from '../services/auth.service'; // Functional guard (recommended since Angular 15+) export const authGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); const router = inject(Router); if (authService.isAuthenticated()) { return true; } // Redirect to login page with return URL return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } }); }; // Role-based guard export const roleGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); const router = inject(Router); const requiredRoles = route.data['roles'] as string[]; const userRole = authService.getUserRole(); if (requiredRoles.includes(userRole)) { return true; } return router.createUrlTree(['/unauthorized']); }; // Guard for lazy loading (canMatch) export const featureGuard: CanMatchFn = (route, segments) => { const featureService = inject(FeatureService); return featureService.isFeatureEnabled(route.path || ''); }; // Route configuration with guards // app.routes.ts export const routes: Routes = [ { path: 'login', loadComponent: () => import('./login.component') }, { path: 'dashboard', loadComponent: () => import('./dashboard.component'), canActivate: [authGuard] }, { path: 'admin', loadChildren: () => import('./admin/admin.routes'), canActivate: [authGuard, roleGuard], canMatch: [featureGuard], data: { roles: ['admin', 'superadmin'] } }, { path: 'settings', loadComponent: () => import('./settings.component'), canActivate: [authGuard], // Guard for leaving page (unsaved data) canDeactivate: [unsavedChangesGuard] } ]; // Guard for unsaved changes export const unsavedChangesGuard: CanDeactivateFn<{ hasUnsavedChanges: () => boolean }> = (component) => { if (component.hasUnsavedChanges()) { return confirm('Unsaved changes will be lost. Continue?'); } return true; }; ``` Functional guard lebih sederhana dan terintegrasi lebih baik dengan dependency injection modern. ### 16. Bagaimana cara mengoper data antar route? Angular menyediakan beberapa metode untuk mengirim data selama navigasi. ```typescript // 1. Route Parameters (in URL) // app.routes.ts export const routes: Routes = [ { path: 'products/:id', loadComponent: () => import('./product-detail.component') } ]; // product-detail.component.ts import { Component, inject, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-product-detail', standalone: true, template: `

Product {{ productId }}

` }) export class ProductDetailComponent implements OnInit { private route = inject(ActivatedRoute); productId!: string; ngOnInit() { // Access route parameters this.productId = this.route.snapshot.paramMap.get('id')!; // Or reactively this.route.paramMap.subscribe(params => { this.productId = params.get('id')!; }); } } // 2. Query Parameters (?key=value) // navigation.component.ts import { Component, inject } from '@angular/core'; import { Router } from '@angular/router'; @Component({ selector: 'app-navigation', standalone: true, template: ` ` }) export class NavigationComponent { private router = inject(Router); navigateWithQuery() { this.router.navigate(['/products'], { queryParams: { category: 'electronics', sort: 'price' } }); } } // 3. State (data not visible in URL) // order-confirmation.component.ts @Component({ selector: 'app-order-confirmation', standalone: true, template: ` @if (orderData) {

Order #{{ orderData.orderId }} confirmed

} ` }) export class OrderConfirmationComponent implements OnInit { private route = inject(ActivatedRoute); orderData: any; ngOnInit() { // Retrieve state passed via navigation this.orderData = history.state; } } // Navigate with state this.router.navigate(['/order-confirmation'], { state: { orderId: '12345', total: 99.99 } }); // 4. Resolvers (data pre-loading) // product.resolver.ts import { inject } from '@angular/core'; import { ResolveFn } from '@angular/router'; import { ProductService } from './product.service'; export const productResolver: ResolveFn = (route) => { const productService = inject(ProductService); const id = route.paramMap.get('id')!; return productService.getProduct(id); }; // Route with resolver { path: 'products/:id', loadComponent: () => import('./product-detail.component'), resolve: { product: productResolver } } // Access resolved data ngOnInit() { this.product = this.route.snapshot.data['product']; } ``` > **State dan refresh** > > Data yang dikirim melalui `state` akan hilang ketika halaman dimuat ulang. Untuk data yang persisten, sebaiknya gunakan parameter URL atau service penyimpanan. ## Performa dan Optimasi ### 17. Bagaimana cara kerja change detection di Angular? Angular memanfaatkan Zone.js untuk mendeteksi event asinkron dan memicu pemeriksaan komponen. ```typescript // change-detection.component.ts import { Component, ChangeDetectionStrategy, ChangeDetectorRef, inject, signal } from '@angular/core'; // Default strategy: checks entire component tree @Component({ selector: 'app-default-strategy', template: `

{{ data }}

` }) export class DefaultStrategyComponent { data = 'Hello'; } // OnPush strategy: checks only if inputs change // or if an event is triggered within the component @Component({ selector: 'app-onpush-strategy', changeDetection: ChangeDetectionStrategy.OnPush, template: `

{{ data().name }}

` }) export class OnPushStrategyComponent { private cdr = inject(ChangeDetectorRef); // Signal: automatically triggers detection data = signal({ name: 'Alice' }); update() { // With signal, update is automatic this.data.set({ name: 'Bob' }); } // For cases where manual detection is needed manualUpdate() { // Mark component for checking this.cdr.markForCheck(); // Or force immediate detection this.cdr.detectChanges(); } } // Practical example: optimized list @Component({ selector: 'app-optimized-list', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: ` @for (item of items(); track item.id) { } ` }) export class OptimizedListComponent { items = signal([]); addItem(item: Item) { // Create new array to trigger detection this.items.update(current => [...current, item]); } updateItem(id: number, changes: Partial) { this.items.update(current => current.map(item => item.id === id ? { ...item, ...changes } : item ) ); } } ``` Strategi OnPush yang dipadukan dengan Signals memberikan performa terbaik dengan membatasi pemeriksaan hanya pada komponen yang benar-benar berubah. ### 18. Bagaimana cara mengoptimalkan performa aplikasi Angular? Beberapa teknik dapat meningkatkan performa aplikasi Angular. ```typescript // 1. Lazy Loading routes (see question 12) // 2. TrackBy for lists (mandatory with @for) @Component({ template: ` @for (user of users(); track user.id) { } ` }) export class UserListComponent { users = signal([]); } // 3. Pure pipes for transformations // format-date.pipe.ts import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'formatDate', standalone: true, pure: true // Default, recalculated only if input changes }) export class FormatDatePipe implements PipeTransform { transform(value: Date, format: string = 'short'): string { return new Intl.DateTimeFormat('en-US', { dateStyle: format as any }).format(value); } } // 4. Virtual Scrolling for large lists import { Component } from '@angular/core'; import { ScrollingModule } from '@angular/cdk/scrolling'; @Component({ selector: 'app-virtual-list', standalone: true, imports: [ScrollingModule], template: `
{{ item.name }}
`, styles: [` .viewport { height: 400px; } .item { height: 50px; } `] }) export class VirtualListComponent { items = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` })); } // 5. Defer for deferred loading (Angular 17+) @Component({ template: `
Always loaded immediately
@defer (on viewport) { } @placeholder {
Loading...
} @loading (minimum 500ms) { } @defer (on interaction) { } @placeholder { } @defer (on timer(2000ms)) { } ` }) export class OptimizedPageComponent {} ``` Menggabungkan teknik-teknik ini dengan profiling melalui Angular DevTools membantu mengidentifikasi dan mengatasi bottleneck. ## Komunikasi antar komponen ### 19. Apa saja metode komunikasi antar komponen? Angular menawarkan berbagai pola komunikasi tergantung pada hubungan hierarkis antar komponen. ```typescript // 1. Parent → Child: @Input / input() // parent.component.ts @Component({ template: `` }) export class ParentComponent { parentMessage = 'Hello from parent'; } // child.component.ts @Component({ template: `

{{ message() }}

` }) export class ChildComponent { message = input.required(); } // 2. Child → Parent: @Output / output() // child.component.ts @Component({ template: `` }) export class ChildComponent { messageEvent = output(); sendMessage() { this.messageEvent.emit('Hello from child'); } } // parent.component.ts @Component({ template: `` }) export class ParentComponent { onMessage(message: string) { console.log(message); } } // 3. Via shared Service (unrelated components) // message.service.ts @Injectable({ providedIn: 'root' }) export class MessageService { private messageSubject = new Subject(); message$ = this.messageSubject.asObservable(); // Or with Signal currentMessage = signal(''); sendMessage(message: string) { this.messageSubject.next(message); this.currentMessage.set(message); } } // component-a.ts @Component({ template: `` }) export class ComponentA { private messageService = inject(MessageService); send() { this.messageService.sendMessage('Hello from A'); } } // component-b.ts @Component({ template: `

{{ message$ | async }}

` }) export class ComponentB implements OnDestroy { private messageService = inject(MessageService); private destroy$ = new Subject(); message$ = this.messageService.message$; ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } } // 4. ViewChild to access a child @Component({ template: `` }) export class ParentComponent implements AfterViewInit { @ViewChild('timer') timerComponent!: TimerComponent; ngAfterViewInit() { this.timerComponent.start(); } } // 5. ContentChild for projected content @Component({ selector: 'app-card', template: `
` }) export class CardComponent implements AfterContentInit { @ContentChild('header') header!: ElementRef; ngAfterContentInit() { console.log('Header content:', this.header); } } ``` Pemilihan metode bergantung pada hubungan antar komponen dan kompleksitas komunikasinya. ### 20. Bagaimana cara mengimplementasikan state management dengan Signals? Signals memungkinkan pembuatan store reaktif sederhana tanpa library eksternal. ```typescript // store/cart.store.ts import { Injectable, signal, computed } from '@angular/core'; interface CartItem { id: number; name: string; price: number; quantity: number; } interface CartState { items: CartItem[]; loading: boolean; error: string | null; } @Injectable({ providedIn: 'root' }) export class CartStore { // Private state private state = signal({ items: [], loading: false, error: null }); // Public selectors (read-only) readonly items = computed(() => this.state().items); readonly loading = computed(() => this.state().loading); readonly error = computed(() => this.state().error); readonly itemCount = computed(() => this.state().items.reduce((sum, item) => sum + item.quantity, 0) ); readonly total = computed(() => this.state().items.reduce( (sum, item) => sum + item.price * item.quantity, 0 ) ); readonly isEmpty = computed(() => this.state().items.length === 0); // Actions addItem(product: Omit) { this.state.update(state => { const existingItem = state.items.find(i => i.id === product.id); if (existingItem) { return { ...state, items: state.items.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item ) }; } return { ...state, items: [...state.items, { ...product, quantity: 1 }] }; }); } removeItem(id: number) { this.state.update(state => ({ ...state, items: state.items.filter(item => item.id !== id) })); } updateQuantity(id: number, quantity: number) { if (quantity <= 0) { this.removeItem(id); return; } this.state.update(state => ({ ...state, items: state.items.map(item => item.id === id ? { ...item, quantity } : item ) })); } clearCart() { this.state.update(state => ({ ...state, items: [] })); } // Async action async checkout() { this.state.update(s => ({ ...s, loading: true, error: null })); try { // API call await fetch('/api/checkout', { method: 'POST', body: JSON.stringify({ items: this.items() }) }); this.clearCart(); } catch (e) { this.state.update(s => ({ ...s, error: 'Order error' })); } finally { this.state.update(s => ({ ...s, loading: false })); } } } // Usage in a component // cart.component.ts @Component({ selector: 'app-cart', standalone: true, template: ` @if (cartStore.isEmpty()) {

Your cart is empty

} @else { @for (item of cartStore.items(); track item.id) {
{{ item.name }} {{ item.price }} $ × {{ item.quantity }}
}
Total: {{ cartStore.total() }} $ ({{ cartStore.itemCount() }} items)
} ` }) export class CartComponent { cartStore = inject(CartStore); } ``` Pola ini menghadirkan state management yang dapat diprediksi dan reaktif tanpa kompleksitas NgRx, cocok untuk aplikasi berskala menengah. ## Pengujian Angular ### 21. Bagaimana cara menguji komponen Angular? Pengujian komponen memverifikasi rendering, interaksi, dan integrasi dengan service. ```typescript // user-card.component.spec.ts import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { UserCardComponent } from './user-card.component'; describe('UserCardComponent', () => { let component: UserCardComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [UserCardComponent] }).compileComponents(); fixture = TestBed.createComponent(UserCardComponent); component = fixture.componentInstance; }); it('should create', () => { expect(component).toBeTruthy(); }); it('should display user name', () => { // Arrange fixture.componentRef.setInput('user', { name: 'Alice', email: 'alice@test.com' }); // Act fixture.detectChanges(); // Assert const nameElement = fixture.debugElement.query(By.css('.user-name')); expect(nameElement.nativeElement.textContent).toContain('Alice'); }); it('should emit event when delete button clicked', () => { // Arrange fixture.componentRef.setInput('user', { id: 1, name: 'Alice' }); fixture.detectChanges(); const deleteSpy = jest.spyOn(component.delete, 'emit'); // Act const deleteButton = fixture.debugElement.query(By.css('.delete-btn')); deleteButton.triggerEventHandler('click', null); // Assert expect(deleteSpy).toHaveBeenCalledWith(1); }); it('should show loading state', () => { // Arrange fixture.componentRef.setInput('isLoading', true); // Act fixture.detectChanges(); // Assert const spinner = fixture.debugElement.query(By.css('.spinner')); expect(spinner).toBeTruthy(); }); }); // Test with mocked service // user-list.component.spec.ts import { of, throwError } from 'rxjs'; describe('UserListComponent', () => { let component: UserListComponent; let fixture: ComponentFixture; let mockUserService: jest.Mocked; beforeEach(async () => { mockUserService = { getUsers: jest.fn(), deleteUser: jest.fn() } as any; await TestBed.configureTestingModule({ imports: [UserListComponent], providers: [ { provide: UserService, useValue: mockUserService } ] }).compileComponents(); fixture = TestBed.createComponent(UserListComponent); component = fixture.componentInstance; }); it('should load users on init', () => { // Arrange const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]; mockUserService.getUsers.mockReturnValue(of(users)); // Act fixture.detectChanges(); // Assert expect(mockUserService.getUsers).toHaveBeenCalled(); expect(component.users()).toEqual(users); }); it('should handle error state', () => { // Arrange mockUserService.getUsers.mockReturnValue( throwError(() => new Error('Network error')) ); // Act fixture.detectChanges(); // Assert expect(component.error()).toBe('Loading error'); }); }); ``` ### 22. Bagaimana cara menguji service Angular? Pengujian service memverifikasi logika bisnis dan interaksi dengan API. ```typescript // auth.service.spec.ts import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { let service: AuthService; let httpMock: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [AuthService] }); service = TestBed.inject(AuthService); httpMock = TestBed.inject(HttpTestingController); }); afterEach(() => { // Verify no pending requests httpMock.verify(); }); describe('login', () => { it('should return user on successful login', () => { // Arrange const credentials = { email: 'test@test.com', password: 'password' }; const mockResponse = { id: 1, email: 'test@test.com', token: 'abc123' }; // Act service.login(credentials).subscribe(user => { // Assert expect(user).toEqual(mockResponse); expect(service.isAuthenticated()).toBe(true); }); // Simulate HTTP response const req = httpMock.expectOne('/api/auth/login'); expect(req.request.method).toBe('POST'); expect(req.request.body).toEqual(credentials); req.flush(mockResponse); }); it('should handle login error', () => { // Arrange const credentials = { email: 'test@test.com', password: 'wrong' }; // Act service.login(credentials).subscribe({ error: (error) => { // Assert expect(error.status).toBe(401); expect(service.isAuthenticated()).toBe(false); } }); // Simulate error const req = httpMock.expectOne('/api/auth/login'); req.flush({ message: 'Invalid credentials' }, { status: 401, statusText: 'Unauthorized' }); }); }); describe('logout', () => { it('should clear authentication state', () => { // Arrange - simulate logged in user service['currentUser'].set({ id: 1, email: 'test@test.com' }); // Act service.logout(); // Assert expect(service.isAuthenticated()).toBe(false); expect(service.getCurrentUser()).toBeNull(); }); }); }); ``` ## Pertanyaan Lanjutan ### 23. Bagaimana Server-Side Rendering (SSR) bekerja dengan Angular? Angular Universal memungkinkan rendering di sisi server untuk meningkatkan SEO dan performa yang dirasakan pengguna. ```typescript // SSR Configuration with Angular 17+ // app.config.server.ts import { ApplicationConfig, mergeApplicationConfig } from '@angular/core'; import { provideServerRendering } from '@angular/platform-server'; import { appConfig } from './app.config'; const serverConfig: ApplicationConfig = { providers: [ provideServerRendering() ] }; export const config = mergeApplicationConfig(appConfig, serverConfig); // Handling browser-only APIs // platform.service.ts import { Injectable, PLATFORM_ID, inject } from '@angular/core'; import { isPlatformBrowser, isPlatformServer } from '@angular/common'; @Injectable({ providedIn: 'root' }) export class PlatformService { private platformId = inject(PLATFORM_ID); get isBrowser(): boolean { return isPlatformBrowser(this.platformId); } get isServer(): boolean { return isPlatformServer(this.platformId); } } // SSR-aware component // analytics.component.ts @Component({ selector: 'app-analytics', standalone: true, template: ` @if (platform.isBrowser) {
} ` }) export class AnalyticsComponent implements OnInit { platform = inject(PlatformService); ngOnInit() { // Code executed only on client side if (this.platform.isBrowser) { this.initializeAnalytics(); } } private initializeAnalytics() { // window and document are available console.log('Analytics initialized on', window.location.href); } } // TransferState to avoid duplicate requests // product.service.ts import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { TransferState, makeStateKey } from '@angular/core'; import { of, tap } from 'rxjs'; const PRODUCTS_KEY = makeStateKey('products'); @Injectable({ providedIn: 'root' }) export class ProductService { private http = inject(HttpClient); private transferState = inject(TransferState); private platform = inject(PlatformService); getProducts() { // Client side: check if data already exists if (this.platform.isBrowser) { const cachedProducts = this.transferState.get(PRODUCTS_KEY, null); if (cachedProducts) { this.transferState.remove(PRODUCTS_KEY); return of(cachedProducts); } } return this.http.get('/api/products').pipe( tap(products => { // Server side: store for client if (this.platform.isServer) { this.transferState.set(PRODUCTS_KEY, products); } }) ); } } ``` SSR meningkatkan First Contentful Paint dan memungkinkan mesin pencari mengindeks konten dinamis. ### 24. Bagaimana cara menangani internasionalisasi (i18n) di Angular? Angular menyediakan beberapa pendekatan untuk menginternasionalisasi aplikasi. ```typescript // 1. Built-in Angular i18n (separate compilation per language) // app.component.ts @Component({ template: `

Welcome to our application

{itemCount, plural, =0 {No items} =1 {One item} other {{{itemCount}} items} }

` }) export class AppComponent { itemCount = 5; } // 2. ngx-translate (runtime language switching) // app.config.ts import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; export function HttpLoaderFactory(http: HttpClient) { return new TranslateHttpLoader(http, './assets/i18n/', '.json'); } // Configuration provideTranslateService({ defaultLanguage: 'en', loader: { provide: TranslateLoader, useFactory: HttpLoaderFactory, deps: [HttpClient] } }) // language-switcher.component.ts @Component({ selector: 'app-language-switcher', standalone: true, imports: [TranslateModule], template: `

{{ 'HOME.TITLE' | translate }}

{{ 'HOME.WELCOME' | translate:{ name: userName } }}

` }) export class LanguageSwitcherComponent { private translate = inject(TranslateService); userName = 'Alice'; changeLanguage(event: Event) { const lang = (event.target as HTMLSelectElement).value; this.translate.use(lang); } } // assets/i18n/en.json { "HOME": { "TITLE": "Welcome", "WELCOME": "Hello {{name}}!" } } // assets/i18n/fr.json { "HOME": { "TITLE": "Bienvenue", "WELCOME": "Bonjour {{name}} !" } } ``` Pemilihan antara i18n bawaan dan ngx-translate bergantung pada kebutuhan: kompilasi terpisah untuk performa terbaik atau pergantian dinamis untuk fleksibilitas yang lebih besar. ### 25. Apa saja praktik terbaik untuk menyusun proyek Angular? Struktur yang tertata dengan baik mempermudah pemeliharaan dan skalabilitas proyek. ``` src/ ├── app/ │ ├── core/ # Singleton services, guards, interceptors │ │ ├── guards/ │ │ │ └── auth.guard.ts │ │ ├── interceptors/ │ │ │ └── auth.interceptor.ts │ │ ├── services/ │ │ │ ├── auth.service.ts │ │ │ └── api.service.ts │ │ └── core.provider.ts # Provider configuration │ │ │ ├── shared/ # Reusable components, pipes, directives │ │ ├── components/ │ │ │ ├── button/ │ │ │ └── modal/ │ │ ├── directives/ │ │ ├── pipes/ │ │ └── index.ts # Barrel exports │ │ │ ├── features/ # Feature modules (lazy-loaded) │ │ ├── products/ │ │ │ ├── components/ │ │ │ ├── services/ │ │ │ ├── models/ │ │ │ ├── products.routes.ts │ │ │ └── products.component.ts │ │ ├── cart/ │ │ └── checkout/ │ │ │ ├── layouts/ # Page layouts │ │ ├── main-layout/ │ │ └── auth-layout/ │ │ │ ├── app.component.ts │ ├── app.config.ts │ └── app.routes.ts │ ├── assets/ ├── environments/ └── styles/ ``` ```typescript // Best code practices // 1. Barrel exports to simplify imports // shared/index.ts export * from './components/button/button.component'; export * from './components/modal/modal.component'; export * from './pipes/format-date.pipe'; // 2. Centralized provider configuration // core/core.provider.ts import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { authInterceptor } from './interceptors/auth.interceptor'; export const coreProviders = [ provideHttpClient( withInterceptors([authInterceptor]) ), // Other global providers ]; // 3. Typed models // features/products/models/product.model.ts export interface Product { id: number; name: string; price: number; category: ProductCategory; createdAt: Date; } export type ProductCategory = 'electronics' | 'clothing' | 'books'; export interface CreateProductDto { name: string; price: number; category: ProductCategory; } // 4. Functional interceptor // core/interceptors/auth.interceptor.ts import { HttpInterceptorFn } from '@angular/common/http'; import { inject } from '@angular/core'; export const authInterceptor: HttpInterceptorFn = (req, next) => { const authService = inject(AuthService); const token = authService.getToken(); if (token) { req = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); } return next(req); }; ``` Konvensi ini memungkinkan navigasi kode yang intuitif dan memudahkan kolaborasi tim. ## Kesimpulan 25 pertanyaan ini mencakup konsep-konsep esensial Angular yang muncul dalam wawancara. Poin penting yang harus dikuasai: - ✅ **Dasar**: komponen, data binding, siklus hidup, DI - ✅ **Angular modern**: Signals, komponen standalone, control flow baru - ✅ **Reaktivitas**: RxJS, Observable, state management dengan Signals - ✅ **Form**: Template-driven vs Reactive, validasi kustom - ✅ **Routing**: Guards, lazy loading, pengiriman data - ✅ **Performa**: OnPush, defer, virtual scrolling - ✅ **Pengujian**: unit test, mock, HttpTestingController Mempersiapkan diri untuk wawancara Angular memerlukan latihan rutin. Membangun proyek pribadi membantu memantapkan pengetahuan ini dan menjelaskannya secara alami selama wawancara. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/angular/top-25-angular-interview-questions