РозробкаSenior

Angular

Angular — фреймворк Google на TypeScript, що постачає весь фронтенд-стек і між мажорними версіями сам оновлює ваш код автоматичними міграціями. Цей стек — компоненти, впровадження залежностей, маршрутизація, форми, HTTP, тестування й збірка, обрані й випущені разом. Між 2022 і 2026 роками він замінив більшу частину того, як на ньому писали: standalone-компоненти замість NgModules, сигнали замість zone.js і OnPush за замовчуванням. Сторінка починається з кожного будівельного блоку на одному екрані — сучасний спосіб поруч із тим, який він замінив; далі — релізи й підтримка, впровадження залежностей, виявлення змін, сигнали й RxJS, форми й безпека та п’ять задокументованих кейсів.

Поточна версія
22.2 · вересень 2026
Мова
TypeScript 6.0+, обов’язково
Мажорні релізи
Раз на рік, з v22

Angular на одному екрані: кожен будівельний блок — тоді й тепер

Застосунок на Angular складається з невеликої кількості видів речей: компонентів, сервісів, від яких вони залежать, шаблонів, входів і виходів, і механізму, що вирішує, коли оновлювати екран. Майже в кожного з них тепер є сучасний спосіб і старіший, який досі працює, — і більшість матеріалів в інтернеті вчить старішого. Тут усе це одразу. Таблиця перелічує кожну роботу, те, як її робить Angular 22 (вересень 2026), що саме цей спосіб замінив, і версію, в якій він став стабільним. Це ще й найшвидший спосіб зрозуміти, який Angular описує туторіал, кодова база чи кандидат на співбесіді.

РоботаСучасний спосібЩо він замінивЗ версії
Організація кодуStandalone-компоненти, що самі перелічують свої importsNgModules, що оголошують групи компонентівv15 · типово v19
Отримання залежностіinject() у полі чи функціїПараметри конструктора — досі підтримуютьсяv14
Стан компонентаsignal()Звичайні поля, які zone.js перевіряє після кожної подіїv17
Похідний станcomputed()Геттери чи значення, що перераховуються в ngOnChangesv17
Стан, що йде за входом, але редагуєтьсяlinkedSignal()Копіювання входу в поле в ngOnChangesv20
Реакція на стан поза шаблономeffect() — ощадливоngOnChanges або subscribe в ngOnInitv20
Входиinput(), input.required()Декоратори @Input()v19
Виходиoutput()@Output() з EventEmitterv19
Двобічне зв’язуванняmodel()@Input() плюс @Output(), чия назва закінчується на Changev19
Доступ до дочірніх елементівviewChild(), contentChild()@ViewChild, @ContentChildv19
Умови й цикли в шаблонах@if, @for (…; track …), @switch*ngIf, *ngFor, *ngSwitch — застарілі з v20v18
Локальна змінна в шаблоні@letТрюк *ngIf="data as d"v19
Пізніше завантаження частини сторінкиБлоки @deferЛінивий маршрут чи написаний вручну динамічний імпортv18
Завантаження данихresource(), httpResource()HttpClient плюс subscribe плюс прапорці завантаження й помилки вручнуv22
ФормиSignal Forms, form()Reactive Forms і форми на шаблонах — жодні не застаріліv22
Що запускає перевіркуZoneless: зміна сигналу, подія в шаблоні, markForCheckzone.js, що патчить кожен async-API браузераv20.2 · типово v21
Що перевіряєтьсяOnPush, за замовчуваннямСтратегія Default, тепер названа Eagerтипово v22
Транспорт HTTPfetchXMLHttpRequest — лишився як withXhr() для прогресу вивантаженнятипово v22
Серверний рендерингІнкрементальна гідратація: @defer (hydrate on …)Гідратація всієї сторінки одразу — або, у старіших версіях, її перебудоваv20 · типово v22
Юніт-тестиVitestKarma і Jasmineтипово v21
Завершення підпискиtakeUntilDestroyed() або async pipeSubject, завершений у ngOnDestroyv19

Усе вирішено — і оновлюється за вас

Почати проєкт на React означає обрати маршрутизатор, шар даних, бібліотеку форм, підхід до стилів і збірку ще до першої фічі. Почати проєкт на Angular означає виконати одну команду й отримати все це — обране, інтегроване й випущене разом. Чи звучить це як полегшення, чи як клітка — і є чесною перевіркою, чи пасує вам фреймворк. Наслідок, що важить у роках, а не тижнях, — однорідність: дві кодові бази на Angular, написані різними командами, суттєво схожі, тож інженер, що переходить між ними, швидко стає продуктивним.

Другий наслідок незвичний для фреймворку: Angular бере на себе відповідальність за те, щоб ваш код рухався вперед. ng update запускає міграції — скрипти, що переписують ваш код під нову версію. Коли v21 перестав за замовчуванням використовувати zone.js, міграція додала в наявні застосунки provideZoneChangeDetection(), щоб вони працювали, як раніше. Коли v22 зробив кожен компонент без явної стратегії OnPush, міграція додала ChangeDetectionStrategy.Eager саме таким компонентам, тож оновлення тихо не змінило поведінку жодного з них. Нове налаштування діє для нового коду; стара поведінка зберігається — записана явно — там, де на неї вже покладалися.

Між 2022 і 2026 роками кожна з найбільших змін Angular приходила як нове налаштування за замовчуванням за фіксованим розкладом: standalone-компоненти стабільні у v15 і типові у v19, сигнали й синтаксис @if / @for у v17, zoneless-виявлення змін за замовчуванням у v21 і OnPush за замовчуванням у v22. Патчі одночасно отримують лише три версії. Станом на 30 вересня 2026 року v22 активна до червня 2027 й отримує патчі безпеки до червня 2028, v21 у довгостроковій підтримці до червня 2027, а v20 — до 28 листопада 2026; усе від v2 до v19 не отримує нічого. До v22 мажорна версія виходила раз на пів року; з v22 — раз на рік.

Міграції допомагають лише всередині вікна підтримки, а вікно коротке. Кожна мажорна версія якийсь час «активна» — отримує виправлення й дрібні можливості, — а потім переходить у довгострокову підтримку, коли випускають лише критичні виправлення й патчі безпеки. Усе старіше не отримує нічого. До v22 мажорна версія виходила раз на пів року; з v22 — раз на рік, із чотирма-шістьма мінорними релізами між ними, тож кожне вікно довше, а кожне оновлення більше.

ВерсіяВипущеноАктивна доПатчі безпеки до
v223 черв. 2026черв. 2027черв. 2028
v2119 лист. 20253 черв. 2026черв. 2027
v2028 трав. 202519 лист. 202528 лист. 2026
v2 – v192016 – 2024—Жодних

Angular

  • Роутер, форми, , тестування — надані та інтегровані
  • і впровадження залежностей не є опційними
  • Автоматичні міграції між мажорними версіями
  • Велике навантаження понять на старті, далі однорідність
  • Шаблони, скомпільовані заздалегідь

React

  • Модель компонентів; решта — ваш вибір або фреймворку
  • TypeScript опційний, впровадження залежностей нетипове
  • Оновлення узгоджуються між незалежними пакетами
  • Мале ядро; складність накопичується у виборі
  • — вигляд є звичайним JavaScript

Вибір рідко буває технічним. Angular пасує організації з багатьма командами, довгоживучими продуктами й плинністю кадрів, де одна відповідь на питання цінніша за найкращу відповідь на кожне. React пасує команді, яка хоче зібрати власний стек і здатна нести ціну його підтримки. Обидва будують ті самі застосунки; різниця в тому, хто ухвалює рішення і хто з ними живе.

Впровадження залежностей: місце надання вирішує, скільки екземплярів

Компонент не створює того, що йому потрібно. Він просить — private http = inject(HttpClient), — і фреймворк дає йому екземпляр, а компонент не знає, звідки той узявся й хто ще ним користується. Об’єкт, що зберігає екземпляри й видає їх, називається інжектором, і в Angular їх ціле дерево.

Виграш — у підміні. Тест замінює справжній HTTP-клієнт підробкою зміною одного провайдера, а не патчем імпорту. Компоненту, що пише в лог через впроваджений логер, можна дати інший логер в іншому середовищі. Ціна — опосередкованість: читаючи компонент, ви бачите, від чого він залежить, але не бачите, яка реалізація прийде. Цей компроміс вартий того, коли підміна справді використовується, і є чистою накладною витратою, коли ні: сервіс, впроваджений в одному місці й ніде не замінений, не дав нічого і зробив код на один стрибок важчим для читання.

Коли компонент просить сервіс, Angular шукає у власному інжекторі компонента, потім угору деревом компонентів, потім у кореневому інжекторі, і перемагає перший знайдений провайдер. Сервіс, наданий у корені, існує один на весь застосунок і живе, доки відкрита вкладка, тож усі три картки отримують той самий CartService. Сервіс у providers компонента створюється по одному на кожен екземпляр цього компонента й знищується разом із ним, тож кожна картка має власний CardState. Коли сервіс ніде не надано, пошук падає з NG0201.

Коли компонент щось просить, Angular шукає спершу у власному інжекторі компонента, потім у батьківському, вгору деревом компонентів, а тоді — в кореневому інжекторі застосунку. Перемагає перший, у кого є провайдер. Якщо ні в кого немає, запит падає з NG0201: No provider for … found — помилкою, яку кожен розробник на Angular зустрічає першого ж тижня, зазвичай тому, що сервіс ніде не зареєстрували. Тож місце, де ви реєструєте сервіс, вирішує, скільки екземплярів існує і як довго живе кожен:

Де наданоСкільки екземплярівЖиве доПідходить для
providedIn: "root"Один на весь застосунокВкладку закрилиСервіси без стану, клієнт API, користувач, що увійшов, кеші
providers компонентаОдин на кожен екземпляр цього компонентаЦей екземпляр знищеноСтан одного віджета, однієї форми, одного покрокового майстра

Як фреймворк знаходить, що робити

Angular має вирішувати, коли порівнювати шаблон кожного компонента з його даними й оновлювати екран. Це порівняння називається виявленням змін. Більшу частину життя Angular відповідь на «коли» давав zone.js — бібліотека, що патчить кожен асинхронний браузера (таймери, слухачі подій, мережеві запити), щоб Angular дізнавався щоразу, коли будь-що завершується. За цим сигналом Angular обходив дерево компонентів і переобчислював вирази в шаблонах.

Та сама подія приходить у той самий компонент в обох деревах. За стратегією Eager, що до v22 звалася Default, Angular перевіряє кожен компонент застосунку на кожному оберті. За OnPush він перевіряє лише шлях від кореня до компонента, де сталася зміна, а решту пропускає — тож обсяг роботи перестає залежати від розміру застосунку й починає залежати від того, де саме сталася зміна. З v22 компонент, що не називає стратегії, є OnPush.

Це менш марнотратно, ніж звучить. Angular компілює шаблони заздалегідь, тож перевірка виразу — це порівняння властивостей, а не інтерпретація, і тисячі таких перевірок швидкі. Проблемою це стає в масштабі та в певних формах: велике дерево, шаблон, що викликає функцію на кожній перевірці, або подія, яка сипле безперервно, як прокрутка чи рух миші над графіком.

Першою відповіддю був OnPush, і схема показує, що він дає. Компонент, позначений OnPush, перевіряється, лише коли один із його входів отримує нове посилання, коли спрацьовує подія, прив’язана в його власному шаблоні, коли змінюється сигнал, який читає його шаблон, або коли хтось викликає markForCheck — а async pipe робить це за вас. Усе інше пропускається разом із піддеревом. Ціна перестає залежати від розміру застосунку й починає залежати від того, де сталася зміна, — за умови, що входи замінюють, а не змінюють на місці; тому OnPush і незмінні оновлення завжди приходять разом. З v22 новий компонент є OnPush, якщо не сказано інакше; стара поведінка, що перевіряє все, тепер називається Eager.

Zoneless: знати замість здогадуватися

zone.js відповідає на питання «чи щось завершилося?», а чесна відповідь майже завжди «так»: таймер у скрипті аналітики, рух миші, який обробила бібліотека графіків, запит опитування, що нічого не змінив. Кожне з них запускало перевірку. Сигнали дають Angular поставити краще питання — «чи змінилося щось, що читають шаблони?», — бо сигнал знає, хто його прочитав. Zoneless-Angular, стабільний з v20.2 і типовий для нових застосунків з v21, повністю відмовляється від zone.js і запускає перевірку лише на зміну сигналу, на подію, прив’язану в шаблоні, або на явний markForCheck.

zone.js патчить кожен асинхронний API браузера, тож Angular чує про кожну подію, що завершилася, і запускає прохід виявлення змін після кожної: дев’ять за цю ілюстративну секунду, з яких сім нічого не змінюють на екрані. Zoneless-Angular, типовий для нових застосунків з v21, запускає прохід лише тоді, коли змінюється сигнал, який читають шаблони, коли спрацьовує подія, прив’язана в шаблоні, або коли хтось викликає markForCheck, — тут двічі. Таймінги ілюстративні; суть — у кількості.

Практичний ефект — менше JavaScript у збірці, жодних пропатчених API браузера й строго менше роботи на кожну взаємодію. Ламається лише код, що покладався на те, що zone.js помітить зміну, про яку ніхто не оголосив, — звичайне поле, присвоєне всередині setTimeout в OnPush- чи zoneless-компоненті. Виправлення в кожному випадку те саме: тримайте це значення в сигналі.

Сигнали для стану, RxJS для подій у часі

Сигнал — це значення, яке запам’ятовує, хто його прочитав, подібно до ref у Vue. signal() тримає значення, яке ви задаєте; computed() виводить значення з інших сигналів — ліниво й лише тоді, коли ті змінюються; linkedSignal() іде за джерелом, але його можна задати й вручну, — як-от обраний пункт, що скидається, коли змінюється список. Входи теж сигнали: name = input<string>() читається як this.name(), тож усе похідне від входів — це computed, і більша частина того, що раніше робив ngOnChanges, зникає.

З effect() треба обережно, і порада Angular збігається з порадою React щодо useEffect: він для синхронізації з чимось поза графом сигналів — локальним сховищем, бібліотекою графіків, логом, — а не для того, щоб тримати один шматок стану в ногу з іншим. Effect, що задає сигнал, — це computed, написаний довгим шляхом, із миттю між ними, коли обидва не згодні.

Завантаження даних отримало власний API на сигналах, стабільний з v22. httpResource(() => "/api/orders/" + this.id()) повертає об’єкт, чиї value, status і error — сигнали, із шістьма статусами: idle, loading, reloading, resolved, error і local, де останній означає, що значення задали вручну. Коли id змінюється, поки запит ще в дорозі, ресурс його скасовує. Це прибирає за самою конструкцією перегони, в яких повільна стара відповідь перезаписує швидку нову, — ті перегони, які сторінки про React і Vue розв’язують вручну.

Де RxJS досі на своєму місці

RxJS моделює значення, що приходить багато разів, — потік — і дає оператори, щоб потоки перетворювати, поєднувати, затримувати й скасовувати. Події маршрутизатора й зміни значень форм в Angular — потоки, тож ви зустрінете його незалежно від того, чи шукали. Свою складність він виправдовує в координації в часі: поле пошуку, що чекає паузи в наборі, скасовує попередній запит на кожне нове натискання, ігнорує відповіді, що прийшли не по черзі, і повторює спробу при збої, — це чотири оператори й приблизно шість рядків. Написане вручну, це тридцять рядків із багом у скасуванні.

Більша частина цієї сили — в одному рішенні: що робити із запитом, який уже виконується, коли приходить нове значення. RxJS має для цього чотири оператори, і вибір не того дає баг, зовсім не схожий на свою причину.

ОператорЗапит, що вже виконуєтьсяДля чогоНе на своєму місці
switchMapСкасовується; важить лише останнійПошук під час набору, параметр маршрутуНа збереженнях: скасоване збереження — втрачений запис
mergeMapЛишається; усі йдуть паралельно, відповіді в будь-якому порядкуНезалежні вивантаження, логуванняНа пошуку: стара відповідь може прийти останньою
concatMapЛишається; новий чекає своєї чергиЗбереження, що мають дійти до сервера по черзіНа пошуку: черга відповідей, які нікому не потрібні
exhaustMapЛишається; нове значення ігноруєтьсяКнопка оплати чи надсилання, натиснута двічіНа пошуку: набір ігнорується, поки йде запит

Шкодить RxJS там, де значення існує по одному за раз. BehaviorSubject із поточним користувачем — це потік з одного елемента, загорнутий у механізм, розрахований на багато, і кожен споживач мусить підписатися, відписатися й обробити початковий стан. Сигнали пасують цій формі краще, і вони задумані жити поруч: сигнали для стану, RxJS для подій у часі, а toSignal() і toObservable() — для перетворення між ними.

Форми й захист, який ви отримуєте за замовчуванням

Форми — найсильніша територія Angular, і з v22 є три способи їх будувати. Жоден не застарілий, а різняться вони тим, де живе модель форми:

ПідхідДе живе модельПідходить для
На шаблонахУ шаблоні, зв’язана через ngModelФорма входу, поле пошуку
Reactive FormsУ класі, як об’єкти FormGroup і FormControl, типізовані з v14Наявні застосунки, форми, що будуються під час виконання
Signal FormsУ сигналі з даними, а правила валідації — у схеміНовий код на v22 — експериментальні у v21, публічний API з v22

Безпека — те, що ви отримуєте, не просячи, і варто точно знати, що вона покриває. Angular вважає кожне значення, прив’язане в шаблоні, недовіреним. Текст у {{ }} екранується, тож коментар із <script> з’являється як текст. Значення, прив’язане до [innerHTML], санітизується — скрипти й небезпечні атрибути вирізаються, — а URL в href і src перевіряються. Шаблони компілюються під час збірки застосунку, тож у продакшені в браузері не працює компілятор шаблонів, до якого міг би дістатися ввід користувача. А HttpClient надсилає токен захисту від підробки запитів — читає його з XSRF-TOKEN і відправляє в заголовку X-XSRF-TOKEN — у запитах, що змінюють дані, лише на ваш власний origin, і серверу лишається одне: перевірити його.

П’ять кейсів і те, чого в кожному бракувало

Кожен кейс нижче задокументовано у власних журналах змін, посібниках чи бюлетенях безпеки Angular. Разом вони пояснюють форму фреймворку сьогодні — чому він так багато вкладає в міграції, чому шаблони компілюються до того, як потрапляють до користувача, і чому версія, на якій ви працюєте, важить не менше за код, який ви пишете.

КейсЩо сталосяЧого бракувало
AngularJS → Angular, 2014–2021Angular 2 був новим фреймворком зі старою назвою, оголошеним без шляху оновленняШлях міграції — причина, з якої існує ng update
Пісочниця виразів, 2016AngularJS прибрав пісочницю, яку команди помилково вважали межею безпекиПравило, що користувачі ніколи не пишуть шаблонів, — тепер його забезпечують скомпільовані шаблони
NgModules, 2016–2024Додані за п’ять тижнів до 2.0, вони вісім років були найважчим поняттям для новачківКомпілятор, здатний прочитати кожен компонент окремо
View Engine → Ivy, 2020–2023Рушій рендерингу замінили; бібліотеки, зібрані під старий, перестали працювати у v16Залежності, що рухаються так само швидко, як фреймворк
CVE-2025-66035, лист. 2025HttpClient надсилав токен захисту на чужі хости; виправлення вийшло лише для підтримуваних версійПеребування у вікні підтримки

Перший кейс — причина, з якої існує другий розділ цієї сторінки. AngularJS, випущений 2010 року, був одним із найпопулярніших фронтенд-фреймворків у світі, коли Google оголосив його наступника на конференції ng-europe в Парижі в жовтні 2014 року. Angular 2 зберіг назву й дуже мало іншого — нова модель компонентів, новий синтаксис шаблонів, а з 2015 року й TypeScript, — і на момент анонсу не мав жодного шляху оновлення. Реакція була люта, і наступні два роки команда будувала цей шлях: ngUpgrade, що дозволяє обом фреймворкам працювати в одному застосунку, поки його переносять частинами. Angular 2.0 вийшов 14 вересня 2016 року. AngularJS увійшов у довгострокову підтримку в липні 2018 року, отримав її продовження на пів року під час пандемії й втратив 31 грудня 2021 року; його останній реліз, 1.8.3, називається «ultimate-farewell». Кожна автоматична міграція, яку Angular випустив відтоді, — почасти відповідь на 2014 рік.

Третій і четвертий кейси — та сама історія, розказана двічі. NgModules з’явилися в серпні 2016 року, у п’ятому реліз-кандидаті, за п’ять тижнів до 2.0, — здебільшого тому, що компілятору був потрібен контекст, щоб знати, які компоненти дозволено використовувати в шаблоні. Вісім років вони були поняттям, яке новачкам давалося найважче, і частиною кожного туторіала, де найімовірніше була помилка. Прибрав їх новий рушій рендерингу. Ivy, типовий з v9 у лютому 2020 року, компілює кожен компонент з усім, що той має знати про себе, тож компонент може сам перелічити свої імпорти. Standalone-компоненти стали стабільними у v15, а типовими — у v19, у листопаді 2024 року. Сама заміна рушія мала власну ціну: бібліотеки, скомпільовані під старий рушій, View Engine, працювали через компілятор сумісності, доки v16 не прибрав його в травні 2023 року з прямим попередженням у журналі змін: «бібліотеки для Angular View Engine більше не працюватимуть». Застосунок не може рухатися швидше за свою найповільнішу залежність, і деякі команди виявили, що їхню вже ніхто не підтримує.

Де він вироджується

Репутація Angular як важкого стосується переважно перших двох тижнів. Навантаження понять справді зсунуте на початок — впровадження залежностей, декоратори, сигнали, RxJS, виявлення змін, — і розробник, що працював лише з React, буде повільнішим два тижні. Купує це п’ятий рік, схожий на перший. Коли кодова база на Angular таки вироджується, рідко підводить механізм фреймворку; це старша колонка таблиці, що живе далі, або можливість, використана всупереч своїй природі.

  • Підписки, які ніколи не завершують, тримають живими знищені компоненти і їхній DOM.
  • Виклики функцій у шаблонах виконуються на кожній перевірці, а не при зміні даних.
  • OnPush зі входами, зміненими на місці: компонент не оновлюється й виглядає випадково зламаним.
  • Effect-и, що задають сигнали, — це computed із вбудованою миттю незгоди.
  • Вкладені підписки замість switchMap повертають ті перегони, які оператор і мав прибрати.
  • Стан одного екрана в кореневому сервісі переживає навігацію й протікає між відвідуваннями.
  • bypassSecurityTrust… на вмісті, що прийшов від користувачів.
  • Новий код на NgModules і *ngIf — це предмет наступної міграції.
  • Версія поза вікном підтримки, де наступний бюлетень безпеки не матиме патча.

Чесна межа — у самій засаді фреймворку. Angular вирішує за вас, і це перевага лише доти, доки його рішення пасують вашій задачі. Переважно статичний контентний сайт платить за рантайм повного фреймворку, який йому не потрібен; невелика команда, що хоче щороку пробувати нову бібліотеку стану чи роутер, боротиметься з фреймворком, а не користуватиметься ним. А однорідність ріже в обидва боки: кожен застосунок на Angular успадковує той самий графік міграцій, хай є в команди на це час у цьому кварталі чи ні.

Практична перевірка — дві команди. ng version каже, на якому рядку таблиці релізів ви перебуваєте і чи ви у вікні підтримки. ng update показує, що на вас чекає, і запускає міграції, — а робити це на кожній мажорній версії, а не на кожній третій, — це різниця між одним днем і цілим проєктом.

Коли це застосовувати

Застосовуйте, коли

  • Велика організація з кількома командами, якій вигідно мати одну відповідь на питання.
  • Довгоживучий продукт, де міграції між мажорними версіями коштують реальних грошей.
  • Складні форми й узгоджені асинхронні потоки — найсильніша територія фреймворку.
  • Команда хоче TypeScript і впровадження залежностей як даність, а не як вибір.

Уникайте, коли

  • Малий проєкт чи прототип, де навантаження понять сплачується наперед і не окупається.
  • Команда, яка хоче зібрати власний стек, — вона боротиметься з фреймворком, а не користуватиметься ним.
  • Переважно статичний контентний сайт, де рантайм повного фреймворку не дає нічого.
  • Ніхто не відповідатиме за оновлення — версія, що відстала, за два роки втрачає патчі безпеки.

Джерела й метод

На чому стоїть ця стаття

  • 9 джерел
  • 1 задокументований збій
  • Перевірено 13 жовтня 2026 р.

Дослідження з Claude. Пошук і звірку першоджерел зроблено з Claude (Anthropic). Відбір, текст і перевірку фактів — редакція Anatomy of IT Product.

Було корисно?

Поділіться з тим, хто працює над тією ж задачею.