# Топ 25 Питань Співбесіди Angular: Повний Посібник до Успіху > 25 найпоширеніших питань на співбесіді з Angular у 2026 році. Детальні відповіді, приклади коду та поради, щоб отримати позицію 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 --- Технічні співбесіди з Angular оцінюють розуміння архітектури фреймворку, рівень володіння TypeScript і найкращі практики frontend-розробки. Цей посібник представляє 25 найпоширеніших питань із детальними відповідями та прикладами коду для оптимальної підготовки. > **Порада для підготовки** > > Ці питання охоплюють останні версії Angular (16+), включно з Signals, standalone-компонентами та новим control flow. Володіння цими сучасними концепціями свідчить про активне відстеження технологій. ## Основи Angular ### 1. Яка різниця між Angular та AngularJS? Angular (версії 2+) — це повне переписування AngularJS. Основні відмінності стосуються архітектури, мови та продуктивності. AngularJS використовував JavaScript і шаблон MVC із системою two-way binding, яка могла спричиняти проблеми з продуктивністю. Angular використовує TypeScript, компонентну архітектуру та оптимізовану систему change detection. ```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 також забезпечує нативну підтримку модулів ES6, кращий тулінг із Angular CLI та архітектуру, краще пристосовану до корпоративних застосунків. ### 2. Що таке компонент Angular і як його створити? Компонент Angular — це клас TypeScript, декорований `@Component`. Він інкапсулює логіку, шаблон і стилі частини інтерфейсу користувача. ```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); } } ``` Standalone-компоненти (Angular 14+) спрощують створення, усуваючи потребу оголошувати компонент у NgModule. ### 3. Поясни життєвий цикл компонента Angular Angular надає lifecycle-хуки, які дозволяють виконувати код у певні моменти життєвого циклу компонента. ```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'); } } ``` Найчастіше використовувані хуки: `ngOnInit` для ініціалізації, `ngOnChanges` для реакції на зміни input-ів і `ngOnDestroy` для звільнення ресурсів. ### 4. Що таке Data Binding в Angular? Data binding з'єднує дані компонента з шаблоном. Angular пропонує чотири форми 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)]` — це поєднання property binding та event binding, яке забезпечує автоматичну синхронізацію моделі та view. ### 5. Яка різниця між Module і Standalone Component? NgModules групують пов'язані компоненти, директиви та сервіси. Standalone-компоненти (Angular 14+) дозволяють створювати автономні компоненти без модуля. ```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(); } ``` Standalone-компоненти зменшують складність, покращують tree-shaking і спрощують lazy loading. Це рекомендований підхід для нових проєктів. ## Сервіси та Dependency Injection ### 6. Як працює dependency injection в Angular? Dependency injection (DI) — це центральний шаблон проєктування в Angular. Фреймворк керує створенням і наданням екземплярів сервісів. ```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(); } } ``` Різні рівні провіжнінгу (`providedIn: 'root'`, рівень модуля чи компонента) дозволяють контролювати область видимості та життєвий цикл сервісів. ### 7. Яка різниця між providedIn root, any і platform? Опції `providedIn` визначають, як Angular створює та поширює екземпляри сервісів. ```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); } ``` У більшості випадків `providedIn: 'root'` достатньо й оптимально для tree-shaking. > **Tree-shaking сервісів** > > З `providedIn: 'root'` Angular може видалити невикористані сервіси з фінального бандлу. Це покращує продуктивність завантаження. ### 8. Як використовувати Observables з RxJS в Angular? RxJS — бібліотека реактивного програмування, яку Angular використовує для роботи з асинхронними потоками даних. ```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(); } } ``` Оператори RxJS, такі як `debounceTime`, `distinctUntilChanged` і `switchMap`, є ключовими для оптимізації пошуку та уникнення зайвих запитів. ## Сучасний Angular (16+) ### 9. Що таке Signals в Angular і як їх використовувати? Signals (Angular 16+) вводять нову реактивну примітиву, простішу та продуктивнішу за Observables для локального стану. ```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 забезпечують вищу продуктивність завдяки точнішій change detection та інтуїтивнішому API порівняно з RxJS для локального стану. ### 10. Як працюють Signal Inputs і Outputs? Angular 17+ вводить `input()` та `output()` як signal-альтернативи декораторам `@Input()` і `@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 inputs забезпечують краще типування, більш узгоджений API з іншими сигналами та готують перехід на zoneless change detection. ### 11. Поясни новий Control Flow в Angular 17+ Angular 17 представляє вбудований синтаксис control flow у шаблонах, який замінює структурні директиви `*ngIf`, `*ngFor` і `*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' } ]); } ``` Новий синтаксис забезпечує кращу продуктивність завдяки оптимізованій компіляції, покращену читабельність і функції на кшталт `@empty` для `@for`. ### 12. Як налаштувати Lazy Loading зі standalone-компонентами? Lazy loading завантажує модулі чи компоненти на вимогу, скорочуючи час початкового завантаження. ```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) ] }); ``` Зі standalone-компонентами lazy loading стає простішим і гранулярнішим, ніж із NgModules. ## Форми в Angular ### 13. Яка різниця між Template-driven і Reactive Forms? Angular пропонує два підходи до роботи з формами, кожен підходить для різних сценаріїв. ```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 рекомендовані для складних форм: вони дають більше гнучкості, легше тестуються та дозволяють краще повторно використовувати логіку валідації. ### 14. Як створити власний валідатор? Власні валідатори дозволяють реалізовувати специфічні бізнесові правила валідації. ```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); } } } ``` Асинхронні валідатори особливо корисні для серверних перевірок, як-от унікальність e-mail чи імені користувача. ## Маршрутизація та Навігація ### 15. Як захистити маршрути за допомогою Guards? Guards контролюють доступ до маршрутів за умовами, такими як автентифікація чи дозволи. ```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 guards простіші та краще інтегруються із сучасною dependency injection. ### 16. Як передавати дані між маршрутами? Angular пропонує кілька способів передачі даних під час навігації. ```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 і перезавантаження** > > Дані, передані через `state`, втрачаються при перезавантаженні сторінки. Для постійних даних краще використовувати параметри URL або сервіс. ## Продуктивність і Оптимізація ### 17. Як працює change detection в Angular? Angular використовує Zone.js для виявлення асинхронних подій і запуску перевірки компонентів. ```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 ) ); } } ``` Стратегія OnPush у поєднанні з Signals дає найкращу продуктивність, обмежуючи перевірки лише фактично зміненими компонентами. ### 18. Як оптимізувати продуктивність Angular-застосунку? Кілька технік допомагають покращити продуктивність 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 {} ``` Поєднання цих технік із профілюванням через Angular DevTools допомагає виявити та усунути вузькі місця. ## Комунікація між компонентами ### 19. Які існують способи комунікації між компонентами? Angular пропонує різні шаблони комунікації залежно від ієрархічного зв'язку між компонентами. ```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); } } ``` Вибір способу залежить від зв'язку між компонентами та складності комунікації. ### 20. Як реалізувати state-менеджмент із Signals? Signals дозволяють створити простий реактивний store без зовнішніх бібліотек. ```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); } ``` Цей шаблон забезпечує передбачуване реактивне керування станом без складності NgRx і добре підходить для застосунків середнього розміру. ## Тестування в Angular ### 21. Як тестувати компонент Angular? Тести компонентів перевіряють рендеринг, взаємодії та інтеграцію із сервісами. ```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. Як тестувати сервіс Angular? Тести сервісу перевіряють бізнес-логіку та взаємодію з 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(); }); }); }); ``` ## Поглиблені питання ### 23. Як працює Server-Side Rendering (SSR) з Angular? Angular Universal вмикає рендеринг на стороні сервера для покращення SEO та сприйнятої продуктивності. ```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 покращує First Contentful Paint і дозволяє пошуковим системам індексувати динамічний контент. ### 24. Як організувати інтернаціоналізацію (i18n) в Angular? Angular пропонує кілька підходів до інтернаціоналізації застосунку. ```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}} !" } } ``` Вибір між вбудованим i18n і ngx-translate залежить від потреб: окрема компіляція для найкращої продуктивності або динамічне перемикання для більшої гнучкості. ### 25. Які найкращі практики структурування Angular-проєкту? Добре організована структура полегшує підтримку та масштабування проєкту. ``` 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); }; ``` Ці угоди дозволяють інтуїтивно орієнтуватися в коді й полегшують роботу в команді. ## Висновок Ці 25 питань охоплюють ключові концепції Angular, які запитують на співбесідах. Основні моменти, які варто опанувати: - ✅ **Основи**: компоненти, data binding, життєвий цикл, DI - ✅ **Сучасний Angular**: Signals, standalone-компоненти, новий control flow - ✅ **Реактивність**: RxJS, Observables, керування станом із Signals - ✅ **Форми**: Template-driven vs Reactive, кастомна валідація - ✅ **Маршрутизація**: Guards, lazy loading, передача даних - ✅ **Продуктивність**: OnPush, defer, virtual scrolling - ✅ **Тестування**: юніт-тести, mock-и, HttpTestingController Підготовка до співбесід з Angular вимагає регулярної практики. Власні проєкти допомагають закріпити ці знання та природно пояснювати їх під час співбесіди. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/angular/top-25-angular-interview-questions