Як працює Change Detection в Angular

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Привіт усім! Мене звати Олег. В ІТ я вже понад 15 років, із них половину працював з Angular. Однак мій поточний проєкт не передбачає його використання, тож час від часу освіжаю знання, щоб залишатися у формі. Під час чергового перечитування матеріалів про Angular помітив, що маю прогалини в розумінні механізму виявлення змін (change detection). Почав досліджувати цю тему та записувати свої спостереження — з часом ці нотатки перетворилися на повноцінну статтю. Оскільки я не знайшов схожих матеріалів, вирішив поділитися нею.

Я планую охопити всі аспекти циклу виявлення змін. Водночас деякі речі доведеться спростити, щоб зробити матеріал зрозумілішим. Також я підготував демо-застосунок, якщо вам цікаво поекспериментувати з механізмом виявлення змін, або ви можете переглянути код у репозиторії на GitHub.

Допомогати нам буде просте дерево компонентів із різними конфігураціями. Натискання на компонент запускає setInterval(), який оновлює й відображає змінни на екрані. Нижче наведено приклад коду компонента.

@Component({
  ...
})
class BlockDefaultComponent {
  count: null | number = null;

  triggerInterval() {
    // Initialize counter value
    this.count = 0;

    // Stop the previous interval if it exists
    clearInterval(this.intervalRef);

    // Start a new interval
    this.intervalRef = **setInterval**(() => {
      // Increment the counter by 1
      this.count = (this.count ?? 0) + 1;
    }, INTERVAL_DELAY);
  }
}

Стратегії виявлення змін

Почнімо зі стратегії виявлення змін Default, яка ілюструється на анімації нижче.

Анімація ілюструє процес виявлення змін при стратегії Default

Тут видно, як Angular оновлює кожен компонент у застосунку під час циклу виявлення змін.

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

Так, це не ідеально, але принаймні Angular виявляє та відображає зміни. А що буде, якщо setInterval() не змінює жодного значення?

triggerInterval() {
  // Stop the previous interval if it exists
  clearInterval(this.intervalRef);

  // Start a new interval
  this.intervalRef = setInterval(() => {
  
    // Do nothing
    
  }, INTERVAL_DELAY);
}

Анімація нижче ілюструє таку поведінку.

Анімація ілюструє поведінку виявлення змін при стратегії Default але відсутності самих змін

Навіть якщо setInterval() не змінює нічого, Angular все одно виконує повне оновлення дерева компонентів. Запуск виявлення змін по всьому застосунку може серйозно впливати на продуктивність, особливо в застосунках з великою кількістю асинхронних операцій або з великим деревом компонентів.

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

Команда Angular це знала й запропонувала рішення. Стратегія OnPush дозволяє розробникам «відрізати» або виключити деякі гілки дерева з процесу виявлення змін і вручну позначати, в якому компоненті сталася зміна. Подивімося, як це працює.

Анімація ілюструє поведінку виявлення змін при стратегії OnPush але без markForCheck()

Щось пішло не так — я не позначив компонент як «брудний» (dirty).

Я використовую фразу «позначити як брудний» (mark as dirty), бо вона краще відображає суть процесу. Хоча розробник викликає метод ChangeDetectorRef.markForCheck(), внутрішньо Angular викликає функцію markViewDirty(), яка позначає поточний компонент і всіх його батьків до самого кореня як брудні. Після цього Angular оновить ці компоненти під час наступного циклу виявлення змін.

Нижче показано приклад базового компонента зі стратегією OnPush.

@Component({
	...
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class BlockOnPushComponent {
  count: null | number = null;

  private changeDetectorRef = inject(ChangeDetectorRef)

  triggerInterval() {
    // Initialize counter value
    this.count = 0;

    // Stop the previous interval if it exists
    clearInterval(this.intervalRef);
    
    // Start a new interval
    this.intervalRef = setInterval(() => {
      // Increment the counter by 1
      this.count = (this.count ?? 0) + 1;

      // Mark the current component and all its ancestors as “dirty”
      this.changeDetectorRef.markForCheck();
    }, INTERVAL_DELAY);
  }
}

Подивімося, що відбувається під час виконання.

Анімація ілюструє поведінку виявлення змін при стратегії OnPush

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

Стратегія OnPush вимагає від розробника вручну вказувати, які компоненти потрібно оновити. Ви повинні явно викликати ChangeDetectorRef.markForCheck(), щоб позначити компонент, який потребує оновлення. Водночас Angular автоматично позначає компоненти в таких випадках:

  • змінилось вхідне значення (@Input);
  • відбулась подія, зв’язана з шаблоном (включаючи @Output або обробник події);
  • async пайп отримав нове значення;
  • зміна стану блоку @defer.

Це працює також і для стратегії Default.

Змішані стратегії виявлення змін

Давайте розглянемо, як змішання стратегій виявлення змін поводяться в різних сценаріях. Почнемо з поширеного випадку — коли частина застосунку переходить на стратегію OnPush.

Анімація ілюструє поведінку виявлення змін при змішані стратегій, частина на OnPush

Я запускаю дві асинхронні функції в різних компонентах. Як видно, їхні гілки оновлюються окремо, на відміну від компонентів зі стратегією Default, які оновлюються щоразу.

Тепер проаналізуймо, що відбувається, коли застосунок в основному використовує OnPush, але має деякі компоненти зі стратегією Default.

Анімація ілюструє поведінку виявлення змін при змішуванні стратегій, основа OnPush

Як і очікувалось, оновлюються лише компоненти, позначені як «брудні» (dirty). А як щодо компонентів, які використовують стратегію Default?

Анімація ілюструє поведінку виявлення змін при змішуванні стратегій, Default при основі OnPush

Погляньмо з висоти пташиного польоту, як Angular виконує виявлення змін для кожного компонента.

На відміну від процесу «позначення», виявлення змін починається з кореневого компонента і відвідує всіх нащадків у глибину (depth-first), дотримуючись наступних умов відвідування:

  • використовує стратегію Default (позначений як «перевіряти завжди»);
  • позначений як «брудний» (dirty);
  • позначений як «має нащадка для оновлення» (has child to refresh) або «оновити представлення (refresh view) — це я поясню трохи згодом.

Цей процес триває доти, доки Angular не відвідає всі компоненти, що відповідають цим критеріям.

Мій перший клік по компоненту [e] викликав подію, прив’язану до шаблону, яка запустила setInterval() і змусила Angular позначити цей компонент і його батьків як «брудні». Після завершення події Angular почав оновлювати всі позначені компоненти. Потім, коли спрацював setInterval(), процес виявлення змін почався, але одразу зупинився, оскільки кореневий компонент не відповідав умовам для відвідування.

Нічого не відбулося до наступного кліку на компонент [c], який знову викликав подію з шаблону, повторивши попередній процес. Однак цього разу Angular не зупинився на компоненті, який ініціював подію — його дочірній компонент також оновився, оскільки відповідав умовам відвідування.

Наступна анімація краще показує цей процес.

Анімація ілюструє поведінку виявлення змін при змішуванні стратегій, основа OnPush

Angular оновив компоненти [a], [b], [d] і [f]. Після кліку на компоненті [b], цей компонент і його батьківський [a] були позначені як «брудні». Процес виявлення змін почався з оновлення кореневого компонента [a], який був позначений як «брудний», далі оновився дочірній компонент [b]. Але на цьому процес не зупинився, оскільки компонент [d] використовує стратегію Default, яка відповідає умовам відвідування, і в результаті оновлюється також останній компонент [f], який також має стратегію Default.

Ручне виявлення змін

Тепер настав час поговорити про zone.js і сервіс ChangeDetectorRef.

Zone

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

Створимо нову зону та виконаємо setTimeout() у її межах:

// Create a new zone based on the current one,
// adding behavior when invoking operations (e.g., setTimeout).
const zone = Zone.current.fork({
  onInvokeTask: (delegate, current, ...params) => {
    console.log('Before setTimeout');
    // Execute the actual operation (e.g., setTimeout)
    delegate.invokeTask(...params); 
    console.log('After setTimeout');
  }
});

// Run a function within the created zone.
zone.run(() => {
  setTimeout(() => {
    console.log('Async operation');
  }, 1000);
});

Після виконання коду ми побачимо такий результат:

Before setTimeout
Async operation
After setTimeout

Angular завантажує zone.js у кожен застосунок і надає сервіс NgZone, який дозволяє керувати поточною зоною. Існує Observable об’єкт onMicrotaskEmpty, який надсилає повідомлення, коли черга мікрозавдань порожня. Angular використовує цей механізм, щоб визначити, коли всі асинхронні задачі завершено, і тоді безпечно запускати зміну стану (change detection).

Оригінальний код можна переглянути тут, а ось спрощена версія:

private zone = inject(**NgZone**);
private applicationRef = inject(ApplicationRef);

this.zone.**onMicrotaskEmpty**.subscribe(() => {
    this.zone.**run**(() => this.applicationRef.tick());
});

Angular надає два методи для ручного запуску виявлення змін:

  • ApplicationRef::tick()
  • ChangeDetectorRef.detectChanges()

У наведеному вище коді демонструється використання ApplicationRef::tick(), який, як ви могли здогадатись, запускає цикл виявлення змін по всьому застосунку. Angular виконує його без додаткової інформації — лише знаючи, що завершилась асинхронна операція.

Другий метод, ChangeDetectorRef.detectChanges(), синхронно запускає виявлення змін для конкретної гілки компонента (оновлюється сам компонент та компоненти під ним). До речі, цей метод також використовується всередині ApplicationRef::tick() тільки для кореневого компонента.

ChangeDetectorRef

Ми вже знаємо кілька методів, які надає ChangeDetectorRef, але є й інші корисні методи:

  • markForCheck() — позначає компонент як такий, що змінився, тож він буде перевірений під час наступного циклу.
  • detectChanges() — запускає процес виявлення змін для цього компонента та його дочірніх.
  • detach() — від’єднує компонент від дерева виявлення змін. Від’єднані компоненти не перевіряються, навіть якщо вони позначені як «брудні».
  • reattach() — повторно приєднує компонент до дерева виявлення змін.
@Component({
  ...
})
export class BlockDefaultDetectChangesComponent {
  count: null | number = null;

  private changeDetectorRef = inject(ChangeDetectorRef);
  private zoneRef = inject(NgZone);

  triggerInterval() {
    // Initialize counter value
    this.count = 0;

    // Detaches this component from the change-detection tree. 
    this.changeDetectorRef.detach();
    this.changeDetectorRef.detectChanges();

    // Stop the previous interval if it exists
    clearInterval(this.intervalRef);

    // Run the interval logic outside Angular's zone
    this.zoneRef.runOutsideAngular(() => {
      // Start a new interval
      this.intervalRef = setInterval(() => {
        // Increment the counter by 1
        this.count = (this.count ?? 0) + 1;

        // Manually trigger change detection since it's detached
        this.changeDetectorRef.detectChanges();
      }, INTERVAL_DELAY);
    });
  }

У коді вище компонент від’єднується від дерева виявлення змін при першому кліку. Саме тому ми повинні викликати detectChanges(), щоби відобразити зміни — адже detach() виключив цей компонент із глобального процесу.

Потім ми запускаємо setInterval() поза зоною Angular. Це необхідно, оскільки зона не знає, коли завершено асинхронну операцію, й не запустить зміну стану по всьому застосунку.

І насамкінець, виявлення змін для цього компонента вручну виконується під час кожного інтервалу, щоби відобразити нові значення.

Анімація нижче показує, як поводиться від’єднаний компонент.

Анімація ілюструє поведінку виявлення змін при detectChanges()

Мій перший клік видалив компонент [e] із дерева виявлення змін і запустив локальне виявлення змін. Angular також тригернув глобальне виявлення змін після кліку.

Другий клік на компоненті [g] оновив лише конкретні компоненти [a], [b], [c] і [d]. Компонент [e] — від’єднаний, тож Angular його і його дочірні пропускає — ми не бачимо змін у компоненті [g].

Оновлення в [g] відбулося пізніше, коли setInterval() з [e] виконав ChangeDetectorRef.detectChanges(), тригернувши зміну стану для компонента [e] та його дочірніх — за тими самими умовами, що й у глобальному циклі.

Натомість setInterval() у [g] запустив глобальний процес, бо працював у межах Angular-зони. Але оскільки [e] був від’єднаний, Angular пропустив його та дочірні компоненти.

Запуск від’єднаного компонента з усіма його дочірніми поза Angular-зоною — єдиний спосіб отримати кращий контроль над процесом виявлення змін.

Сигнали та виявлення змін

Тепер розгляньмо цикл виявлення змін у поєднанні з сигналами.

@Component({
    ...
  // changeDetection: ChangeDetectionStrategy.OnPush
})
export class BlockOnPushSignalComponent {
  count = signal<number | null>(null);

  triggerInterval() {
    // Initialize counter value
    this.count.set(0);

    // Stop the previous interval if it exists
    clearInterval(this.intervalRef);

    // Start a new interval
    this.intervalRef = **setInterval**(() => {
      // Increment the counter by 1
      this.count.update(i => (i ?? 0) + 1);
    }, INTERVAL_DELAY);
  }
}

Анімація нижче демонструє, як процес виявлення змін реагує на зміну сигналу в компонентах зі стратегією Default.

Анімація ілюструє поведінку виявлення змін в сигналах при стратегії Default

Цікавіша ситуація виникає, коли компонент із сигналом має стратегію OnPush, а кореневий компонент — Default.

Анімація ілюструє поведінку виявлення змін в сигналі при стратегії OnPush і Default

Усе виглядає як очікувалося, за винятком одного моменту, коли компонент [e] був відвіданий лише один раз.

Тепер заглибимось глибше. Щоб продемонструвати, як це працює, подивімося наступну анімацію. Вона показує ситуацію, коли кореневий компонент використовує стратегію OnPush, і ми взаємодіємо з компонентами зі стратегією Default, клікаючи на них.

Анімація ілюструє поведінку виявлення змін в сигналах при стратегії OnPush

Тут видно, що кореневий компонент [a] відвідується двічі — по одному разу на кожен клік. Наступний клік запускає виявлення змін для всіх компонентів, вкладених у компонент [b].

Як згадувалося раніше, компоненти можуть бути позначені як «has child to refresh» (має нащадка, який треба оновити) або «refresh view» (оновити представлення) у певних сценаріях. Як можна здогадатися, ця функція викликається під час оновлення сигналу. Це схоже на markViewDirty(), але з однією ключовою відмінністю: вона позначає сам компонент як «refresh view», а всіх його предків — як «has child to refresh».

Сигнали також приносять новий режим виявлення змін. Angular тепер має два режими: старий «Global» (глобальний) і новий «Targeted» (цільовий). У «Targeted» режимі Angular проходить тільки ті компоненти, які позначені як «has child to refresh», та оновлює ті, що мають мітку «refresh view». Компоненти з міткою «dirty» або ті, що використовують стратегію Default, ігноруються у цьому режимі.

«Targeted» режим вмикається, коли Angular не знаходить компонентів, які можна оновити, і вимикається, коли оновлено компонент із міткою «refresh view».

Код, який реалізує цю логіку, можна переглянути тут.

У чому різниця між «refresh view» і «dirty»? Angular завжди оновлює компоненти з міткою «refresh view», незалежно від активного режиму. У той час як «dirty» компоненти оновлюються лише в «Global» режимі.

Початковий клік по компоненту [d] викликав подію, пов’язану з шаблоном. Angular автоматично позначив компонент [d] та його предків [b] і [a] як «dirty». Цикл виявлення змін оновив усі ці компоненти.

Потім setInterval() змінив значення сигналу, і Angular позначив лише компонент [d] як «refresh view», а його батьків [b] і [a] — як «has child to refresh». Оскільки кореневий компонент має стратегію OnPush і не був «dirty», Angular перейшов у «Targeted» режим. Цей режим пропускає пряме оновлення компонентів «has child to refresh», натомість перевіряє їхніх нащадків на наявність мітки «refresh view». Зрештою, Angular оновив компонент [d].

Другий клік на компоненті [b] викликав подію шаблону і позначив усіх предків як «dirty», тому Angular оновив їх усіх.

Після того як setInterval() з компонента [b] змінив сигнал, Angular позначив компонент [b] як «refresh view», а його предка [a] як «has child to refresh». Цикл перейшов у «Targeted» режим, пройшовся по дітях компонента [a], виявив компонент [b] з міткою «refresh view», оновив його і повернувся до «Global» режиму. Потім Angular оновив компоненти [c] і [d], оскільки вони використовують Default і відповідають критеріям відвідування.

Наступна анімація показує, як зміни працюють краще зі стратегією OnPush.

Анімація ілюструє поведінку виявлення змін у сигналів при стратегії OnPush

Zoneless

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

Переваги видалення zone.js як залежності:

  • Покращена продуктивність: як ми вже знаємо, zone.js виявляє, коли Angular може знадобитися запустити цикл виявлення змін. Проте zone.js не може визначити, чи справді змінився стан, тому оновлення відбуваються частіше, ніж потрібно.
  • Покращені Core Web Vitals: zone.js додає певний обсяг до розміру файлу та збільшує час старту застосунку.
  • Полегшене дебагування: zone.js значно ускладнює процес відлагодження через глибоко вкладені stack traces.

У зонлес-режимі Angular надає внутрішній сервіс ChangeDetectionScheduler як альтернативу zone.js для планування циклів виявлення змін. Метод notify цього сервісу викликається функціями markViewDirty і markAncestorsForTraversal, які в свою чергу викликаються в різних ситуаціях.

Варто зазначити, що видалення зони не гарантує автоматичне покращення продуктивності в усіх випадках. Анімація нижче демонструє, як працює виявлення змін із сигналами, коли батьківські компоненти використовують Default.

Анімація ілюструє поведінку виявлення змін при стратегії OnPush і без zone.js

Стратегія Default нівелює переваги zoneless. Варто розуміти, що zoneless — це лише про те, як буде запущено цикл виявлення змін. Уся інша логіка циклу залишається незмінною. Тому стратегія OnPush у цьому випадку працюватиме краще. Це демонструє наступна анімація.

Анімація ілюструє поведінку виявлення змін при OnPush і без zone.js

Виявлення змін і ngOnCheck()

Наостанок хочу поділитися кількома думками щодо життєвого циклу ngOnCheck(). На анімації нижче жовтий фон означає, що компонент було оновлено, а червоний текст показує, що в компоненті був викликаний ngOnCheck().

Анімація ілюструє поведінку виявлення змін і виклики ngOnCheck() при стратегії OnPush

Як бачите, в деяких компонентах ngOnCheck() було викликано двічі.

Angular викликає ngOnCheck():

  • у самому компоненті — перед його оновленням;
  • у дочірніх компонентах — після оновлення батьківського компонента;

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

@Component({
 ...
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class BlockOnPushComponent implements DoCheck {
  // Assume some untracked changes in this object
  data: any = {}; 

  private previousData?: string;
  private changeDetectorRef = inject(ChangeDetectorRef)

  ngDoCheck(): void {
    // Check if the data has changed
    if (JSON.stringify(this.data) !== JSON.stringify(this.previousData)) {

      // Update snapshot
      this.previousData = structuredClone(this.data);

      // Marks the component for change detection
      this.changeDetectorRef.markForCheck();
      // or 
      // this.changeDetectorRef.detectChanges();
    }
  }
}

Це один із можливих варіантів використання. Переважно ми застосовуємо цей підхід, коли потрібно реалізувати власну логіку виявлення змін.

Тепер спростимо приклад і додамо логіку, яка примусово оновлює компоненти [b] і [d]. Подивимось на код нижче:

ngDoCheck(): void {
  // Check if the component is 'b' or 'd'
  if (['b', 'd'].includes(this.id)) {
    // Marks the component for change detection
    this.changeDetectorRef.markForCheck();
  }
}

Результат показано на анімації нижче.

Анімація ілюструє поведінку виявлення змін і виклики ngOnCheck() при стратегії OnPush з додатковими ngOnCheck()

Як бачите, Angular перевіряє компоненти [b] і [d] під час основного циклу виявлення змін.

Висновок

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

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

Для детального занурення в механізми виявлення змін рекомендую звернути увагу на наступні статті:

— A change detection, zone.js, zoneless, local change detection, and signals story 📚
— The Latest in Angular Change Detection — All You Need to Know
— Change Detection Big Picture — Overview
— Change detection and component trees in Angular applications

Ця стаття надихнула мене на створення анімацій:

— Angular Change Detection: Simplified Overview

👍ПодобаєтьсяСподобалось7
До обраногоВ обраному8
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Особлива подяка за анімовані ілюстрації. Чудова публікація! Я б почитав таких ще.

Дякую за коментар! У планах також є написати про Dependency Injection — як матиму трохи часу, обов’язково візьмусь.

Насамперед, вибачаюсь, якщо плутано — в мене досить невеликий досвід із Angular, але розкажу про свій перехід днями на zone-less approach. до речі, він існує вже ну років із 5, і досі експериментальний, як вважають самі розробники Angular.

Видалив zone.js, проапгрейдивши попередньо Angular до 19 версії.
zone-less навіть на досить нескладній відносно новій аплікусі (менше року) був доволі болячим.

1. все трекати самому, а не раніше як було — воно хз як там працює, не мої справи.
2. остаточно позбутися zone.js не вдалося тільки в jasmine тестах — деякі бібліотеки таки його тягнуть, як залежність, довелося його явним читом в test.js підключати, ну то таке.
3. враження — я задумався, як працює трекінг змін, нарешті
4. видалив polyfills, що трошки зменшує розмір білдів

Чому я це зробив — тому, що можу. і в мене раз на секунду реквести до бекенду, що трохи мені гальмувало реакцію всіляких там слайдерів, які я рендерю із даних свіжих.

друге — мені попереду ще писати певні компоненти на WebSockets, де все має бути realtime, ну під що, мені здається, zone.js буде гальмом тотальним.

Результат — не більше, як трошки швидше відклик. поки що.

В контексті тестів мабуть вже є сенс поглядувати на vitest який начебто не залежить від zonejs. Ангуляр збираються переходити на нього

дякую, дивлюся, здається — більш розумна альтернатива + typescript + швидкість.

поки проект молодий, здається, є сенс перемкнутися (122 юніт тести наразі)

все трекати самому

а при OnPush + signals (у всіх компонентів) це не теж саме? чи буде якись додатковий оверхед?

різниця в тому, що будуть апдейтитися тільки в’юхи на сигналах, а не увесь шаблон компоненту. тут, навпаки, немає оверхеду. це якщо я правильно зрозумів питання.

Як я вже згадував у статті, zone потрібна лише для запуску процесу пошуку зміни (change detection), але вона не впливає на сам механізм пошуку.
У випадку використання OnPush у поєднанні з signals в середовищі з активною zone, поведінка буде такою ж, як і при повністю відключеній zone. Різниця полягає лише в тому, хто саме запускає оновлення стану:

  • якщо zone увімкнена — пошук змін ініціює вона;
  • якщо ж zone вимкнена — за оновлення відповідатимуть самі сигнали.

Підписатись на коментарі