Skip to the solution
EasyFrontend7 min read

What is the difference between the constructor and ngOnInit?

896 views

The problem

Question presented to candidate: "A component loads a user by an id input. Should that code go in the constructor or in ngOnInit, and what actually differs between the two?"

What a strong answer should cover:

  • The constructor is plain TypeScript: Angular calls it to create the instance; use it (or field initialisers) for dependency injection and simple setup.
  • Inputs are not set yet in the constructor: decorator inputs hold their defaults; reading a required signal input throws NG0950 (and AOT flags it as NG8118).
  • ngOnInit is a lifecycle hook Angular calls once, after the first ngOnChanges, when inputs have their first values.
  • ngOnInit runs once: if the input changes later, code there does not re-run (ngOnChanges, computed or effect handle changes).
  • With signal inputs, derived state is often a computed in a field rather than code in ngOnInit.
  • The constructor is the injection context (inject() works there); ngOnInit is not.

Clarifying questions expected:

  • "Are the inputs decorator inputs or signal inputs?"
  • "Can the id change while the component stays on screen?"

Code / implementation expected: Yes β€” log the order of constructor, ngOnChanges and ngOnInit, and show a value that goes stale.

  • lifecycle

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

7 min read Β· 8 sections

Read the code

AngularConstructor vs ngOnChanges vs ngOnInit, and the stale ngOnInit copy
import { Component, Injectable, Input, OnChanges, OnInit, computed, inject, input, signal } from "@angular/core";

@Injectable({ providedIn: "root" })
export class UserApi {
  load(id: number) {
    return `user #${id}`;
  }
}

@Component({
  selector: "user-profile",
  template: `<p>ngOnInit copy: {{ status() }}</p><p>computed: {{ live() }}</p>`,
})
export class UserProfileComponent implements OnChanges, OnInit {
  // Dependencies are available in the constructor (and field initialisers).
  private api = inject(UserApi);

  @Input() legacyId = -1; // decorator input, with a default
  userId = input.required<number>(); // signal input
  status = signal("loading"); // set ONCE, in ngOnInit
  live = computed(() => this.api.load(this.userId())); // re-derives when userId changes

  constructor() {
    console.log(`constructor : api ready? ${!!this.api} | legacyId = ${this.legacyId}`);
    try {
      const readTooEarly = this.userId; // the compiler flags a direct this.userId() here (NG8118)
      console.log(`constructor : userId() = ${readTooEarly()}`);
    } catch (e) {
      console.log(`constructor : userId() threw ${(e as Error).message.split(":")[0]}`);
    }
  }

  ngOnChanges() {
    console.log(`ngOnChanges : legacyId = ${this.legacyId} | userId() = ${this.userId()}`);
  }

  ngOnInit() {
    console.log(`ngOnInit    : legacyId = ${this.legacyId} | userId() = ${this.userId()}`);
    this.status.set(this.api.load(this.userId()));
  }
}

@Component({
  selector: "app-root",
  imports: [UserProfileComponent],
  template: `
    <user-profile [userId]="id()" [legacyId]="7" />
    <button (click)="id.set(id() + 1)">next user</button>
  `,
})
export class AppComponent {
  id = signal(42);
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 2 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextEasy

Explain the difference between FormGroup.patchValue() and FormGroup.setValue().

Continue