# Angular 19 Zoneless: Продуктивність та Виявлення Змін Без Zone.js > Повний посібник з Angular zoneless change detection: видалення Zone.js, перехід на сигнали, налаштування provideZonelessChangeDetection, оптимізація SSR, міграція існуючих застосунків та підготовка до технічних співбесід з Angular. - Published: 2026-04-27 - Updated: 2026-04-27 - Author: SharpSkill - Tags: angular, zoneless, change-detection, performance, signals, zone-js - Reading time: 10 min --- Zoneless change detection в Angular являє собою найбільш значущу архітектурну зміну фреймворку з моменту впровадження standalone-компонентів. Повне видалення Zone.js з застосунку забезпечує зменшення розміру бандла приблизно на 33 КБ, скорочення зайвих циклів виявлення змін на 30-40% та чисті стек-трейси без специфічного шуму Zone.js. > **Хронологія Zoneless** > > Angular 18 представив zoneless у статусі experimental. Angular 19 вдосконалив експериментальний API через `provideExperimentalZonelessChangeDetection()`. Angular 20 перевів його у стабільний статус як `provideZonelessChangeDetection()`. Angular 21 зробив zoneless режимом за замовчуванням для нових проєктів. ## Як працює виявлення змін через Zone.js під капотом Перед розглядом zoneless-підходу варто детально розібрати механізм роботи Zone.js. Ця бібліотека перехоплює (monkey-patch) кожен асинхронний API браузера: `setTimeout`, `setInterval`, `Promise.then`, `addEventListener`, `XMLHttpRequest` та десятки інших. Щоразу, коли один з цих API завершує виконання, Zone.js повідомляє Angular, який запускає цикл виявлення змін по всьому дереву компонентів. Такий підхід має фундаментальний недолік: Zone.js не має інформації про те, чи дійсно змінився стан застосунку. Виклик `setTimeout`, який використовується виключно для тайміну анімації, все одно ініціює повний цикл виявлення змін. У великих застосунках з сотнями компонентів ці накладні витрати стають відчутними. ```typescript // app.config.ts - Traditional Zone.js setup (Angular 18-19) import { ApplicationConfig } from '@angular/core'; import { provideZoneChangeDetection } from '@angular/core'; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), // Zone.js patches ~130+ browser APIs // Every async callback triggers change detection ] }; ``` Об'єднання подій (event coalescing), впроваджене в Angular 14, частково зменшує накладні витрати шляхом групування кількох подій в один цикл виявлення змін. Проте фундаментальна проблема залишається: виявлення змін запускається значно частіше, ніж це необхідно. ## Увімкнення Zoneless Change Detection в Angular 19 та 20 Шлях міграції відрізняється залежно від версії Angular. Angular 19 використовує експериментальний API, тоді як Angular 20 надає стабільну версію. ```typescript // app.config.ts - Angular 19 (experimental) import { ApplicationConfig } from '@angular/core'; import { provideExperimentalZonelessChangeDetection } from '@angular/core'; export const appConfig: ApplicationConfig = { providers: [ provideExperimentalZonelessChangeDetection(), // No more Zone.js patching ] }; ``` ```typescript // app.config.ts - Angular 20+ (stable) import { ApplicationConfig } from '@angular/core'; import { provideZonelessChangeDetection } from '@angular/core'; export const appConfig: ApplicationConfig = { providers: [ provideZonelessChangeDetection(), ] }; ``` Після зміни провайдера необхідно видалити `zone.js` з масиву polyfills у файлі `angular.json` для цілей build та test, а потім деінсталювати пакет: ```bash # Remove zone.js polyfill from angular.json build and test targets # Then uninstall the package npm uninstall zone.js ``` Зменшення розміру бандла відбувається миттєво: Zone.js займає приблизно 33 КБ у сирому вигляді (10 КБ у gzip) коду, що завантажується негайно. ## Що ініціює виявлення змін у Zoneless-режимі Без перехоплення кожної асинхронної операції засобами Zone.js, Angular покладається на явні сповіщення. Фреймворк планує виявлення змін при виникненні будь-якої з наступних умов: - Сигнал, прочитаний у шаблоні, оновлює своє значення - Викликається `ChangeDetectorRef.markForCheck()` (автоматично через `AsyncPipe`) - Вхідний параметр компонента змінюється через `ComponentRef.setInput()` - Виконується callback обробника подій шаблону або хоста (click, input тощо) - Раніше позначене як "брудне" (dirty) представлення приєднується до дерева компонентів ```typescript // counter.component.ts - Signal-driven change detection import { Component, signal, computed } from '@angular/core'; @Component({ selector: 'app-counter', template: `
{{ count() }}

Double: {{ doubled() }}

` }) export class CounterComponent { // Signal updates automatically notify the template count = signal(0); doubled = computed(() => this.count() * 2); increment() { this.count.update(v => v + 1); // No markForCheck() needed - signal handles notification } decrement() { this.count.update(v => v - 1); } } ``` Сигнали є природним доповненням zoneless-режиму. Коли значення сигналу змінюється, Angular точно знає, які шаблони залежать від нього, і планує цілеспрямоване виявлення змін лише для цих представлень. > **OnPush рекомендовано, але не обов'язково** > > Застосування `ChangeDetectionStrategy.OnPush` є рекомендованим кроком до zoneless-сумісності, але не є суворо обов'язковим. Стратегія виявлення змін за замовчуванням продовжує працювати у zoneless-режимі. Водночас OnPush гарантує, що компоненти перерендерюються лише при зміні вхідних параметрів або виклику `markForCheck()`, що природно узгоджується з ментальною моделлю zoneless. ## Міграція існуючих застосунків: типові помилки Перехід від Zone.js до zoneless рідко зводиться до зміни одного рядка коду для діючих застосунків. Декілька патернів, що покладалися на неявну поведінку Zone.js, потребують явної обробки. ### setTimeout та setInterval більше не ініціюють оновлення У режимі Zone.js callback-функції `setTimeout` автоматично ініціювали виявлення змін. У zoneless-режимі цього не відбувається. ```typescript // user-status.component.ts - Before: relies on Zone.js @Component({ selector: 'app-user-status', template: `{{ statusMessage }}` }) export class UserStatusComponent { statusMessage = 'Loading...'; ngOnInit() { setTimeout(() => { // Zone.js would trigger CD here - zoneless does NOT this.statusMessage = 'Ready'; }, 2000); } } ``` ```typescript // user-status.component.ts - After: signal-based approach import { Component, signal } from '@angular/core'; @Component({ selector: 'app-user-status', template: `{{ statusMessage() }}` }) export class UserStatusComponent { statusMessage = signal('Loading...'); ngOnInit() { setTimeout(() => { // Signal update notifies Angular automatically this.statusMessage.set('Ready'); }, 2000); } } ``` ### Реактивні форми потребують явного сповіщення Зміни стану форм через `FormControl.setValue()` або `patchValue()` не ініціюють автоматичне виявлення змін у zoneless-режимі. Два підходи вирішують цю проблему: підключення Observable-потоків форми до `markForCheck()` або відображення даних форми через сигнали. ```typescript // search.component.ts - Reactive forms with zoneless import { Component, inject, signal } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { toSignal } from '@angular/core/rxjs-interop'; import { debounceTime, distinctUntilChanged } from 'rxjs'; @Component({ selector: 'app-search', imports: [ReactiveFormsModule], template: `

Results for: {{ searchTerm() }}

` }) export class SearchComponent { searchControl = new FormControl(''); // Convert observable to signal for automatic template updates searchTerm = toSignal( this.searchControl.valueChanges.pipe( debounceTime(300), distinctUntilChanged() ), { initialValue: '' } ); } ``` ## Серверний рендеринг без Zone.js SSR із zoneless Angular потребує особливої уваги. Zone.js раніше допомагав Angular визначати, коли застосунок досягнув "стабільного" стану, придатного для серіалізації. Без нього цю роль виконує сервіс `PendingTasks`. ```typescript // data-loader.component.ts - SSR-compatible async loading import { Component, inject, signal } from '@angular/core'; import { PendingTasks } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-data-loader', template: ` @if (data()) {
{{ data()!.title }}
} @else {
Loading...
} ` }) export class DataLoaderComponent { private http = inject(HttpClient); private pendingTasks = inject(PendingTasks); data = signal<{ title: string } | null>(null); ngOnInit() { // PendingTasks.run() prevents SSR serialization until complete this.pendingTasks.run(async () => { const result = await firstValueFrom( this.http.get<{ title: string }>('/api/data') ); this.data.set(result); }); } } ``` Без використання `PendingTasks` Angular серіалізує сторінку до завершення завантаження асинхронних даних, відправляючи клієнту порожній контент. ## Бенчмарки продуктивності: Zone.js проти Zoneless Покращення продуктивності від видалення Zone.js поділяються на три категорії: | Метрика | Zone.js | Zoneless | Покращення | |--------|---------|----------|-------------| | Початковий розмір бандла | +33 КБ raw / +10 КБ gzip | 0 КБ накладних витрат | 100% зменшення | | Цикли виявлення змін (типовий застосунок) | 150-300 на взаємодію | 5-15 на взаємодію | На 80-95% менше | | Глибина стек-трейсу | 8-12 додаткових Zone-фреймів | Чисті нативні трейси | Миттєва ясність | | Time to Interactive (TTI) | Базовий рівень | На 15-25% швидше | Усунено завантаження Zone | Найбільш суттєве покращення спостерігається у застосунках з інтенсивною асинхронною активністю: HTTP-поллінг, WebSocket-з'єднання, таймери та складні обробники подій. Кожен з цих механізмів раніше ініціював непотрібні цикли виявлення змін, які zoneless-режим повністю усуває. > **Сумісність бібліотек** > > Деякі сторонні бібліотеки досі залежать від Zone.js внутрішньо. Модальні діалоги, окремі обгортки Web Components та деякі бібліотеки анімацій можуть поводитися непередбачувано в zoneless-режимі. Необхідно ретельно тестувати інтеграції бібліотек перед розгортанням zoneless у продакшн. ## API NgZone, які залишаються після переходу Поширена помилка: видалення Zone.js означає видалення усіх посилань на `NgZone`. Це невірно. `NgZone.run()` та `NgZone.runOutsideAngular()` залишаються сумісними із zoneless-застосунками і мають зберігатися у спільних бібліотеках. Їх видалення може спричинити регресію продуктивності у застосунках, які досі використовують Zone.js та споживають ці бібліотеки. Проте три Observable-потоки `NgZone` необхідно усунути: - `NgZone.onMicrotaskEmpty` — ніколи не спрацьовує у zoneless-режимі - `NgZone.onUnstable` — ніколи не спрацьовує у zoneless-режимі - `NgZone.onStable` — ніколи не спрацьовує у zoneless-режимі Логіку, залежну від таймінгу та побудовану на цих Observable-потоках, слід замінити на `afterNextRender()` або `afterEveryRender()` з `@angular/core`. ## Від Angular 19 Experimental до Angular 21 за замовчуванням Еволюція zoneless API має чітку траєкторію стабілізації: - **Angular 18.1**: `provideExperimentalZonelessChangeDetection()` представлено як experimental - **Angular 19**: Експериментальний API вдосконалено, розширене тестування екосистеми - **Angular 20**: Перейменовано на `provideZonelessChangeDetection()`, переведено у стабільний статус - **Angular 20.2**: API повністю стабільний, без очікуваних змін поведінки - **Angular 21**: Zoneless стає режимом за замовчуванням для `ng new`, виклик провайдера не потрібен Для команд на Angular 19 шлях оновлення є прямолінійним: оновлення до Angular 20, заміна `provideExperimentalZonelessChangeDetection` на `provideZonelessChangeDetection` та видалення polyfill `zone.js`. Підтримка Angular 19 завершується 19 травня 2026 року, що робить цю міграцію терміновою. Підготовка до співбесід з Angular? Модуль [питань для співбесід з Angular](/technologies/angular/interview-questions/change-detection) на SharpSkill детально охоплює патерни виявлення змін, включаючи zoneless-сценарії, про які все частіше запитують на інтерв'ю. Для загального огляду варто ознайомитись з посібником [топ-25 питань для Angular-співбесід](/blog/angular/top-25-angular-interview-questions). Модуль [Angular Signals](/technologies/angular/interview-questions/angular-signals) також розкриває модель реактивності, яка робить zoneless можливим. ## Підсумки - Видалення Zone.js усуває 33 КБ ваги бандла та прибирає шар monkey-patching, який перехоплював понад 130 API браузера - Angular-сигнали забезпечують явну модель реактивності, яка робить zoneless практичним, автоматично сповіщаючи шаблони при зміні стану - Міграція вимагає перетворення патернів `setTimeout`/`setInterval`, реактивних форм та логіки, залежної від таймінгу, на підходи на основі сигналів або `markForCheck()` - SSR-застосунки мають впровадити `PendingTasks` для заміни механізму визначення стабільності Zone.js - `NgZone.run()` та `NgZone.runOutsideAngular()` слід зберігати у спільних бібліотеках для зворотної сумісності - Експериментальний API Angular 19 (`provideExperimentalZonelessChangeDetection`) безпосередньо відповідає стабільному `provideZonelessChangeDetection()` Angular 20, що робить оновлення операцією перейменування - Сумісність сторонніх бібліотек залишається основним фактором ризику і потребує ретельного тестування перед розгортанням у продакшн --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/angular/angular-19-zoneless-change-detection-performance