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 ngOnInit instead 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 value
  • change.previousValue - The previous value
  • change.isFirstChange() - Whether this is the first change

DoCheck

Use for custom change detection logic. Called on every change detection cycle.

Warning: ngDoCheck is 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:

  1. Constructor
  2. ngOnChanges (first call, for each input)
  3. ngOnInit
  4. ngDoCheck (first call)
  5. ngAfterContentInit
  6. ngAfterContentChecked
  7. ngAfterViewInit
  8. ngAfterViewChecked

On subsequent change detection cycles:

  • ngOnChanges (if inputs changed)
  • ngDoCheck
  • ngAfterContentChecked
  • ngAfterViewChecked

On destruction:

  • ngOnDestroy

Best Practices

  • Use ngOnInit for initialization, not the constructor
  • Always cancel subscriptions and timers in ngOnDestroy
  • Keep ngDoCheck lightweight
  • Use ngAfterViewInit to access @ViewChild references
  • Don't modify inputs in ngOnChanges