Change Detection
Change detection is the process by which AngularDart checks to see if your component's data has changed, and if so, updates the DOM to reflect those changes.
How Change Detection Works
AngularDart runs change detection in response to:
- User events (clicks, input, etc.)
- HTTP responses
- Timers (setTimeout, setInterval)
- Observable events (streams)
Change Detection Strategies
Default Strategy
The default strategy checks every component on every event:
@Component(
selector: 'my-component',
template: '{{ data.value }}',
// Default strategy (implicit)
)
class MyComponent {
Data data = Data();
}
OnPush Strategy
The OnPush strategy only checks when:
- An
@Input()reference changes - An event originates from the component or its children
- An observable emits (with
asyncpipe) - Change detection is explicitly triggered
@Component(
selector: 'user-card',
template: '{{ user.name }}',
changeDetection: ChangeDetectionStrategy.OnPush,
)
class UserCardComponent {
@Input()
User? user;
}
Important: With
OnPush, AngularDart only checks if the reference touserchanges, not if properties insideuserchange.
When OnPush Triggers
Input reference changes
// This triggers change detection
user = User(name: 'Alice');
// This does NOT trigger change detection with OnPush
user.name = 'Bob';
Events from the component
<!-- Clicking this button triggers change detection -->
<button (click)="onClick()">Click me</button>
Async pipe emissions
<!-- Stream emissions trigger change detection -->
<p>{{ data$ | async }}</p>
Manually Triggering Change Detection
Use ChangeDetectorRef to manually control change detection:
@Component(
selector: 'my-component',
template: '{{ count }}',
changeDetection: ChangeDetectionStrategy.OnPush,
)
class MyComponent {
final ChangeDetectorRef _cd;
int count = 0;
MyComponent(this._cd);
void updateFromExternalSource() {
// External update (e.g., from a service)
count++;
// Manually trigger change detection
_cd.markForCheck();
}
}
markForCheck()
Marks the component and its ancestors for check:
_cd.markForCheck();
detectChanges()
Immediately runs change detection on the component and its children:
_cd.detectChanges();
detach() / reattach()
Detach from the change detection tree:
@Component(
selector: 'heavy-component',
template: '...',
)
class HeavyComponent {
final ChangeDetectorRef _cd;
HeavyComponent(this._cd);
void pauseUpdates() {
_cd.detach();
}
void resumeUpdates() {
_cd.reattach();
}
}
Performance Optimization
Use OnPush for leaf components
@Component(
selector: 'user-avatar',
template: '<img [src]="user.avatarUrl">',
changeDetection: ChangeDetectionStrategy.OnPush,
)
class UserAvatarComponent {
@Input()
User? user;
}
Immutable data
Use immutable data structures to make reference changes clear:
class User {
final String name;
final String email;
const User({required this.name, required this.email});
User copyWith({String? name, String? email}) {
return User(
name: name ?? this.name,
email: email ?? this.email,
);
}
}
// Update creates a new reference
user = user.copyWith(name: 'New Name');
TrackBy with ngFor
Use trackBy to avoid re-rendering unchanged items:
<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
dynamic trackById(int index, Item item) => item.id;
Avoid expensive computations in templates
// Bad: computed on every change detection
@Component(
template: '{{ items.where((i) => i.isActive).length }}',
)
// Good: pre-compute and store
@Component(
template: '{{ activeItemCount }}',
)
class MyComponent {
List<Item> items = [];
int get activeItemCount => items.where((i) => i.isActive).length;
}
Debugging Change Detection
Enable development mode
void main() {
enableDevMode();
runApp(AppComponentNgFactory);
}
This throws an error if change detection modifies bindings (ExpressionChangedAfterCheckedError).
Log change detection cycles
@Component(
selector: 'debug-component',
template: '...',
)
class DebugComponent implements DoCheck {
int _checkCount = 0;
@override
void ngDoCheck() {
_checkCount++;
print('Change detection cycle: $_checkCount');
}
}
Best Practices
- Use
OnPushfor components that don't mutate their inputs - Use immutable data structures
- Use
trackBywithngFor - Avoid expensive computations in templates
- Use
markForCheck()for external updates withOnPush - Enable dev mode during development