In-Depth Look at Angular's Change Detection, Mode Switching and DOM Traversal

In-Depth Look at Angular's Change Detection, Mode Switching and DOM Traversal

Overview

In this document, we will build upon our previous discussion of Angular's change detection system, which helps synchronize the UI with the app's internal state. By referring to the concepts outlined in the last documentation, we can gain a complete understanding of how Angular detects changes and updates the DOM.

Recap of Previous Documentation

In the previous document, we covered:

  • Triggers of Change Detection: The role of Zone.js in automatically detecting changes through asynchronous events and how developers can manually control detection using methods like markForCheck(), signals(), and set().
  • Change Detection Strategies: We looked at the two main strategies, the Default Strategy and the OnPush Strategy.
  • DOM Update Mechanism: We discussed how Angular traverses the component tree during change detection, ensuring the UI is always in sync with the data.

Expanding on Change Detection Algorithms

In this documentation, we will dive deeper into how Angular marks a component as dirty and how it uses Global and Targeted change detection algorithms to optimize the process of updating the DOM.


Marking a Component as Dirty

Angular identifies when part of the UI needs to be synchronized with the DOM by marking the component as dirty. There are three ways Angular determines a component’s dirtiness:

  1. Marked for Check: When a component calls markForCheck(), the operation propagates upwards through the component tree. The root component gets marked for check, and each parent and child component is checked all the way down the tree to ensure the necessary updates are applied.
  2. Maybe Dirty via Signal: Angular tracks the state of components without needing to follow the entire traversal path. If a component’s state changes or a signal is emitted, Angular doesn’t traverse all the way from the top; it only needs to run the necessary checks in order.
  3. Contains Dirtiness: Angular keeps track of when a parent component may not be dirty itself but contains dirty parts (e.g., child components, effects, or render hooks). This ensures that necessary updates are applied, even when a parent component itself doesn't require an update.

By doing this, Angular now knows the application’s dirtiness state and what operations need to happen at the application level (e.g., effects, rendering, and after-render hooks). This allows for fine-grained control over what needs to be updated in the UI.

Here’s a simplified version of how Angular performs the change detection check at the top level:

while(dirty) {
    if (dirty & Dirty.RootEffects) runRootEffects();
    if (dirty & Dirty.Views) checkViews();
    
    // If there are still dirty views, loop back
    if (dirty & Dirty.Views) continue;
    if (dirty & Dirty.RenderHooks) runRenderHooks();
}        

This loop ensures that change detection is performed as long as there are dirty components or operations. The traversal occurs in cycles, ensuring no part of the tree is missed, and it handles cases where lifecycle hooks or signals might mark additional components as dirty.


How Do We Check Views?

Angular checks views using two modes: Global Mode and Targeted Mode.

  1. Global Mode (Default): Check if the component is not using the OnPush strategy. Angular checks all components that are marked dirty or do not have OnPush.
  2. Targeted Mode (OnPush): Check if the component is explicitly marked dirty or if it has active signals or state changes.

These two modes allow Angular to prune parts of the component tree that do not require change detection, improving performance by skipping checks on components that cannot have updates.

Following this approache change detection will scale based on the number of changes, not the size of the application.

Global Mode vs. Targeted Mode (Mode Switching)

Angular optimizes change detection by switching between Global Mode (Default Strategy) and Targeted Mode (OnPush Strategy) as needed during the traversal of the component tree. This ensures that only relevant components are checked, and the application remains performant, especially in large apps.

The modes switch during traversal:

  • Global Mode: When Angular detects a change in a component that is not marked with OnPush, it checks the component and all of its children (unless explicitly excluded).
  • Targeted Mode: When a component is in OnPush Mode, Angular only checks that component if its inputs change or if it is explicitly marked dirty (e.g., via markForCheck() or if the component emits a signal).

As Angular traverses from parent to child, it switches between these modes based on the conditions:

  1. Global to Targeted: When a Global Mode component (e.g., Default Strategy) encounters a child component that uses OnPush, Angular switches to Targeted Mode for the child, checking only if the component is dirty or has active signals.
  2. Targeted to Global: When an OnPush component is marked as dirty (via signals or state changes), Angular switches to Global Mode to check the child components, even if they are in OnPush Mode.

This mode switching mechanism allows Angular to minimize unnecessary checks and improve performance by limiting the number of components visited during change detection.


Mode Switching in Practice: Examples

Example 1: Global Mode to Targeted Mode

Consider an application where you have a parent component and a child component. The parent component is set to use the Default Change Detection Strategy (which is Global Mode), and the child component is set to use the OnPush Change Detection Strategy (which is Targeted Mode).

Scenario:

  • The parent component is triggered by an event (e.g., a button click or a network response) and gets marked as dirty.
  • Because the parent component is using the Global Mode, Angular will check the parent and its children during the change detection cycle.
  • Since the child component uses the OnPush Strategy, Angular will skip checking the child unless: The child’s inputs have changed. The child component is marked for check using markForCheck().The child component is dirty (which can happen if the parent triggers a signal or observable that affects the child).

Thus, when Angular encounters the parent in Global Mode, it switches to Targeted Mode for the child. This allows the child component to be checked only if necessary (i.e. if it is dirty or marked for check).

Flow:

  1. The parent is dirty and checked in Global Mode.
  2. The child component is skipped unless inputs are changed, it is marked dirty, or a signal is emitted, at which point it is checked in Targeted Mode.


Example 2: Targeted Mode to Global Mode

Let’s consider another example where we have a parent component using the OnPush Change Detection Strategy (Targeted Mode) and a child component using the Default Strategy (Global Mode).

Scenario:

  • The parent component is in OnPush Mode and is not marked dirty. It doesn't require any change detection unless its inputs change or it is explicitly marked dirty (via signals or markForCheck()).
  • The child component, on the other hand, is using the Default Strategy (Global Mode), so Angular will check it every time the parent is checked.

Let’s say the parent component's state triggers a signal, which makes the child component’s state dirty. Angular, upon encountering the child, switches to Global Mode to check the child component (because the child component is using the Default Strategy).

Flow:

  1. The parent is checked in Targeted Mode.
  2. The child component, although not dirty, will be checked because it uses Global Mode.
  3. The child will be checked globally, and the traversal will proceed accordingly.


Conclusion

Angular’s change detection system uses a combination of Global Mode and Targeted Mode to optimize the performance of UI updates. The key to this optimization lies in the mode-switching mechanism, which ensures that only the necessary components are checked, thus minimizing the computational overhead during change detection. The flexibility to switch between these modes based on component strategies (OnPush vs Default) allows Angular to handle large and complex applications efficiently.

By understanding how dirty components are tracked and how mode switching occurs, developers can leverage Angular’s change detection system to build performant, scalable applications that are both responsive and efficient.

Amazing insight! Thank you for sharing

要查看或添加评论,请登录

Amin Atwi的更多文章

社区洞察

其他会员也浏览了