constructor is for DI + trivial class setup, ngOnInit is for initialization that depends on inputs/bindings, and ngAfterViewInit is for work that needs the component view (DOM, ViewChild). For DOM/ViewChild access, ngAfterViewInit is usually correct—except @ViewChild({ static: true }) cases which are available earlier.
What’s the difference between constructor, ngOnInit, and ngAfterViewInit, and which one is correct for DOM/ViewChild access?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core idea
These run at different moments in the component lifecycle. The right hook depends on what you need: DI only (constructor), inputs/bindings (ngOnInit), or the rendered view / ViewChild / DOM (ngAfterViewInit).
Hook | When it runs | What it’s for | DOM / ViewChild availability |
|---|---|---|---|
| When the class instance is created (before Angular initializes bindings). | Dependency injection + minimal field initialization (no Angular-dependent logic). | ❌ Not safe for DOM. View is not created. ViewChild not resolved. |
| Once, after Angular sets data-bound inputs (after first | Initialization that depends on inputs, services, starting streams, fetching data. | ⚠️ Usually not for DOM. ViewChild only if |
| Once, after Angular finishes creating the component’s view + child views. | Work that needs the rendered view: | ✅ Safe for DOM/ViewChild (default |
Which one is correct for DOM / ViewChild?
Use ngAfterViewInit() for DOM/ViewChild access in most cases. That’s when Angular guarantees the view exists and view queries (default static: false) are resolved.
Important nuance: @ViewChild static option@ViewChild(..., { static: true }) resolves earlier (before/at init), so it can be used in ngOnInit() if the element is always present (not inside *ngIf/*ngFor). Default is static: false, which is available in ngAfterViewInit().
import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
@Component({
selector: 'app-demo',
template: `
<input #nameInput />
<!-- If this input were under *ngIf, static:true would be wrong -->
`
})
export class DemoComponent implements OnInit, AfterViewInit {
// Case A (common): default static:false => available in ngAfterViewInit
@ViewChild('nameInput') nameInput?: ElementRef<HTMLInputElement>;
// Case B: static:true => available in ngOnInit (ONLY if always present)
// @ViewChild('nameInput', { static: true }) nameInput!: ElementRef<HTMLInputElement>;
constructor(/* inject services here */) {
// DI only. No DOM.
}
ngOnInit(): void {
// Good: init that depends on @Input() or services.
// If using static:true, you *can* access nameInput here.
}
ngAfterViewInit(): void {
// Safe place for DOM/ViewChild
this.nameInput?.nativeElement.focus();
}
}
Scenario | Correct hook | Why |
|---|---|---|
Read |
| Inputs are set; component is initialized. |
Access |
| View is created; queries are resolved. |
ViewChild element is always present (not under *ngIf/*ngFor) and you need it early |
| Static query resolves before init. |
Access projected content via |
| Projected content is initialized (different from the view). |
Common pitfall
If you change a bound value inside ngAfterViewInit, you can trigger ExpressionChangedAfterItHasBeenCheckedError in dev mode. Fix by avoiding sync bound mutations there, or scheduling them (microtask) / triggering a follow-up change detection deliberately (advanced).
constructor = DI + minimal setup. ngOnInit = initialization that depends on inputs/bindings. ngAfterViewInit = anything that needs the rendered view (DOM, ViewChild). For DOM/ViewChild access, ngAfterViewInit is the default correct choice, with the exception of @ViewChild({ static: true }) when the element is guaranteed to exist.