Angular Default vs OnPush Change Detection
Learn Angular Default vs OnPush Change Detection with architecture diagrams, performance comparison, Signals integration, ChangeDetectorRef, enterprise use cases, best practices, and interview questions.
Angular Default vs OnPush Change Detection - Complete Guide
Choosing the correct Change Detection Strategy is one of the most important performance decisions in Angular applications.
Angular provides two built-in strategies:
- Default
- OnPush
Understanding the differences between them helps developers build faster, more scalable, and enterprise-ready applications.
In modern Angular, Signals integrate seamlessly with both strategies and further improve rendering performance.
Table of Contents
- What is Change Detection?
- Default Strategy
- OnPush Strategy
- Architecture Comparison
- Default vs OnPush
- Signals with OnPush
- ChangeDetectorRef
- Enterprise Banking Example
- Performance Best Practices
- Common Mistakes
- Top 10 Interview Questions
- Summary
What is Change Detection?
Change Detection is Angular's mechanism for keeping the DOM synchronized with application state.
Whenever application data changes, Angular determines which bindings must be refreshed and updates the UI.
Change Detection Overview
flowchart LR
UserEvent
APIResponse
SignalUpdate
Timer
UserEvent --> ChangeDetection
APIResponse --> ChangeDetection
SignalUpdate --> ChangeDetection
Timer --> ChangeDetection
ChangeDetection --> ComponentTree
ComponentTree --> DOM
Default Change Detection Strategy
The Default strategy is Angular's standard behavior.
@Component({
selector: 'app-dashboard'
})
export class DashboardComponent {
}
Angular checks:
- Current component
- Child components
- Entire affected component tree
during every Change Detection cycle.
Advantages
- Easy to understand
- Less developer effort
- Great for beginners
- Suitable for small applications
Disadvantages
- More component checks
- Higher CPU usage
- Less efficient in very large applications
Default Strategy Flow
flowchart TD
Application
Application --> Root
Root --> ComponentA
Root --> ComponentB
ComponentA --> ChildA
ComponentB --> ChildB
Root --> Checked
ComponentA --> Checked
ComponentB --> Checked
ChildA --> Checked
ChildB --> Checked
OnPush Change Detection Strategy
OnPush minimizes unnecessary checks.
import {
Component,
ChangeDetectionStrategy
} from '@angular/core';
@Component({
selector:'app-user',
changeDetection:
ChangeDetectionStrategy.OnPush
})
export class UserComponent{
}
Angular checks an OnPush component only when one of these occurs:
- An Input reference changes
- An event originates from the component or one of its children
- An Observable bound with the Async Pipe emits
- A Signal read by the template changes
markForCheck()is calleddetectChanges()is called
OnPush Strategy Flow
flowchart TD
InputChanged
EventOccurred
SignalUpdated
AsyncPipe
ManualCheck
InputChanged --> Component
EventOccurred --> Component
SignalUpdated --> Component
AsyncPipe --> Component
ManualCheck --> Component
Component --> DOM
Architecture Comparison
flowchart LR
subgraph Default
Trigger1[Trigger]
Trigger1 --> Root1[Root]
Root1 --> AllComponents[All Components Checked]
end
subgraph OnPush
Trigger2[Trigger]
Trigger2 --> Eligible[Only Eligible Components]
Eligible --> DOM
end
Default vs OnPush
| Feature | Default | OnPush |
|---|---|---|
| Component Checked Every CD Cycle | ✅ Yes | ❌ No |
| Better Performance | ❌ | ✅ |
| Simpler to Use | ✅ | ⚠️ |
| Enterprise Ready | ⚠️ | ✅ |
| Large Applications | ⚠️ | ✅ |
| CPU Usage | Higher | Lower |
| Rendering Efficiency | Moderate | High |
How OnPush Detects Changes
Angular does not continuously monitor every property.
Instead, it reacts to specific triggers.
Example
@Input()
user!: User;
Parent
this.user = {
...this.user,
name:'John'
};
A new object reference causes Angular to check the OnPush component.
Object Reference Example
flowchart LR
OldObject
OldObject --> NewObject
NewObject --> OnPushComponent
OnPushComponent --> UI
Signals with OnPush
Signals work naturally with OnPush.
count = signal(0);
increment(){
this.count.update(
value => value + 1
);
}
Template
<h2>{{ count() }}</h2>
When count() changes,
Angular automatically marks the component for update.
This eliminates much of the manual work previously associated with OnPush.
Signals Architecture
flowchart LR
Signal
Signal --> OnPushComponent
OnPushComponent --> DOM
Async Pipe with OnPush
users$ =
this.http
.get<User[]>(this.api);
Template
<div *ngFor="let user of users$ | async">
{{ user.name }}
</div>
Whenever the Observable emits,
Angular updates the OnPush component automatically.
ChangeDetectorRef
Angular provides manual control when needed.
constructor(
private cd:
ChangeDetectorRef
){}
Methods
| Method | Purpose |
|---|---|
markForCheck() |
Schedule an OnPush component for checking |
detectChanges() |
Immediately run Change Detection |
detach() |
Stop automatic checks |
reattach() |
Resume automatic checks |
Example: markForCheck()
loadData(){
this.http
.get<User[]>(this.api)
.subscribe(users=>{
this.users = users;
this.cd.markForCheck();
});
}
Useful when data changes outside Angular's normal triggers.
Example: detach()
constructor(
private cd:
ChangeDetectorRef
){
this.cd.detach();
}
Later
refresh(){
this.cd.detectChanges();
}
Useful for:
- Dashboards
- Live charts
- Large tables
- High-frequency updates
Enterprise Banking Example
Online Banking Dashboard
Components
- Customer Summary
- Account Overview
- Portfolio
- Transactions
- Investments
- Notifications
Recommended Architecture
- OnPush for feature components
- Signals for UI state
- RxJS for APIs
- Async Pipe for Observables
- Immutable updates
flowchart TD
API
API --> Observable
Observable --> Signal
Signal --> OnPushComponents
OnPushComponents --> Dashboard
Dashboard --> User
Benefits
- Faster rendering
- Reduced CPU usage
- Better scalability
- Smaller Change Detection scope
- Improved responsiveness
Performance Comparison
| Scenario | Default | OnPush |
|---|---|---|
| Small App | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Enterprise Dashboard | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Large Data Grid | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| Financial Dashboard | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Memory Usage | Higher | Lower |
| Rendering Efficiency | Moderate | Excellent |
Performance Best Practices
| Practice | Benefit |
|---|---|
| Prefer OnPush for feature components | Better rendering performance |
| Use immutable updates | Reliable change detection |
| Combine OnPush with Signals | Fine-grained updates |
| Use Async Pipe | Automatic subscription handling |
Use trackBy with lists |
Fewer DOM updates |
| Split large components | Smaller component trees |
| Keep templates lightweight | Faster rendering |
| Measure before optimizing | Avoid premature optimization |
Common Mistakes
Mutating Objects
❌ Wrong
this.user.name = 'John';
No new reference is created.
✅ Correct
this.user = {
...this.user,
name:'John'
};
Forgetting Async Pipe
Instead of manual subscriptions,
prefer
{{ users$ | async }}
when rendering Observable data.
Calling detectChanges() Excessively
Avoid unnecessary manual Change Detection.
Use Angular's automatic mechanisms whenever possible.
Using Default Strategy Everywhere
Default is simple, but enterprise applications often benefit from OnPush.
Choose the strategy based on the application's needs.
Heavy Template Logic
❌ Avoid
{{ calculateTotal() }}
Prefer
total = computed(() =>
this.calculateTotal()
);
Default vs OnPush Decision Guide
| Application Type | Recommended Strategy |
|---|---|
| Learning Project | Default |
| Small CRUD App | Default |
| Admin Dashboard | OnPush |
| Banking System | OnPush |
| E-Commerce Platform | OnPush |
| Analytics Dashboard | OnPush |
| Real-Time Monitoring | OnPush + Signals |
| Enterprise Applications | OnPush + Signals + RxJS |
Best Practices
- Start with the simplest strategy that meets your needs.
- Prefer OnPush for reusable feature components.
- Use immutable state updates.
- Combine OnPush with Signals.
- Continue using RxJS for asynchronous operations.
- Use Async Pipe with Observables.
- Keep templates simple.
- Use
trackByfor large lists. - Avoid unnecessary manual Change Detection.
- Profile performance before making optimizations.
Advantages
| Strategy | Advantages |
|---|---|
| Default | Simplicity, less setup, ideal for beginners |
| OnPush | Better performance, fewer checks, enterprise scalability |
| Signals + OnPush | Fine-grained rendering, cleaner reactive code |
Top 10 Default vs OnPush Interview Questions
1. What is the difference between Default and OnPush Change Detection?
Answer
Default checks components during every Change Detection cycle, while OnPush checks components only when specific triggers occur, such as new input references, template events, Signal updates, Async Pipe emissions, or manual Change Detection.
2. When should you use OnPush?
Answer
Use OnPush for medium to large applications, reusable feature components, dashboards, data-intensive screens, and enterprise systems where rendering performance is important.
3. What triggers an OnPush component to update?
Answer
- New Input reference
- Component or child event
- Async Pipe emission
- Signal update
markForCheck()detectChanges()
4. Why doesn't object mutation work well with OnPush?
Answer
OnPush relies primarily on reference changes for inputs. Mutating an existing object keeps the same reference, so Angular cannot detect that change through input reference comparison.
5. How do Signals work with OnPush?
Answer
When a template reads a Signal, Angular tracks that dependency. Updating the Signal automatically marks the component for rendering without requiring manual subscriptions.
6. What is markForCheck()?
Answer
It marks an OnPush component so Angular includes it during the next Change Detection cycle.
7. What is detectChanges()?
Answer
It immediately runs Change Detection for the current component and its child components.
8. Is OnPush always faster than Default?
Answer
Not always. Small applications may not see noticeable benefits. OnPush provides the greatest value in larger or more complex applications with many components.
9. What are common OnPush best practices?
Answer
Use immutable updates, Signals for UI state, Async Pipe for Observables, trackBy in lists, and avoid unnecessary manual Change Detection.
10. What architecture is recommended for enterprise Angular applications?
Answer
Use OnPush for feature components, Signals for synchronous UI state, RxJS for asynchronous workflows, Async Pipe for Observable rendering, and immutable state updates.
Interview Cheat Sheet
| Requirement | Recommended Solution |
|---|---|
| Simple Application | Default |
| Enterprise Application | OnPush |
| UI State | Signals |
| Async Operations | RxJS |
| Observable Rendering | Async Pipe |
| Manual Refresh | detectChanges() |
| Schedule Next Check | markForCheck() |
| Large Lists | trackBy |
| Better Performance | OnPush + Signals |
| Immutable State | Recommended |
Summary
Angular offers two Change Detection strategies: Default and OnPush. The Default strategy prioritizes simplicity by checking components during every Change Detection cycle, while OnPush improves scalability by checking components only when meaningful triggers occur. Modern Angular applications achieve excellent performance by combining OnPush, Signals, RxJS, Async Pipe, and immutable state updates. Choosing the appropriate strategy depends on application size, complexity, and performance requirements.
Key Takeaways
- ✔ Default Change Detection is simple and beginner-friendly.
- ✔ OnPush reduces unnecessary component checks.
- ✔ OnPush reacts to input reference changes, events, Async Pipe emissions, Signal updates, and manual triggers.
- ✔ Signals integrate seamlessly with OnPush.
- ✔ Use immutable updates for reliable OnPush behavior.
- ✔ Async Pipe works naturally with OnPush.
- ✔
ChangeDetectorRefprovides manual control when needed. - ✔ Use
trackByfor better list rendering performance. - ✔ OnPush is recommended for most enterprise feature components.
- ✔ Understanding Default vs OnPush is a core Angular interview topic.
Next Article
➡️ 90-Zone.js-QA.md