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.
Constructor vs ngOnInit(): DI timing, @Input timing, and what belongs where
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core ideaconstructor 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 |
|
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 |
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().
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 |
Reading | You often read default/undefined instead of the parent-provided value | Use |
Accessing | View may not exist yet; | Use |
Using |
| Treat it like constructor-time DI; still do initialization in |
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.
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.