Полная миграция на Signal-компоненты в Angular 23: Освободить проект от Zone.js
Ваш Angular-проект всё ещё работает с zone.js. Это значит, каждый пользовательский клик, таймер или HTTP-запрос запускает дорогой цикл проверки изменений, который проходится по всему дереву компонентов. Это приводит к избыточной работе движка рендеринга, сложностям в отладке и падению производительности на сложных формах и дашбордах. Ситуация усугубляется, когда команда использует обновлённый Angular с сигналами (Signals), но оставляет старый механизм обнаружения изменений, упуская весь потенциал новой реактивной модели.
В этой статье вы получите пошаговый, прагматичный план полной миграции. Мы уберём зависимость от zone.js, переведём все компоненты на сигналы и получим приложение с предсказуемым рендерингом, где обновляется только то, что действительно изменилось. Вы сэкономите ресурсы сервера, улучшите отзывчивость интерфейса и подготовите код к стандартам 2026 года, где реактивность на основе сигналов станет доминирующей парадигмой в Angular-экосистеме.
Что меняет отказ от Zone.js на фундаментальном уровне
Zone.js был гениальным компромиссом. Он создавал контекст выполнения вокруг асинхронных операций (событий, setTimeout, промисов) и по их завершению запускал глобальную проверку изменений для всего приложения. Это избавляло разработчиков от ручного указания, когда нужно обновить представление, но влекло за собой «грязные проверки» и избыточные вычисления.
Сигналы предлагают другой контракт. Когда вы объявляете состояние через signal(), вы создаёте отслеживаемый источник истины. Компоненты и вычисления, которые используют этот сигнал (через эффекты или привязку в шаблоне), автоматически подписываются на его изменения. Когда сигнал обновляется через set() или update(), Angular точно знает, какие компоненты затронуты, и перерисовывает только их. Это точечное, гранулярное обновление.
Отключение Zone.js означает, что не будет никакого автоматического триггера для проверки изменений после асинхронного события. Только обновление сигнала вызовет перерисовку. Это требует более дисциплинированного, явного управления состоянием, но взамен даёт полный контроль над рендерингом и радикально сокращает ненужную работу.
Типичный сценарий: Сложная форма с динамической валидацией
Представьте форму заказа с 20 полями. Поля зависят друг от друга: выбор способа доставки меняет список городов, ввод промокода пересчитывает итоговую сумму. С Zone.js каждое нажатие клавиши в любом поле запускает проверку для всей компонентной модели формы. С сигналами вы создаёте:
- Сигнал для каждого поля формы: customerName = signal(»).
- Вычисляемый сигнал для итоговой суммы: total = computed(() => basePrice() + deliveryCost() — discount()).
- Вычисляемый сигнал для списка городов: availableCities = computed(() => getCitiesForDelivery(deliveryMethod())).
При изменении deliveryMethod обновится только сигнал availableCities и те элементы шаблона, которые от него зависят (например,
Пошаговый план миграции существующего проекта
Резкий переход «одним днём» рискован. Следуйте итеративному подходу.
Шаг 1: Подготовка и настройка среды
Убедитесь, что вы используете Angular 22.2 или выше. В angular.json добавьте опцию для новой сборки без Zone.js. Это можно сделать постепенно, создав дополнительную конфигурацию.
"build": {
"configurations": {
"no-zones": {
"fileReplacements": [
{
"replace": "src/polyfills.ts",
"with": "src/polyfills-no-zones.ts"
}
]
}
}
}
В файле polyfills-no-zones.ts просто не импортируйте ‘zone.js’. Также в src/main.ts при запуске с этой конфигурацией нужно отключить Zone, передав опцию в bootstrapApplication.
Шаг 2: Постепенная замена @Input и @Output
Начните с «тупых» презентационных компонентов. Вместо декоратора @Input() используйте input() из ‘@angular/core’. Это преобразует свойство в сигнал.
Распространённая ошибка: попытка изменить значение входного сигнала внутри компонента-потомка. Это антипаттерн. Входной сигнал должен оставаться «readonly». Если нужно модифицированное значение, создайте на его основе вычисляемый сигнал внутри компонента.
Пример:
// Было
@Component({...})
export class ButtonComponent {
@Input() label: string = '';
}
// Стало
@Component({...})
export class ButtonComponent {
label = input.required(); // Signal
// Для дефолтных значений используйте input('default')
}
Для событий заменяйте @Output() на output() и используйте метод emit.
Шаг 3: Рефакторинг состояния компонента на сигналы
Это самый объёмный этап. Найдите все поля класса компонента, которые относятся к состоянию и используются в шаблоне. Преобразуйте их в сигналы.
- Локальное состояние: isLoading = signal(false).
- Массивы данных: items = signal
- ([])
. - Результаты вычислений: filteredItems = computed(() => …).
Note: Не превращайте в сигналы всё подряд. Вспомогательные методы, константы, инжектированные сервисы должны остаться обычными полями. Сигналы — только для реактивного состояния.
Шаг 4: Работа с асинхронными операциями и сервисами
Без Zone.js асинхронные операции не запускают рендер. После HTTP-запроса вам нужно вручную обновить сигнал.
Обычный паттерн в сервисе:
@Injectable({providedIn: 'root'})
export class DataService {
private readonly http = inject(HttpClient);
// Состояние загружаемых данных храним как сигнал в сервисе
private _users = signal([]);
// Предоставляем наружу только readonly версию
public readonly users = this._users.asReadonly();
public readonly loading = signal(false);
loadUsers() {
this.loading.set(true);
this.http.get('/api/users').subscribe({
next: (data) => {
this._users.set(data); // Обновление сигнала вызовет реакцию
this.loading.set(false);
},
error: () => this.loading.set(false)
});
}
}
В компоненте вы просто вызываете dataService.loadUsers() и в шаблоне привязываетесь к dataService.users() и dataService.loading().
Ответы на ключевые вопросы разработчиков
Вопрос: Смогу ли я использовать сторонние библиотеки компонентов (например, Angular Material) без Zone.js?
Да, большинство современных библиотек уже адаптированы. Они используют сигналы внутри или совместимы с ChangeDetectionStrategy.OnPush, который хорошо работает в среде без Zone.js. Проверьте документацию библиотеки на предмет поддержки «zoneless» режима. Если библиотека критически зависит от Zone.js, её компоненты будут работать, но могут не обновляться автоматически — вам нужно будет обернуть их в свой компонент с сигналами.
Вопрос: Как теперь работать с формами (Reactive Forms)?
Reactive Forms модуль пока тесно связан с Zone.js. В краткосрочной перспективенеобходимо либо использовать паттерн «сигнального состояния» для формы (управляя каждым полем через свой сигнал и связывая с нативным через [value] и (input)), либо обернуть FormGroup в адаптер, который будет обновлять сигналы при изменениях формы. Команда Angular работает над нативной интеграцией сигналов и форм, и к 2026 году это станет стандартным подходом.
Аудит производительности и замеры выигрыша
После миграции одного крупного модуля проведите замеры. Используйте Angular DevTools Profiler.
- Зафиксируйте количество циклов детекта изменений для типичного сценария (например, ввод в поле поиска). При правильной миграции их должно быть 0 — обновления будут проходить через механизм сигналов.
- Измерьте время отклика интерфейса (First Input Delay, Interaction to Next Paint) в Lighthouse. Гранулярные обновления должны улучшить эти метрики.
- Следите за размером бандла: удаление zone.js (около 30 КБ gzipped) даёт мгновенный выигрыш.
Реальный кейс: после миграции дашборда с 50+ интерактивными графиками команда отметила сокращение лишних вычислений на 70% и увеличение FPS в сложных анимациях с 25 до стабильных 60.
Ваш следующий конкретный шаг
Не откладывайте. Сегодня же создайте в своём проекте отдельную ветку git и новую конфигурацию сборки без Zone.js, как показано выше. Соберите проект и запустите его. Вы увидите множество ошибок — это нормально. Выберите один небольшой, изолированный «умный» компонент (например, страницу профиля) и переведите его на сигналы полностью, следуя плану из этой статьи. Измерьте сложность работы и время, затраченное на компонент. Этот эксперимент даст вам точное понимание масштаба задачи для всего проекта и сформирует внутренний чек-лист миграции, уникальный для вашей кодовой базы. С этого первого шага начинается путь к более быстрому и предсказуемому приложению.

















