Skip to the solution
HardFrontend13 min read

How does compilation work under the hood in Ivy?

956 views

The problem

Question presented to candidate: "Take a small component with an interpolation, a click handler, an @if and an @for. What does the Angular compiler turn it into, how does the runtime use that output to render and update the DOM, and why did Ivy make libraries and tree-shaking work better than before?"

What a strong answer should cover:

  • The compiler adds static definitions to each class: ɵfac (a factory) and ɵcmp (via ɵɵdefineComponent) with selectors, inputs, decls, vars, consts and a template function.
  • The template function takes (rf, ctx): the create pass (rf & 1) builds DOM once with instructions like ɵɵelementStart, ɵɵtext, ɵɵlistener; the update pass (rf & 2) re-evaluates bindings with ɵɵadvance, ɵɵtextInterpolate, ɵɵproperty.
  • Control flow compiles into separate embedded view template functions plus ɵɵconditionalCreate / ɵɵrepeaterCreate.
  • Locality: each component is compiled from its own metadata plus the type-level declarations of its dependencies, so compilation is per file and incremental.
  • Tree-shaking: instructions are plain functions imported from @angular/core; unused features are never imported.
  • Libraries ship partial declarations (ɵɵngDeclareComponent) that the linker turns into final definitions in the app build.

Clarifying questions expected:

  • "Do you want the compiler side, the runtime side, or both?"
  • "Should I compare with View Engine?"

Code / implementation expected: Optional — a small component and its real compiled output.

  • ivy
  • under-the-hood

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

13 min read · 11 sections

Read the code

AngularA small component whose ngc output is dissected above (interpolation, pipe, listener, @if, @for)
import { Component, Input, computed, signal } from "@angular/core";
import { UpperCasePipe } from "@angular/common";

// A child with a decorator input (works in AOT and in the JIT playground alike).
@Component({
  selector: "todo-item",
  template: `<li [class.done]="done">{{ label }}</li>`,
})
export class TodoItemComponent {
  @Input({ required: true }) label = "";
  @Input() done = false;
}

// Small on purpose: every template feature here maps to a handful of Ivy instructions.
@Component({
  selector: "app-root",
  imports: [TodoItemComponent, UpperCasePipe],
  template: `
    <h3>{{ title | uppercase }}</h3>
    <button (click)="add()">Add</button>
    @if (remaining() > 0) {
      <p>{{ remaining() }} left</p>
    }
    <ul>
      @for (t of todos(); track t.id) {
        <todo-item [label]="t.label" [done]="t.done" />
      }
    </ul>
  `,
})
export class AppComponent {
  title = "ivy demo";
  todos = signal([{ id: 1, label: "read ngc output", done: true }]);
  remaining = computed(() => this.todos().filter((t) => !t.done).length);

  add() {
    const id = this.todos().length + 1;
    this.todos.update((list) => [...list, { id, label: `task ${id}`, done: false }]);
  }
}

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 90 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 nextHard

What is an InjectionToken?

Continue