Skip to main content

Що таке 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 оновлюється

Швидкий приклад

typescript
// Базовий 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-маніпуляцій

Порівняння

ХарактеристикаAngularReactVue
ТипПовний фреймворкUI-бібліотекаФреймворк
МоваTypeScriptJS/TSJS/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

html
<!-- ПОГАНО: 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

typescript
// ПОГАНО: новий екземпляр UserService на кожен компонент - ламає спільний стан @Component({ providers: [UserService] }) // ДОБРЕ: один синглтон на весь застосунок @Injectable({ providedIn: 'root' }) export class UserService {}

3. Відсутній async pipe для Observables

html
<!-- ПОГАНО: падає, коли Observable ще не емітував значення --> <div>{{ (user$ | async).name }}</div> <!-- ДОБРЕ: безпечна навігація обробляє undefined --> <div *ngIf="user$ | async as user">{{ user.name }}</div>

4. OnPush change detection із мутацією об'єкта

typescript
// ПОГАНО: той самий об'єктний референс - 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.

Приклади

Базовий: лічильник кліків

typescript
// 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

typescript
// 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 з незмінними даними

typescript
// Цей патерн ловить більшість 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) підходу до даних.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?