Angular без ZoneJS (беззонный режим)
Зачем использовать режим без ZoneJS?
Основные преимущества отказа от ZoneJS как зависимости:
- Повышение производительности: ZoneJS использует события DOM и асинхронные задачи как признаки того, что состояние приложения могло измениться, и затем запускает синхронизацию приложения для обнаружения изменений в его представлениях. ZoneJS не может определить, изменилось ли состояние приложения на самом деле, поэтому синхронизация запускается чаще, чем необходимо.
- Улучшение показателей Core Web Vitals: ZoneJS создаёт заметные накладные расходы как на размер полезной нагрузки, так и на время запуска.
- Упрощение отладки: ZoneJS затрудняет отладку кода. Со стеком вызовов при использовании ZoneJS сложнее разобраться. Также трудно понять, почему код перестаёт работать, если он выполняется за пределами зоны Angular.
-
Лучшая совместимость с экосистемой: ZoneJS работает, переопределяя API браузера, но не добавляет переопределения автоматически для каждого нового API браузера. Некоторые API нельзя эффективно переопределить, например
async/await, и для работы с ZoneJS их необходимо транспилировать в более старый синтаксис. Иногда библиотеки экосистемы также несовместимы со способом, которым ZoneJS переопределяет нативные API. Отказ от ZoneJS как зависимости обеспечивает лучшую долгосрочную совместимость, устраняя источник сложности, monkey patching и необходимости постоянного сопровождения.
Включение режима без ZoneJS в приложении
В Angular v21 и более поздних версиях режим без ZoneJS включён по умолчанию, поэтому для его включения ничего делать не нужно. Убедитесь, что нигде не используется provideZoneChangeDetection для переопределения конфигурации по умолчанию.
Если вы используете Angular v20, включите обнаружение изменений без ZoneJS, добавив provideZonelessChangeDetection() при запуске приложения:
bootstrapApplication(MyApp, {providers: [provideZonelessChangeDetection()]});
platformBrowser().bootstrapModule(AppModule);
@NgModule({
providers: [provideZonelessChangeDetection()],
})
export class AppModule {} Удаление ZoneJS
Чтобы уменьшить размер пакета, в приложениях без ZoneJS его следует полностью исключить из сборки. Обычно ZoneJS загружается с помощью параметра polyfills в angular.json как для цели build, так и для цели test. Удалите zone.js и zone.js/testing из обеих целей, чтобы исключить его из сборки. В проектах, использующих явный файл polyfills.ts, удалите import 'zone.js'; и import 'zone.js/testing'; из этого файла.
После удаления ZoneJS из сборки зависимость zone.js больше не нужна, поэтому пакет можно удалить полностью:
npm uninstall zone.js
Требования для совместимости с режимом без ZoneJS
Angular использует уведомления от основных API, чтобы определить, когда запускать обнаружение изменений и для каких представлений. К таким уведомлениям относятся:
-
ChangeDetectorRef.markForCheck(вызывается автоматически черезAsyncPipe) ComponentRef.setInput- Обновление сигнала, который считывается в шаблоне
- Обработчики привязанных событий хоста или шаблона
- Подключение представления, которое было помечено как требующее обновления одним из перечисленных выше способов
Компоненты, совместимые с OnPush
Один из способов убедиться, что компонент использует правильные механизмы уведомления, перечисленные выше, — применять ChangeDetectionStrategy.OnPush.
Стратегия обнаружения изменений OnPush не является обязательной, но её рекомендуется использовать для обеспечения совместимости компонентов приложения с режимом без ZoneJS. Компоненты библиотек не всегда могут использовать ChangeDetectionStrategy.OnPush. Если компонент библиотеки выступает хостом для пользовательских компонентов, которые могут использовать ChangeDetectionStrategy.Eager/Default, он не может использовать OnPush, поскольку это помешает обновлению дочернего компонента, если он несовместим с OnPush и полагается на ZoneJS для запуска обнаружения изменений. Компоненты могут использовать стратегию Default, если уведомляют Angular о необходимости запустить обнаружение изменений (вызывая markForCheck, используя сигналы, AsyncPipe и т. д.). Компонент выступает хостом для пользовательского компонента, если использует такой API, как ViewContainerRef.createComponent, а не просто отображает часть шаблона пользовательского компонента (например, через проекцию содержимого или входной параметр со ссылкой на шаблон).
Удалите NgZone.onMicrotaskEmpty, NgZone.onUnstable, NgZone.isStable или NgZone.onStable
Из приложений и библиотек необходимо удалить использование NgZone.onMicrotaskEmpty, NgZone.onUnstable и NgZone.onStable. Эти Observable никогда не будут выдавать значения, если в приложении включено обнаружение изменений без ZoneJS. Аналогично, NgZone.isStable всегда будет иметь значение true, поэтому его не следует использовать как условие для выполнения кода.
Observable NgZone.onMicrotaskEmpty и NgZone.onStable чаще всего используются, чтобы дождаться завершения обнаружения изменений Angular перед выполнением задачи. Вместо них можно использовать afterNextRender, если нужно дождаться одного цикла обнаружения изменений, или afterEveryRender, если условие может сохраняться на протяжении нескольких циклов обнаружения изменений. В других случаях эти Observable использовались потому, что были привычны и их временные характеристики подходили для нужной задачи. Вместо них можно использовать более простые или прямые API DOM, например MutationObserver, если коду нужно дождаться определённого состояния DOM (а не ждать его косвенно с помощью хуков рендеринга Angular).
NgZone.run и NgZone.runOutsideAngular совместимы с режимом без ZoneJS
NgZone.run и NgZone.runOutsideAngular не нужно удалять для обеспечения совместимости кода с приложениями без ZoneJS. Более того, удаление этих вызовов может привести к снижению производительности библиотек, используемых в приложениях, которые по-прежнему зависят от ZoneJS.
PendingTasks для серверного рендеринга (SSR)
Если вы используете SSR с Angular, то, возможно, знаете, что он полагается на ZoneJS, чтобы определить, когда приложение находится в «стабильном» состоянии и его можно сериализовать. Если есть асинхронные задачи, которые должны препятствовать сериализации, приложение, не использующее ZoneJS, должно сообщить о них Angular с помощью сервиса PendingTasks. Сериализация будет ждать момента, когда все ожидающие задачи будут завершены.
Для большинства случаев использования ожидающих задач достаточно метода run:
const taskService = inject(PendingTasks);
taskService.run(async () => {
const someResult = await doSomeWorkThatNeedsToBeRendered();
this.someState.set(someResult);
});
Для более сложных случаев можно вручную добавить и удалить ожидающую задачу:
const taskService = inject(PendingTasks);
const taskCleanup = taskService.add();
try {
await doSomeWorkThatNeedsToBeRendered();
} catch {
// handle error
} finally {
taskCleanup();
}
Кроме того, вспомогательная функция pendingUntilEvent в rxjs-interop обеспечивает нестабильное состояние приложения до тех пор, пока Observable не выдаст значение, не завершится, не выдаст ошибку или не будет отменена подписка.
readonly myObservableState = someObservable.pipe(pendingUntilEvent());
Фреймворк также использует этот сервис внутри себя, чтобы предотвратить сериализацию до завершения асинхронных задач. К ним относятся, помимо прочего, выполняющаяся навигация Router и незавершённый запрос HttpClient.
Реактивные формы в приложениях без ZoneJS
Обновления модели реактивных форм (setValue, patchValue, FormArray.push и аналогичные API) обновляют состояние формы и выдают значения через Observable формы, но автоматически не планируют обнаружение изменений компонента.
Если шаблон зависит от состояния реактивной формы, свяжите Observable формы с уведомлением об обнаружении изменений (например, с помощью ChangeDetectorRef.markForCheck()) или передавайте данные через сигналы, используемые шаблоном.
Тестирование и отладка
Использование режима без ZoneJS в TestBed
TestBed по умолчанию использует обнаружение изменений на основе Zone, если zone.js загружен через polyfills.
Если zone.js отсутствует, TestBed по умолчанию работает без ZoneJS. Чтобы принудительно включить режим без ZoneJS, когда загружен zone.js, добавьте provideZonelessChangeDetection():
TestBed.configureTestingModule({
// Optional: include the provider to force the testing environment
// uses the same zoneless behavior as a zoneless application.
providers: [provideZonelessChangeDetection()],
});
const fixture = TestBed.createComponent(MyComponent);
await fixture.whenStable();
Чтобы поведение тестов было максимально похоже на поведение производственного кода, по возможности не используйте fixture.detectChanges(). Это принудительно запускает обнаружение изменений в случаях, когда Angular мог бы не планировать его запуск. Тесты должны проверять, что эти уведомления отправляются, и позволять Angular самому определять, когда синхронизировать состояние, а не запускать синхронизацию вручную в тесте.
В существующих наборах тестов часто используется fixture.detectChanges(), и, скорее всего, нет смысла тратить усилия на их перевод на await fixture.whenStable(). TestBed по-прежнему будет проверять, что компонент фикстуры совместим с OnPush, и выбрасывать ExpressionChangedAfterItHasBeenCheckedError, если обнаружит, что значения шаблона изменились без уведомления об изменениях (то есть fixture.componentInstance.someValue = 'newValue';). Если компонент используется в производственном коде, эту проблему следует устранить, обновив компонент так, чтобы он использовал сигналы для хранения состояния или вызывал ChangeDetectorRef.markForCheck(). Если компонент используется только как тестовая оболочка и никогда не используется в приложении, допустимо использовать fixture.changeDetectorRef.markForCheck().
Проверка в режиме отладки, подтверждающая обнаружение обновлений
Angular также предоставляет дополнительный инструмент, который помогает убедиться, что приложение обновляет состояние совместимым с режимом без ZoneJS способом. provideCheckNoChangesConfig({exhaustive: true, interval: <milliseconds>}) можно использовать для периодической проверки того, что привязки не обновлялись без уведомления. Angular выбрасывает ExpressionChangedAfterItHasBeenCheckedError, если обнаруживает обновлённую привязку, которая не была бы обновлена при обнаружении изменений без ZoneJS.
Super-powered by Google ©2010–2025.
Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0.
https://angular.dev/guide/zoneless