Что такое Change Detection в Angular и как она работает?
Change Detection (обнаружение изменений) — это механизм в Angular, обеспечивающий синхронизацию данных между компонентами и представлением (DOM). Он отвечает за то, чтобы при изменении данных в компоненте соответствующие изменения отображались в шаблоне и наоборот.
Основные принципы работы Change Detection
Angular использует унифицированную стратегию обнаружения изменений — zone-based change detection, основанную на библиотеке zone.js. Она патчит (перехватывает) асинхронные события (например, setTimeout, Promise, XHR и т. д.) и вызывает процесс проверки компонентов после любого потенциально изменяющего состояния действия.
Механизм проходит по дереву компонентов и сравнивает текущие значения привязанных данных с их предыдущими значениями. Если обнаружены отличия, Angular обновляет DOM.
Основные этапы цикла обнаружения изменений
-
Срабатывает асинхронное событие (например, клик, HttpClient, setTimeout, Promise).
-
zone.js перехватывает событие и инициирует цикл обнаружения изменений.
-
Angular проходит дерево компонентов от корня к листьям.
-
Для каждого компонента:
-
Выполняется метод ngDoCheck() (если реализован).
-
Вызываются все выражения в шаблоне (например, {{ value }}, [attr], *ngIf).
-
Angular сравнивает новые и старые значения.
-
Если значения изменились — обновляется DOM.
-
Change Detection Tree
Angular строит дерево компонентов и вызывает Change Detection рекурсивно от корневого компонента AppComponent вниз по иерархии. Каждый компонент — это узел в дереве.
Пример:
AppComponent
├── HeaderComponent
├── SidebarComponent
└── ContentComponent
├── PostComponent
└── CommentComponent
При срабатывании события Angular проверяет все дочерние компоненты каждого узла.
Стратегии обнаружения изменений
Angular поддерживает две стратегии Change Detection:
1. Default
-
Проверяются все компоненты в дереве, начиная от корня.
-
Используется по умолчанию.
-
Подходит для большинства случаев, но может быть неэффективной при большом количестве компонентов.
@Component({
changeDetection: ChangeDetectionStrategy.Default
})
2. OnPush
-
Angular проверяет компонент только в двух случаях:
-
Изменился @Input(), переданный в компонент (по ссылке).
-
Вручную вызван ChangeDetectorRef.markForCheck().
-
-
Используется для оптимизации производительности.
-
Требует иммутабельных данных или ручного контроля.
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
Пример работы Default стратегии
@Component({
selector: 'app-counter',
template: \`<button (click)="increment()">{{ count }}</button>\`
})
export class CounterComponent {
count = 0;
increment() {
this.count++;
}
}
-
При клике на кнопку zone.js перехватывает событие.
-
Angular запускает Change Detection.
-
Все компоненты в дереве проверяются, включая CounterComponent.
Пример с OnPush
@Component({
selector: 'app-item',
changeDetection: ChangeDetectionStrategy.OnPush,
template: \`{{ item.name }}\`
})
export class ItemComponent {
@Input() item!: { name: string };
}
-
Если в родительском компоненте сделать this.item.name = 'Новое имя' — Angular не отобразит изменения.
-
Нужно заменить весь объект: this.item = { name: 'Новое имя' }, чтобы Angular увидел новое значение по ссылке.
ChangeDetectorRef
Сервис ChangeDetectorRef предоставляет доступ к управлению Change Detection вручную.
Основные методы:
-
markForCheck() — помечает компонент и его родителей как нуждающиеся в проверке.
-
detectChanges() — запускает Change Detection для текущего компонента и его детей.
-
detach() — отключает компонент от дерева изменений (больше не обновляется автоматически).
-
reattach() — подключает обратно.
constructor(private cd: ChangeDetectorRef) {}
ngAfterViewInit() {
setTimeout(() => {
this.cd.detectChanges(); // вручную запускаем CD
});
}
Примеры ситуаций, когда Angular НЕ обнаружит изменения
- Изменение объекта по ссылке без его пересоздания:
this.obj.value = 42; // Angular не увидит это в OnPush
Решение:
this.obj = { ...this.obj, value: 42 };
- Мутация массивов:
this.arr.push('новый элемент'); // не работает
this.arr = \[...this.arr, 'новый элемент'\]; // работает
Примеры применения detach()
ngOnInit() {
this.cd.detach(); // отключить CD
}
updateManually() {
this.data = this.service.getData();
this.cd.detectChanges(); // вручную обновить
}
Используется для оптимизации при дорогих вычислениях или большом объёме DOM.
Особенности работы с асинхронными данными
При использовании async пайпа Angular сам подписывается на Observable/Promise и вызывает markForCheck() при получении новых значений. Это делает работу с OnPush проще:
<div \*ngIf="user$ | async as user">
{{ user.name }}
</div>
Change Detection в связке с ngZone
Можно полностью выйти из зоны Angular с помощью NgZone.runOutsideAngular() — это отключит запуск Change Detection, пока мы не вернёмся в зону:
constructor(private zone: NgZone) {}
runHeavyTask() {
this.zone.runOutsideAngular(() => {
// долгий процесс
doSomething();
// вернуть в Angular-зону и обновить
this.zone.run(() => {
this.cd.detectChanges();
});
});
}
Общая картина:
Событие | Запускает CD? | Angular обновит DOM? |
---|---|---|
(click) | Да | Да |
--- | --- | --- |
setTimeout() | Да | Да |
--- | --- | --- |
Observable.subscribe() | Да (если в зоне) | Да |
--- | --- | --- |
@Input() обновился | Да | Да |
--- | --- | --- |
Объект мутирован по ссылке | Нет (в OnPush) | Нет |
--- | --- | --- |
Новый объект присвоен | Да (в OnPush) | Да |
--- | --- | --- |
Change Detection — это центральный механизм реактивного обновления DOM в Angular. Он обеспечивает корректное отображение данных, но требует понимания принципов работы для оптимизации производительности, особенно при использовании стратегии OnPush. Управление изменениями вручную с помощью ChangeDetectorRef и NgZone позволяет разрабатывать высокоэффективные и масштабируемые приложения.