# Top 25 Pytań Rekrutacyjnych Angular: Kompletny Przewodnik do Sukcesu > 25 najczęściej zadawanych pytań rekrutacyjnych z Angulara w 2026 roku. Szczegółowe odpowiedzi, przykłady kodu i wskazówki, by zdobyć stanowisko developera 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 --- Techniczne rozmowy rekrutacyjne z Angulara sprawdzają zrozumienie architektury frameworka, znajomość TypeScriptu oraz dobre praktyki tworzenia frontu. Ten przewodnik prezentuje 25 najczęściej zadawanych pytań wraz ze szczegółowymi odpowiedziami i przykładami kodu, które pomogą w optymalnym przygotowaniu. > **Wskazówka przygotowawcza** > > Te pytania obejmują najnowsze wersje Angulara (16+), w tym Signals, komponenty standalone i nowy control flow. Opanowanie tych nowoczesnych koncepcji świadczy o aktywnym śledzeniu rozwoju technologii. ## Podstawy Angulara ### 1. Jaka jest różnica między Angularem a AngularJS? Angular (wersje 2+) to całkowite przepisanie AngularJS. Główne różnice dotyczą architektury, języka i wydajności. AngularJS używał JavaScriptu i wzorca MVC z systemem two-way binding, który mógł powodować problemy z wydajnością. Angular korzysta z TypeScriptu, architektury opartej na komponentach i zoptymalizowanego systemu 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 oferuje również natywne wsparcie dla modułów ES6, lepszy tooling z Angular CLI oraz architekturę lepiej dopasowaną do aplikacji enterprise. ### 2. Czym jest komponent Angulara i jak go utworzyć? Komponent Angulara to klasa TypeScript udekorowana adnotacją `@Component`. Hermetyzuje logikę, szablon i style fragmentu interfejsu użytkownika. ```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); } } ``` Komponenty standalone (Angular 14+) upraszczają tworzenie, eliminując konieczność deklarowania komponentu w NgModule. ### 3. Wyjaśnij cykl życia komponentu Angulara Angular udostępnia lifecycle hooks, które pozwalają wykonać kod w określonych momentach życia komponentu. ```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'); } } ``` Najczęściej używane hooki to `ngOnInit` do inicjalizacji, `ngOnChanges` do reagowania na zmiany inputów oraz `ngOnDestroy` do zwolnienia zasobów. ### 4. Czym jest Data Binding w Angularze? Data binding łączy dane komponentu z szablonem. Angular oferuje cztery formy bindingu. ```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)]` to połączenie property bindingu i event bindingu, które zapewnia automatyczną synchronizację modelu i widoku. ### 5. Jaka jest różnica między Module a Standalone Component? NgModules grupują powiązane komponenty, dyrektywy i serwisy. Komponenty standalone (Angular 14+) pozwalają tworzyć autonomiczne komponenty bez modułu. ```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(); } ``` Komponenty standalone zmniejszają złożoność, poprawiają tree-shaking i upraszczają lazy loading. To zalecane podejście dla nowych projektów. ## Serwisy i Dependency Injection ### 6. Jak działa dependency injection w Angularze? Dependency injection (DI) to centralny wzorzec projektowy w Angularze. Framework zarządza tworzeniem i dostarczaniem instancji serwisów. ```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(); } } ``` Różne poziomy provisioning (`providedIn: 'root'`, poziom modułu lub komponentu) pozwalają kontrolować zasięg i cykl życia serwisów. ### 7. Jaka jest różnica między providedIn root, any i platform? Opcje `providedIn` decydują o tym, jak Angular tworzy i współdzieli instancje serwisów. ```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); } ``` W większości przypadków `providedIn: 'root'` jest wystarczające i optymalne dla tree-shakingu. > **Tree-shaking serwisów** > > Dzięki `providedIn: 'root'` Angular potrafi usunąć z finalnego bundla nieużywane serwisy. Poprawia to wydajność ładowania. ### 8. Jak używać Observables z RxJS w Angularze? RxJS to biblioteka programowania reaktywnego używana przez Angulara do obsługi asynchronicznych strumieni danych. ```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(); } } ``` Operatory RxJS takie jak `debounceTime`, `distinctUntilChanged` czy `switchMap` są kluczowe do optymalizacji wyszukiwania i unikania zbędnych żądań. ## Nowoczesny Angular (16+) ### 9. Czym są Signals w Angularze i jak ich używać? Signals (Angular 16+) wprowadzają nowy reaktywny prymityw, prostszy i wydajniejszy od Observables w przypadku stanu lokalnego. ```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 zapewniają lepszą wydajność dzięki bardziej granularnej change detection oraz bardziej intuicyjnemu API niż RxJS dla stanu lokalnego. ### 10. Jak działają Signal Inputs i Outputs? Angular 17+ wprowadza `input()` i `output()` jako alternatywy oparte na signalach dla dekoratorów `@Input()` i `@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 oferują lepsze typowanie, bardziej spójne API z resztą signali oraz przygotowują przejście na zoneless change detection. ### 11. Wyjaśnij nowy Control Flow w Angular 17+ Angular 17 wprowadza nową, wbudowaną składnię control flow w szablonach, zastępującą strukturalne dyrektywy `*ngIf`, `*ngFor` i `*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' } ]); } ``` Nowa składnia zapewnia lepszą wydajność dzięki zoptymalizowanej kompilacji, większą czytelność i nowe funkcje, takie jak `@empty` dla `@for`. ### 12. Jak skonfigurować Lazy Loading z komponentami standalone? Lazy loading ładuje moduły lub komponenty na żądanie, skracając czas pierwszego ładowania. ```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) ] }); ``` Z komponentami standalone lazy loading jest prostszy i bardziej granularny niż z NgModules. ## Formularze w Angularze ### 13. Jaka jest różnica między Template-driven i Reactive Forms? Angular oferuje dwa podejścia do obsługi formularzy, z których każde sprawdza się w innych przypadkach użycia. ```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 są zalecane do złożonych formularzy: dają większą elastyczność, łatwiej je testować i pozwalają lepiej współdzielić logikę walidacji. ### 14. Jak utworzyć własny validator? Własne validatory pozwalają wdrożyć specyficzne, biznesowe reguły walidacji. ```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); } } } ``` Asynchroniczne validatory są szczególnie przydatne do weryfikacji po stronie serwera, np. unikalności adresu e-mail czy nazwy użytkownika. ## Routing i Nawigacja ### 15. Jak chronić trasy za pomocą Guards? Guards kontrolują dostęp do tras na podstawie warunków, takich jak uwierzytelnienie czy uprawnienia. ```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 są prostsze i lepiej integrują się z nowoczesną dependency injection. ### 16. Jak przekazywać dane między trasami? Angular oferuje kilka metod przekazywania danych podczas nawigacji. ```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 i odświeżanie** > > Dane przekazane przez `state` są tracone przy odświeżeniu strony. Dla danych trwałych lepiej użyć parametrów URL lub serwisu. ## Wydajność i Optymalizacja ### 17. Jak działa change detection w Angularze? Angular wykorzystuje Zone.js do wykrywania zdarzeń asynchronicznych i uruchamiania weryfikacji komponentów. ```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 ) ); } } ``` Strategia OnPush w połączeniu z Signalami daje najlepszą wydajność, ograniczając sprawdzanie wyłącznie do faktycznie zmienionych komponentów. ### 18. Jak optymalizować wydajność aplikacji Angulara? Kilka technik pozwala zwiększyć wydajność aplikacji Angulara. ```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 {} ``` Połączenie tych technik z profilowaniem przez Angular DevTools pomaga zidentyfikować i wyeliminować wąskie gardła. ## Komunikacja między komponentami ### 19. Jakie są różne metody komunikacji między komponentami? Angular oferuje różne wzorce komunikacji w zależności od relacji hierarchicznej między komponentami. ```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); } } ``` Wybór metody zależy od relacji między komponentami i złożoności komunikacji. ### 20. Jak zaimplementować zarządzanie stanem za pomocą Signali? Signals umożliwiają stworzenie prostego, reaktywnego store'a bez zewnętrznych bibliotek. ```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); } ``` Ten wzorzec daje przewidywalne, reaktywne zarządzanie stanem bez złożoności NgRx i sprawdza się w aplikacjach średniej wielkości. ## Testowanie w Angularze ### 21. Jak testować komponent Angulara? Testy komponentów weryfikują renderowanie, interakcje i integrację z serwisami. ```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. Jak testować serwis Angulara? Testy serwisów weryfikują logikę biznesową i interakcje z 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(); }); }); }); ``` ## Pytania zaawansowane ### 23. Jak działa Server-Side Rendering (SSR) w Angularze? Angular Universal umożliwia renderowanie po stronie serwera, co poprawia SEO i odczuwalną wydajność. ```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 poprawia First Contentful Paint i pozwala wyszukiwarkom indeksować dynamiczne treści. ### 24. Jak obsługiwać internacjonalizację (i18n) w Angularze? Angular oferuje kilka podejść do internacjonalizacji aplikacji. ```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}} !" } } ``` Wybór między wbudowanym i18n a ngx-translate zależy od potrzeb: oddzielna kompilacja dla najlepszej wydajności lub dynamiczne przełączanie dla większej elastyczności. ### 25. Jakie są dobre praktyki strukturyzowania projektu Angulara? Dobrze zorganizowana struktura ułatwia utrzymanie i skalowanie projektu. ``` 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); }; ``` Te konwencje umożliwiają intuicyjną nawigację po kodzie i ułatwiają pracę zespołową. ## Podsumowanie Te 25 pytań pokrywa kluczowe koncepcje Angulara pojawiające się na rozmowach. Najważniejsze zagadnienia do opanowania: - ✅ **Podstawy**: komponenty, data binding, cykl życia, DI - ✅ **Nowoczesny Angular**: Signals, komponenty standalone, nowy control flow - ✅ **Reaktywność**: RxJS, Observables, zarządzanie stanem z Signalami - ✅ **Formularze**: Template-driven vs Reactive, własna walidacja - ✅ **Routing**: Guards, lazy loading, przekazywanie danych - ✅ **Wydajność**: OnPush, defer, virtual scrolling - ✅ **Testowanie**: testy jednostkowe, mocki, HttpTestingController Przygotowanie do rozmów rekrutacyjnych z Angulara wymaga regularnej praktyki. Tworzenie własnych projektów pomaga utrwalić tę wiedzę i swobodnie ją tłumaczyć podczas rozmowy. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/angular/top-25-angular-interview-questions