Advanced
Change Detection
Master Angular Change Detection, covering Zone.js, Default vs OnPush strategies, and manual rendering control via ChangeDetectorRef.
1. Learning Objectives
In this lesson, you will master Angular's change detection mechanism. By the end of this topic, you will be able to:
- Explain how Angular synchronization keeps component models and templates in sync.
- Describe the role of Zone.js in monkey-patching browser APIs to trigger automatic digests.
- Differentiate between the Default and OnPush change detection strategies.
- Identify what triggers change detection in a component configured with OnPush.
- Control rendering manually using the
ChangeDetectorRefAPI.
2. Overview
Change detection is the process by which Angular synchronizes the state of user-facing templates with underlying component data models. Whenever an event happens (like a click, an API return, or a timer tick), Angular traverses the component tree from top to bottom, checking for updates. You can optimize this process using the **OnPush** strategy, which instructs Angular to skip checking a component unless its inputs change, an event originates inside it, or an async pipe emits.
3. Why This Topic Matters
In large-scale applications, change detection performance is the difference between a laggy UI and a fluid user experience:
- Unnecessary Checks: By default, if a button in Component A is clicked, Angular runs change detection checks on *every* component on the screen, even if they have no shared relationships or inputs. This wastes CPU cycles.
- Manual Control: In real-time dashboards receiving hundreds of stock ticks per second via WebSockets, running change checks on every tick freezes the UI. Detaching change detection and updating manually at controlled intervals keeps rendering performant.
4. Real-World Analogy
Think of change detection strategies like **two different office reporting styles**:
- Default Strategy (Micromanaging Boss): The boss asks *every* employee on the floor if they have updates every single time *any* phone rings. It doesn't matter if the call was for accounting; the boss checks the engineers, the designers, and the cleaners.
- OnPush Strategy (Result-Oriented Boss): The boss only checks an employee if: (1) a manager hands them new physical paperwork (Input reference change), (2) the employee explicitly raises their hand to report (internally triggered event), or (3) they have a scheduled daily task runner (async pipe emit). Otherwise, the employee is left to work in peace, saving time.
5. Core Concepts
Below is a comparison of default change detection versus the OnPush optimization strategy:
| Property | Default Strategy | OnPush Strategy |
|---|---|---|
| When checked? | On any asynchronous event anywhere in the app. | Only on Input changes, local events, or async pipe emits. |
| Object Mutations | Detected (shallow property mutations are checked). | Ignored. Requires reference changes (new objects). |
| Rendering Performance | Low/Medium (Scale issues on large component trees). | High (Bypasses subtrees entirely). |
6. Syntax & API Reference
This example shows how to configure a component to use the OnPush strategy and inject ChangeDetectorRef:
7. Visual Diagram
This diagram displays how Angular skips subtrees during change check traversal under the OnPush strategy:
8. Live Example — Full Working Code
Below is a WebSocket live pricing dashboard that detaches from the change detection engine, batching and rendering updates manually to prevent freezing:
What just happened? By default, a websocket sending ticks every 50ms would trigger change detection globally 20 times per second. By injecting ChangeDetectorRef and calling this.cdr.detach() in ngOnInit, we completely disconnect this component from the change checking loop. The price variable updates in the background, and we manually run change detection using detectChanges() once per second, reducing rendering load by 95%.
9. Interactive Playground
Try It Yourself Challenges:
- Comment out
this.cdr.detach()andthis.cdr.detectChanges(). Check how the pricing ticker performs when checked normally. - Experiment with
this.cdr.markForCheck()instead ofdetectChanges(). Note how `markForCheck` relies on the parent's change detector checking, which won't trigger immediately when detached.
10. Common Mistakes
| Mistake | Why it happens | Wrong | Correct |
|---|---|---|---|
| Mutating Input properties in OnPush | Mutating an array property directly on a parent object without changing the reference. OnPush only checks inputs via reference check (===). | parentObj.items.push(x) |
parentObj.items = [...parentObj.items, x] |
| Assuming setTimeout updates views automatically in OnPush | Using `setTimeout` inside an OnPush component does not trigger change checks unless the timeout runs inside a zone event listener. | setTimeout(() => this.val = x, 1000) |
setTimeout(() => { |
11. Best Practices
- Use OnPush everywhere: Make it a project standard to build all presentational (dumb) components using the OnPush strategy.
- Pass immutable data: Always emit new references from services, ensuring OnPush components capture inputs cleanly.
- Use async pipe: The async pipe handles marking OnPush components for checks automatically, eliminating manual
ChangeDetectorRefmanagement. - Run code outside Angular when appropriate: For Canvas drawing, WebGL, or complex scroll animations that don't affect templates, inject
NgZoneand run tasks usingrunOutsideAngular().
12. Browser Compatibility/Requirements
Change detection relies on Zone.js which monkey-patches DOM prototypes. It is compatible with all modern browsers.
13. Interview Questions
Q1: How does Zone.js know when to trigger change detection in Angular?
Answer: Zone.js intercepts (monkey-patches) standard browser asynchronous APIs (like addEventListener, setTimeout, setInterval, and the native fetch API). When any asynchronous callback finishes executing within the Angular Zone, Zone.js notifies Angular via the onMicrotaskEmpty event, prompting it to trigger a top-down change check.
Q2: What is the difference between detectChanges() and markForCheck() in ChangeDetectorRef?
Answer:
detectChanges()synchronously runs change detection on the current component and all its child components immediately.markForCheck()does not run a check immediately. It marks the component and all its parent ancestors as "dirty," prompting Angular to check them during the next global change detection cycle.
14. Debugging Exercise
Identify why clicking the "Add User" button in the parent component does not update the user list inside the child component:
Diagnosis: The parent component mutates the array reference using users.push(). The child component is using ChangeDetectionStrategy.OnPush. Because the reference of the input users has not changed (it points to the same array object in memory), Angular bypasses the child component during change detection.
Fix: Create a new array reference in the parent component.
15. Practice Exercises
Exercise 1: Optimize an interactive dashboard
Configure a nested component structure where all children use ChangeDetectionStrategy.OnPush. Bind them to a parent data model. Prove that updating an item in the parent model only updates the specific child component that received the new reference.
16. Scenario-Based Challenge
The Real-Time Stocks Graph Challenge:
You are building an analytics dashboard rendering complex canvas charts with live stock trends. Rendering checks trigger 60 times a second, dragging performance down. Design an optimization architecture that detaches the chart rendering component from change checks using `ChangeDetectorRef`, implementing rendering updates manually inside `NgZone.runOutsideAngular()` to maintain smooth framerates.
17. Quick Quiz
Q1: Which ChangeDetectorRef method should you invoke to run change checks immediately on a detached component?
A) markForCheck()
B) detectChanges()
C) reattach()
Answer: B — detectChanges() runs change checks on this component and its children instantly.
18. Summary & Key Takeaways
- Angular change detection keeps templates and components in sync.
- Zone.js monitors async tasks and prompts Angular to run checks.
- OnPush bypasses checking subtrees unless input object references change.
- Using
ChangeDetectorReflets developers manual check or detach rendering loops.
19. Cheat Sheet
| Method | Description |
|---|---|
cdr.markForCheck() |
Marks OnPush components and parents as dirty for the next check loop. |
cdr.detectChanges() |
Triggers change checking immediately on the component. |
cdr.detach() |
Disconnects the component from the automatic change detection loop. |