Constructor vs ngOnInit(): DI timing, @Input timing, and what belongs where

LowIntermediateAngular
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

Constructor runs when the class instance is created (mainly for DI + trivial field setup). ngOnInit() runs once after Angular has set initial @Input() bindings (after the first ngOnChanges). Put Angular-dependent initialization (inputs, data streams, side effects) in ngOnInit, not in the constructor. Covers: angular, lifecycle, hooks, components, basics.

Answer

Core idea

constructor is a TypeScript/class instantiation step (good for dependency injection + trivial setup). ngOnInit() is an Angular lifecycle hook (good for initialization that depends on Angular bindings like @Input(), and for starting side effects like data loading).

Runs when

Typical first-render order

Component creation

constructor → ngOnChanges? → ngOnInit → ngAfterViewInit

Constructor is earliest; ngOnInit happens after the first input binding pass.

Aspect

constructor

ngOnInit()

Who calls it?

TypeScript runtime (class instantiation)

Angular (lifecycle hook)

Dependency injection

✅ Yes (primary use)

✅ Yes (DI already done)

@Input() values available?

❌ Not reliably (bindings not applied yet)

✅ Yes (initial bindings are applied)

Good for

Assigning injected services to fields; lightweight defaults

Starting streams, fetching data, initializing based on inputs, wiring subscriptions

Avoid

Heavy work, subscriptions, API calls, reading inputs, touching DOM/ViewChild

Direct DOM/ViewChild access (usually use ngAfterViewInit)

What belongs where (interview version)

Why interviewers care

Putting side effects in the constructor makes component instantiation do "real work" before Angular finishes binding and before tests can set up spies. Putting Angular-dependent logic in ngOnInit is predictable: bindings are ready, and in tests it usually runs on fixture.detectChanges().

TYPESCRIPT
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-user',
  template: `User id: {{ userId }}`
})
export class UserComponent implements OnChanges, OnInit {
  @Input() userId!: string;

  constructor() {
    // At this point Angular hasn't applied @Input bindings yet.
    console.log('constructor userId =', this.userId); // often undefined
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['userId']) {
      console.log('ngOnChanges userId =', changes['userId'].currentValue);
    }
  }

  ngOnInit(): void {
    // Safe: initial @Input bindings already applied.
    console.log('ngOnInit userId =', this.userId);
  }
}
                  

Common pitfall

What breaks

Fix

Calling APIs/subscribing in the constructor

Runs before inputs are set; harder to test (spies not set up yet); surprise side effects on instantiation

Move to ngOnInit (or a facade/service triggered from init)

Reading @Input() in the constructor

You often read default/undefined instead of the parent-provided value

Use ngOnInit for initial value; use ngOnChanges (or an input setter) for reacting to changes

Accessing @ViewChild / DOM in ngOnInit

View may not exist yet; ViewChild can be undefined (default static:false)

Use ngAfterViewInit (or @ViewChild({ static: true }) only when the element is always present)

Using inject() and assuming it behaves like ngOnInit

inject() runs during instantiation (same timing category as constructor)

Treat it like constructor-time DI; still do initialization in ngOnInit

What senior candidates mention

Practical notes

Watch for edge case behavior, common pitfalls, and trade-offs between clarity and performance. Mention accessibility and testing considerations when the concept affects UI output or event timing.

Summary

constructor = DI + trivial setup (no inputs, no side effects). ngOnInit = initialization that depends on Angular bindings (@Input) and starting side effects (streams/data loading). If you must react to input changes over time, use ngOnChanges (or an input setter). DOM/ViewChild work usually belongs in ngAfterViewInit.

Similar questions
Guides
14 / 37