Що таке Angular?
Angular - це платформа на базі TypeScript від Google для створення динамічних клієнтських веб-додатків. Використовує компонентну архітектуру, вбудоване впровадження залежностей (dependency injection) і реактивне зв'язування даних.
Теорія
TL;DR
- Angular - як набір міського планувальника: готові блоки (компоненти) з чіткими правилами з'єднання (dependency injection), щоб додаток не розвалювався при масштабуванні
- Головна різниця від React: Angular - повний фреймворк із маршрутизацією, формами та HTTP-клієнтом з коробки; React - UI-бібліотека, до якої треба докупляти пакети
- Команда більше 5 осіб або великий проект: Angular. Прототип або малий стартап: React чи Vue
- TypeScript за замовчуванням - помилки ловиш на етапі компіляції, не в браузері о другій ночі
- Zone.js і Ivy-рендерер контролюють change detection самостійно: змінюєш дані, DOM оновлюється
Швидкий приклад
// Базовий Angular-компонент - app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root', // рендериться в HTML як <app-root>
template: `
<h1>{{ title }}</h1>
<button (click)="update()">Натисни</button>
`
})
export class AppComponent {
title = 'Привіт Angular'; // прив'язане до шаблону
update() {
this.title = 'Оновлено!'; // автоматично оновлює DOM
}
}
// Жодного document.querySelector.
// Angular стежить за title і перерендерює h1 при кожній зміні.Декоратор @Component говорить Angular, що це компонент, який HTML-тег він займає і що рендерити. Змінюєш title у класі - браузер оновлюється миттєво. Жодних DOM-маніпуляцій вручну.
Angular проти бібліотеки
React дає тобі функцію рендерингу і каже: решта на твій розсуд. Angular поставляється з маршрутизацією (@angular/router), HTTP-клієнтом (HttpClient), формами (ReactiveFormsModule), анімацією та CLI, який розгортає повний проект однією командою. Це і є ключовий компроміс: більше рішень прийнято заздалегідь, менше хаосу у великій команді.
На практиці це відчутно. Команда з 10 розробників на React витратить тижні, домовляючись яку бібліотеку форм взяти, який стейт-менеджер і як організувати папки. В Angular команда відкриває ті самі файли в тих самих папках з першого дня. За моїм спостереженням, саме ця узгодженість скорочує онбординг на великих проектах більше, ніж будь-яка технічна фіча.
Коли використовувати
- Enterprise-дашборд із auth guards і lazy-loaded модулями: Angular
- Команда більше 10 розробників: Angular (TypeScript рятує при роботі з великою кодовою базою)
- Мобільний додаток із нативними компонентами: Angular + Ionic
- Малий прототип або MVP: React чи Vue запустяться швидше
- Рефакторинг старого jQuery-коду: Angular-директиви замінять більшість ручних DOM-маніпуляцій
Порівняння
| Характеристика | Angular | React | Vue |
|---|---|---|---|
| Тип | Повний фреймворк | UI-бібліотека | Фреймворк |
| Мова | TypeScript | JS/TS | JS/TS |
| Розмір Hello World | ~65KB gzip | ~35KB | ~20KB |
| Крива навчання | 1-2 тижні | ~3 дні | ~1 день |
| Вбудований роутер | Так | Ні (React Router) | Так |
| Вбудовані форми | Reactive + template | Ні (Formik, RHF) | Ні |
| Коли обирати | 50+ компонентів, enterprise | Гнучкий стек, кастомний UI | Швидкий старт |
Як працює Ivy і Zone.js
Браузер завантажує index.html із тегом <app-root>. Angular CLI компілює TypeScript у JS-бандли через webpack, Ivy-рендерер (за замовчуванням з Angular 9) розбирає декоратори @Component і будує дерево компонентів у пам'яті. Zone.js патчить браузерні асинхронні API - setTimeout, Promise, обробники подій - щоб Angular знав, коли запускати change detection. Оновлення DOM відбуваються в мікрозадачах, без перезавантаження сторінки.
Ivy зменшив бандли приблизно на 40% порівняно зі старим View Engine: він компілює компоненти незалежно, а не за один глобальний прохід для всього застосунку. Тому й перезбірки в режимі розробки стали помітно швидшими.
Типові помилки
1. *ngFor без trackBy
<!-- ПОГАНО: Angular знищує і відтворює всі елементи при кожній зміні масиву -->
<li *ngFor="let user of users">{{ user.name }}</li>
<!-- ДОБРЕ: Angular перевикористовує DOM-вузли за ідентифікатором -->
<li *ngFor="let user of users; trackBy: trackById">{{ user.name }}</li>На списках із 500+ елементів різниця у швидкості рендерингу помітна без профайлера.
2. Сервіс у providers компонента замість root
// ПОГАНО: новий екземпляр UserService на кожен компонент - ламає спільний стан
@Component({
providers: [UserService]
})
// ДОБРЕ: один синглтон на весь застосунок
@Injectable({ providedIn: 'root' })
export class UserService {}3. Відсутній async pipe для Observables
<!-- ПОГАНО: падає, коли Observable ще не емітував значення -->
<div>{{ (user$ | async).name }}</div>
<!-- ДОБРЕ: безпечна навігація обробляє undefined -->
<div *ngIf="user$ | async as user">{{ user.name }}</div>4. OnPush change detection із мутацією об'єкта
// ПОГАНО: той самий об'єктний референс - OnPush пропускає перевірку, UI застаріває
this.data.value = 'new'; // UI не оновиться!
// ДОБРЕ: новий референс запускає перевірку
this.data = { ...this.data, value: 'new' };Це ловить навіть досвідчених розробників, які звикли до мутабельного стану.
Де використовується
- Google Workspace: 100+ Angular-компонентів із lazy-loaded модулями
- Microsoft Office Online: Angular 16+ для спільної роботи в реальному часі (Nx monorepo)
- Forbes.com: Angular Universal (SSR) для серверного рендерингу та SEO
- NBA.com: RxJS-стріми для live-оновлення рахунків
- PayPal checkout: кастомні Angular-директиви для обробки платіжних форм
Питання для співбесіди
Q: Яка різниця між template-driven і reactive forms?
A: Template-driven форми використовують ngModel для двостороннього зв'язування і підходять для простих випадків. Reactive форми будуються через FormBuilder у TypeScript: вони незмінні, легко тестуються і добре масштабуються на складну валідацію.
Q: Поясни ієрархію dependency injection в Angular.
A: providedIn: 'root' створює один синглтон для всього застосунку. providers: [] у компоненті створює новий екземпляр, видимий лише для цього компонента і його дочірніх елементів. Lazy-loaded модулі отримують власний інжектор. Розуміння цієї ієрархії пояснює більшість помилок типу "чому дані в сервісі застаріли".
Q: Що покращив Ivy-рендерер порівняно зі старим View Engine?
A: Ivy компілює кожен компонент незалежно замість одного глобального проходу для всього застосунку. Це зменшує бандли приблизно на 40%, покращує tree-shaking і прискорює інкрементні перезбірки в розробці.
Q: Навіщо Angular потрібен Zone.js?
A: Zone.js патчить браузерні асинхронні API, щоб Angular автоматично запускав change detection після завершення асинхронної роботи. Без нього треба було б вручну викликати ChangeDetectorRef.detectChanges() після кожного HTTP-запиту, таймера або події.
Q: Senior-питання: з ChangeDetectionStrategy.OnPush і async pipe - чи оновиться компонент при новому значенні з NgRx-селектора? Чому?
A: Так. async pipe всередині викликає markForCheck() при кожному новому значенні Observable. NgRx select повертає новий Observable-референс при зміні стану. Тому навіть з OnPush компонент позначається як такий, що потребує перевірки, і Angular включає його в наступний цикл change detection.
Приклади
Базовий: лічильник кліків
// counter.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<p>Лічильник: {{ count }}</p>
<button (click)="increment()">+1</button>
`
})
export class CounterComponent {
count = 0;
increment() {
this.count++; // Angular оновлює <p> автоматично
}
}Основний цикл: дія користувача викликає метод, метод змінює дані, Angular перерендерює шаблон. Жодних DOM-запитів.
Середній: список користувачів із API
// user.service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class UserService {
constructor(private http: HttpClient) {}
getUsers(): Observable<any[]> {
return this.http.get<any[]>('https://jsonplaceholder.typicode.com/users');
}
}
// users.component.ts
@Component({
template: `
<ul>
<li *ngFor="let user of users; trackBy: trackById">
{{ user.name }}
</li>
</ul>
`
})
export class UsersComponent {
users: any[] = [];
constructor(private userService: UserService) {
this.userService.getUsers().subscribe(data => this.users = data);
}
trackById(index: number, user: any) {
return user.id; // запобігає повному перерендерингу списку при зміні одного елемента
}
}
// Виводить 10 імен із API. UserService вводиться автоматично через DI.UserService оголошено один раз із providedIn: 'root' - кожен компонент, якому він потрібен, отримує той самий екземпляр без додаткових налаштувань.
Просунутий: OnPush з незмінними даними
// Цей патерн ловить більшість middle-розробників хоча б раз.
@Component({
changeDetection: ChangeDetectionStrategy.OnPush // перевіряє тільки при зміні референсу @Input
})
export class UserCardComponent {
@Input() user: { name: string };
}
// Батьківський компонент - НЕПРАВИЛЬНО:
this.user.name = 'Яна'; // той самий референс - OnPush пропускає, UI застаріє!
// ПРАВИЛЬНО: новий об'єктний референс
this.user = { ...this.user, name: 'Яна' }; // OnPush виявляє зміну, оновлює UI
// Те саме для масивів:
// НЕПРАВИЛЬНО: this.items.push(newItem)
// ПРАВИЛЬНО: this.items = [...this.items, newItem]OnPush - це оптимізація: Angular пропускає компонент під час change detection, якщо жоден із його @Input не змінив референс. У великих деревах компонентів це вдвічі скорочує зайвий рендеринг, але вимагає незмінного (immutable) підходу до даних.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.