Lifecycle Hooks
AngularDart calls lifecycle hook methods on directives and components at key moments: when they are created, after their inputs change, when they are destroyed, and more.
Hook Interfaces
Implement these interfaces from package:angulardart/angular.dart to tap into lifecycle events:
| Hook | Interface | Description |
|---|---|---|
ngOnInit |
OnInit |
Called once after first ngOnChanges |
ngOnDestroy |
OnDestroy |
Called before the directive/component is destroyed |
ngOnChanges |
OnChanges |
Called before ngOnInit and whenever input bindings change |
ngDoCheck |
DoCheck |
Called during every change detection run |
ngAfterContentInit |
AfterContentInit |
Called once after content is projected into the directive |
ngAfterContentChecked |
AfterContentChecked |
Called after every check of projected content |
ngAfterViewInit |
AfterViewInit |
Called once after the component's view is initialized |
ngAfterViewChecked |
AfterViewChecked |
Called after every check of the component's view |
OnInit
Use ngOnInit for initialization logic that requires input bindings to be set:
@Component(
selector: 'user-profile',
template: '<h1>{{ user.name }}</h1>',
)
class UserProfileComponent implements OnInit {
@Input()
String? userId;
User? user;
final UserService _userService;
UserProfileComponent(this._userService);
@override
void ngOnInit() {
_userService.getUser(userId!).then((u) => user = u);
}
}
Note: Use
ngOnInitinstead of the constructor for initialization. The constructor runs before input bindings are available.
OnDestroy
Use ngOnDestroy to clean up resources:
@Component(
selector: 'timer',
template: '<p>Elapsed: {{ elapsed }}s</p>',
)
class TimerComponent implements OnDestroy {
int elapsed = 0;
late final Timer _timer;
TimerComponent() {
_timer = Timer.periodic(Duration(seconds: 1), (_) => elapsed++);
}
@override
void ngOnDestroy() {
_timer.cancel();
}
}
Common cleanup tasks:
- Unsubscribe from observables/streams
- Detach event listeners
- Cancel timers
- Close WebSocket connections
OnChanges
Called whenever an @Input() binding changes:
@Component(
selector: 'my-component',
template: '<p>{{ name }}</p>',
)
class MyComponent implements OnChanges {
@Input()
String? name;
@override
void ngOnChanges(SimpleChanges changes) {
if (changes.containsKey('name')) {
final change = changes['name']!;
print('Name changed from ${change.previousValue} to ${change.currentValue}');
}
}
}
SimpleChanges provides:
change.currentValue- The new valuechange.previousValue- The previous valuechange.isFirstChange()- Whether this is the first change
DoCheck
Use for custom change detection logic. Called on every change detection cycle.
Warning:
ngDoCheckis called very frequently. Keep it lightweight.
@Component(
selector: 'my-component',
template: '<p>{{ items.length }} items</p>',
)
class MyComponent implements DoCheck {
@Input()
List<String> items = [];
int _previousLength = 0;
@override
void ngDoCheck() {
if (items.length != _previousLength) {
print('Items count changed: $_previousLength -> ${items.length}');
_previousLength = items.length;
}
}
}
AfterContentInit / AfterContentChecked
Called when projected content (via <ng-content>) is initialized/checked:
@Component(
selector: 'wrapper',
template: '<div class="wrapper">'
'<ng-content></ng-content>'
'</div>',
)
class WrapperComponent implements AfterContentInit {
@override
void ngAfterContentInit() {
print('Content has been projected');
}
}
AfterViewInit / AfterViewChecked
Called when the component's own view (template) is initialized/checked:
@Component(
selector: 'my-component',
template: '<div #container></div>',
)
class MyComponent implements AfterViewInit {
@ViewChild('container')
HtmlElement? container;
@override
void ngAfterViewInit() {
// ViewChild references are available here
container?.style.backgroundColor = 'lightblue';
}
}
Lifecycle Sequence
The full lifecycle order:
- Constructor
ngOnChanges(first call, for each input)ngOnInitngDoCheck(first call)ngAfterContentInitngAfterContentCheckedngAfterViewInitngAfterViewChecked
On subsequent change detection cycles:
ngOnChanges(if inputs changed)ngDoCheckngAfterContentCheckedngAfterViewChecked
On destruction:
ngOnDestroy
Best Practices
- Use
ngOnInitfor initialization, not the constructor - Always cancel subscriptions and timers in
ngOnDestroy - Keep
ngDoChecklightweight - Use
ngAfterViewInitto access@ViewChildreferences - Don't modify inputs in
ngOnChanges