Skip to the solution
MediumFrontend8 min read

What is ngTemplateOutlet?

497 views

The problem

Question presented to candidate: "We are building a reusable list component, and each team wants rows to look different. How would you use ngTemplateOutlet to let the parent decide what a row looks like, and how does data get into that template?"

What a strong answer should cover:

  • NgTemplateOutlet is a directive from @angular/common that renders a TemplateRef at its location as an embedded view.
  • Data goes in through a context object: $implicit maps to let-x, named keys map to let-x="key".
  • Two syntaxes: *ngTemplateOutlet="tpl; context: ctx" or [ngTemplateOutlet]="tpl" [ngTemplateOutletContext]="ctx", usually on an ng-container.
  • Pattern: a child accepts a TemplateRef input (or queries one), and falls back to a default template.
  • Behaviour: changing the context updates the existing view; changing the template destroys and recreates it.
  • Limits: let- variables are untyped (any) under strict templates unless you add a typed directive; ngTemplateOutletInjector controls which injector the view uses.

Clarifying questions expected:

  • "Should the parent pass a template as an input, or project it as content?"
  • "Does the row template need access to services from the child or from the parent?"

Code / implementation expected: Yes β€” a child component rendering a parent-supplied template with a context.

  • ngtemplateoutlet

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

8 min read Β· 10 sections

Read the code

AngularA generic list that renders a row template supplied by its parent
import { Component, Input, TemplateRef, signal } from "@angular/core";
import { NgTemplateOutlet } from "@angular/common";

type Person = { name: string; role: string };
type RowContext = { $implicit: Person; index: number };

// A generic list. It owns the looping; the PARENT decides how each row looks.
@Component({
  selector: "app-list",
  imports: [NgTemplateOutlet],
  template: `
    <ul>
      @for (item of items; track item.name; let i = $index) {
        <li>
          <ng-container
            [ngTemplateOutlet]="rowTemplate ?? defaultRow"
            [ngTemplateOutletContext]="{ $implicit: item, index: i }" />
        </li>
      }
    </ul>
    <ng-template #defaultRow let-p>{{ p.name }}</ng-template>
  `,
})
export class ListComponent {
  @Input() items: Person[] = [];
  @Input() rowTemplate?: TemplateRef<RowContext>;
}

@Component({
  selector: "app-root",
  imports: [ListComponent],
  template: `
    <ng-template #fancy let-person let-i="index">
      <b>{{ i + 1 }}. {{ person.name }}</b> <small>({{ person.role }})</small>
    </ng-template>

    <h4>Default row</h4>
    <app-list [items]="team()" />

    <h4>Custom row from the parent</h4>
    <app-list [items]="team()" [rowTemplate]="fancy" />

    <button (click)="promote()">Promote Grace</button>
  `,
})
export class AppComponent {
  team = signal<Person[]>([
    { name: "Ada", role: "lead" },
    { name: "Grace", role: "dev" },
  ]);
  promote() {
    this.team.update((t) => t.map((p) => (p.name === "Grace" ? { ...p, role: "architect" } : p)));
  }
}

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 54 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 nextMedium

How do you pass data between sibling components that don't have a direct parent-child relationship?

Continue