Angular

Lifecycle Hooks

15 Questions

Lifecycle hooks are methods that Angular calls on directives and components as it creates, changes, and destroys them. They let you tap into key moments in a component's life. The main hooks are ngOnChanges, ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterViewInit, and ngOnDestroy. Each hook is defined by implementing its corresponding interface.
export class MyComponent implements OnInit, OnChanges, OnDestroy {
  @Input() data!: string;

  ngOnChanges(changes: SimpleChanges) { /* input changed */ }
  ngOnInit() { /* component initialized */ }
  ngOnDestroy() { /* cleanup before destroy */ }
}
Hooks are called in a specific order: ngOnChanges first, then ngOnInit, then the check and view hooks. ngOnDestroy is always called last before the component is removed.

Why it matters: Tests fundamental understanding of component initialization flow. Shows you know when and how Angular manages component lifecycle.

Real applications: Every component uses at least ngOnInit and ngOnDestroy. Lifecycle hooks are essential for initialization, data fetching, and resource cleanup.

Common mistakes: Developers don't understand hook order, running initialization code in wrong hooks. They don't clean up subscriptions causing memory leaks. They treat constructor and ngOnInit as equivalent.

ngOnInit is called once after the first ngOnChanges. It is the best place for initialization logic like fetching data or setting up subscriptions. At this point, all @Input properties are available with their bound values. Use ngOnInit instead of the constructor for any logic that depends on input bindings.
export class UserComponent implements OnInit {
  @Input() userId!: string;
  user: User;

  ngOnInit() {
    // Safe to use @Input values here
    this.user = this.userService.getUser(this.userId);
  }
}
ngOnInit runs only once during the component's lifetime. For reacting to subsequent input changes, use ngOnChanges instead.

Why it matters: Tests knowledge of component initialization timing. Shows you understand when @Input values are available for use.

Real applications: Fetching user data by ID, initializing timers, setting up subscriptions all happen in ngOnInit after @Input values are bound.

Common mistakes: Developers use constructor for logic that depends on @Input values. They run initialization in ngOnChanges instead of ngOnInit, causing performance issues.

The constructor is a TypeScript feature for class instantiation where dependency injection happens. ngOnInit is an Angular lifecycle hook called after Angular sets input properties. In the constructor, @Input values are NOT available yet because bindings have not been resolved. Use the constructor only for DI and ngOnInit for all initialization logic.
export class ExampleComponent implements OnInit {
  @Input() name!: string;

  constructor(private service: DataService) {
    // DI only — this.name is undefined here
  }

  ngOnInit() {
    // this.name is available here
    this.service.load(this.name);
  }
}
The constructor runs before Angular processes any bindings. ngOnInit is the safe place to use @Input values and interact with services.

Why it matters: Tests deep understanding of Angular initialization order and TypeScript patterns. Knowing the difference prevents bugs from undefined @Input values.

Real applications: Constructor handles DI only. ngOnInit loads data for display, subscribes to status changes, initializes timers.

Common mistakes: Developers use @Input values in constructor, getting undefined. They try to call services in constructor that depend on @Input. They don't understand Angular binding timing.

ngOnChanges is called when any data-bound @Input property changes. It receives a SimpleChanges object containing the previous and current values for each changed input. It is called before ngOnInit and on every subsequent input change. Use it to react to input property changes from a parent component.
ngOnChanges(changes: SimpleChanges) {
  if (changes['userId']) {
    console.log('Previous:', changes['userId'].previousValue);
    console.log('Current:', changes['userId'].currentValue);
    console.log('First change:', changes['userId'].firstChange);
  }
}
Each entry in SimpleChanges has previousValue, currentValue, and firstChange properties. The firstChange flag is true during the initial binding.

Why it matters: Tests knowledge of input change detection and reaction patterns. Shows you can build components that respond to parent updates.

Real applications: Reloading data when userId changes, refiltering lists when filter changes, updating chart visualization when config changes.

Common mistakes: Developers don't implement ngOnChanges and miss parent property updates. They don't check firstChange, processing initial values twice. They don't understand ngOnChanges runs before ngOnInit.

ngDoCheck is called during every change detection run. It lets you implement custom change detection for changes that Angular does not detect on its own. This is useful for detecting deep object mutations or comparing complex data structures. Keep the logic lightweight because this hook is called very frequently.
ngDoCheck() {
  if (this.currentName !== this.previousName) {
    this.previousName = this.currentName;
    this.changeLog.push('Name changed to: ' + this.currentName);
  }
}
ngDoCheck runs on every change detection cycle, even when no inputs have changed. Avoid expensive operations here to prevent performance issues.

Why it matters: Tests understanding of custom change detection and performance implications. Shows you know when Angular's default detection isn't enough.

Real applications: Detecting deep mutations Angular misses like object property changes, tracking complex data transformations, monitoring performance metrics.

Common mistakes: Developers run expensive operations in ngDoCheck, killing performance. They overuse ngDoCheck instead of leveraging OnPush change detection. They don't understand it runs excessively.

ngAfterViewInit is called once after Angular initializes the component's view and all child views. It is the safe place to access @ViewChild references because the DOM is fully rendered at this point. This hook runs after ngAfterContentInit. Use it for DOM manipulations like focusing an input or initializing third-party libraries.
@ViewChild('myInput') inputRef!: ElementRef;

ngAfterViewInit() {
  this.inputRef.nativeElement.focus();
}
Do not change component data in ngAfterViewInit as it can trigger ExpressionChangedAfterItHasBeenCheckedError. If needed, wrap changes in setTimeout().

Why it matters: Tests understanding of template rendering timing and ViewChild access patterns. Shows you know how to safely interact with the DOM.

Real applications: Focusing input fields for better UX, initializing third-party JS libraries like charts/maps, setting element sizes based on viewport.

Common mistakes: Developers change component state in ngAfterViewInit causing ExpressionChangedAfterItHasBeenCheckError. They try to access @ViewChild in ngOnInit when it's undefined.

ngAfterContentInit is called once after Angular projects external content into the component via <ng-content>. At this point, @ContentChild and @ContentChildren references are available. It runs before ngAfterViewInit. Use it to interact with projected content from a parent component.
@ContentChild(ChildDirective) child!: ChildDirective;

ngAfterContentInit() {
  console.log(this.child); // Now available
}
@ContentChild references are only safe to access from this hook onwards. This hook is called once, while ngAfterContentChecked runs on every change detection cycle.

Why it matters: Tests knowledge of content projection patterns and component composition. Shows you understand how Angular handles ng-content.

Real applications: Wrapper components accessing projected children, custom form components reading child form controls, layout components managing projected content.

Common mistakes: Developers try to access @ContentChild before ngAfterContentInit, getting undefined. They confuse @ContentChild with @ViewChild. They don't understand projection timing.

ngOnDestroy is called just before Angular destroys the component or directive. Use it for cleanup: unsubscribe from observables, detach event listeners, stop timers, and release resources. Failing to clean up can cause memory leaks especially with long-lived subscriptions. Every component that subscribes should implement this hook.
private sub!: Subscription;

ngOnInit() {
  this.sub = this.data$.subscribe(val => this.value = val);
}

ngOnDestroy() {
  this.sub.unsubscribe(); // Prevent memory leaks
}
In Angular 16+, use takeUntilDestroyed() to auto-unsubscribe instead of manually implementing ngOnDestroy. Always clean up event listeners and intervals here.

Why it matters: Tests understanding of resource cleanup and memory leak prevention. Shows you can prevent common memory leak issues.

Real applications: Unsubscribing from data observables, removing window resize listeners, clearing polling timers, releasing external resources.

Common mistakes: Developers never call ngOnDestroy, leaking subscriptions and memory. They don't understand subscriptions accumulate over time. They forget to unsubscribe on long-lived components.

Angular calls lifecycle hooks in a specific order during component creation and change detection. The order is: ngOnChanges, ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit, ngAfterViewChecked. Steps 3-7 repeat on every change detection cycle, while ngOnInit runs only once.
// Order during creation:
// 1. ngOnChanges (if @Input exists)
// 2. ngOnInit
// 3. ngDoCheck
// 4. ngAfterContentInit
// 5. ngAfterContentChecked
// 6. ngAfterViewInit
// 7. ngAfterViewChecked
// On destroy: ngOnDestroy
The init hooks (ngOnInit, ngAfterContentInit, ngAfterViewInit) run only once. The check hooks run on every change detection cycle. ngOnDestroy is called last.

Why it matters: Tests mental model of Angular's lifecycle and initialization flow. Understanding order prevents bugs from running code at wrong times.

Real applications: Initialization in ngOnInit, DOM access in ngAfterViewInit, repetitive checks in ngAfterViewChecked, cleanup in ngOnDestroy.

Common mistakes: Developers run initialization in wrong hooks. They don't understand init vs check hook differences. They expect one-time hooks that actually repeat.

Angular 16+ provides takeUntilDestroyed to auto-unsubscribe when a component is destroyed. It replaces the manual takeUntil + Subject pattern with a single operator. It must be called in the constructor or injection context. This greatly simplifies cleanup code in components with multiple subscriptions.
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

export class MyComponent {
  constructor() {
    this.data$.pipe(
      takeUntilDestroyed() // auto-unsubscribes on destroy
    ).subscribe(val => this.value = val);
  }
}
takeUntilDestroyed() uses Angular's DestroyRef internally. You can also inject DestroyRef directly for more control over cleanup timing.

Why it matters: Tests knowledge of modern Angular (16+) RxJS integration and simplified cleanup patterns. Shows you know the latest best practices.

Real applications: New Angular applications use takeUntilDestroyed instead of manual ngOnDestroy. This is the recommended standard for all new projects.

Common mistakes: Developers still use the old takeUntil + Subject pattern in new projects. They don't know about takeUntilDestroyed operator. They manually implement ngOnDestroy unnecessarily.

ngAfterViewChecked is called after Angular checks the component's view and child views for changes. It runs after every change detection cycle, not just once, so keep logic lightweight. It is useful for tasks that depend on the view being fully updated, like measuring element sizes after re-rendering. Be careful not to change component state here as it can cause infinite change detection loops.
ngAfterViewChecked() {
  // Runs after every change detection in this view
  const height = this.elementRef.nativeElement.offsetHeight;
  console.log('View checked, height:', height);
}
This hook pairs with ngAfterViewInit — the init runs once, checked runs repeatedly. Avoid modifying bound properties here to prevent ExpressionChangedAfterItHasBeenCheckedError.

Why it matters: Tests understanding of repeated lifecycle hooks and performance implications. Shows you can implement efficient post-render logic.

Real applications: Measuring element dimensions after render, adjusting scroll positions, triggering animations after layout complete.

Common mistakes: Developers run expensive operations in ngAfterViewChecked, hurting performance. They change state here causing ExpressionChangedAfterItHasBeenCheckedError. They don't understand "Checked" means it runs repeatedly.

ngAfterContentChecked is called after Angular checks the content projected into the component through <ng-content>. It runs after every change detection cycle, before ngAfterViewChecked. You can safely use @ContentChild references here since content is initialized first. Keep logic minimal due to frequent execution.
@ContentChild('projectedItem') item!: ElementRef;

ngAfterContentChecked() {
  if (this.item) {
    console.log('Projected content checked');
  }
}
This hook tells you when projected content has been re-checked. It pairs with ngAfterContentInit which runs only once during initialization.

Why it matters: Tests understanding of content projection lifecycle and ng-content patterns. Shows you know how to monitor projected content changes.

Real applications: Wrapper components monitoring projected content, tabs tracking tab content changes, form components watching field updates.

Common mistakes: Developers confuse ngAfterContentChecked with ngAfterContentInit. They run expensive logic here repeatedly hurting performance. They don't use it correctly for content observation.

Yes, all lifecycle hooks work in directives just like in components. Directives support ngOnInit, ngOnChanges, ngDoCheck, ngOnDestroy, and the content hooks. However, directives do not have templates, so ngAfterViewInit and ngAfterViewChecked are not applicable. The most commonly used hooks in directives are ngOnInit for setup and ngOnDestroy for cleanup.
@Directive({ selector: '[appTracker]' })
export class TrackerDirective implements OnInit, OnDestroy {
  ngOnInit() { console.log('Directive initialized'); }
  ngOnDestroy() { console.log('Directive destroyed'); }
}
Directives are simpler than components because they have no view. Use ngOnChanges to react to input changes and ngOnDestroy to remove event listeners.

Why it matters: Tests understanding that lifecycle hooks apply beyond components. Shows you can implement reusable directive logic with lifecycle management.

Real applications: Attribute directives using ngOnInit to set up behaviors, ngOnChanges to react to condition updates, ngOnDestroy to clean up listeners.

Common mistakes: Developers think lifecycle hooks only work in components. They forget directives need cleanup. They don't understand directives have simpler lifecycles without view hooks.

ngOnInit runs after Angular sets input properties and initializes the component, but the template is not fully rendered yet. ngAfterViewInit runs after the component's view and all child views have been fully initialized. Use ngOnInit for data fetching and property setup. Use ngAfterViewInit when you need to access DOM elements or child components via @ViewChild.
@ViewChild('myInput') inputRef!: ElementRef;

ngOnInit() {
  // inputRef is NOT available here
  this.loadData();
}

ngAfterViewInit() {
  // inputRef IS available here
  this.inputRef.nativeElement.focus();
}
@ViewChild references are only guaranteed to be available in ngAfterViewInit and later hooks. Accessing them in ngOnInit will result in undefined.

Why it matters: Tests knowledge of template DOM access timing. Shows you understand when ViewChild references become available.

Real applications: Using ngOnInit for data fetching, ngAfterViewInit for DOM manipulation like focus, scrolling, or DOM measurements.

Common mistakes: Developers try to use @ViewChild in ngOnInit, getting undefined. They don't understand template is rendered after initialization. They access it too early causing runtime errors.

Changing component state in ngAfterViewInit can cause an ExpressionChangedAfterItHasBeenCheckedError in development mode. This happens because Angular has already finished checking the view, and changing state forces another check that finds a different value. The solution is to wrap the change in setTimeout() or use ChangeDetectorRef.detectChanges(). This error is a safeguard that helps you avoid data flow issues.
// This causes ExpressionChangedAfterItHasBeenCheckedError
ngAfterViewInit() {
  this.title = 'Updated'; // ERROR in dev mode
}

// Fix: use setTimeout
ngAfterViewInit() {
  setTimeout(() => this.title = 'Updated');
}
The error only appears in development mode as a safety check. In production, Angular skips the second verification pass for performance reasons.

Why it matters: Tests understanding of Angular's development safety checks and debugging ngAfterViewInit errors. Shows you know how to work within Angular's constraints.

Real applications: Updating component properties after view renders needs setTimeout() wrapper. This is common when modifying state based on DOM measurements.

Common mistakes: Developers see ExpressionChangedAfterItHasBeenCheckedError and are confused. They don't wrap in setTimeout, continuing to get errors. They don't understand it's development-only safety checking.