0 day streak
javascript / expert
Snippet

Typed Content Projection with Angular Template Contracts

Angular's structural directive context objects are untyped by default, so template consumers get no compile-time feedback when they misspell a context variable like `let-idx="indx"`. The `ngTemplateContextGuard` static method is a compiler hook Angular's template type-checker specifically looks for: it lets a generic directive assert the shape of its context object so the template compiler narrows and validates `let-` bindings against the real interface. Without this guard, the Ivy template checker falls back to `any` for the context, silently accepting broken bindings that only fail at runtime with 'undefined is not a function' deep in a loop.

snippet.js
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { Directive, TemplateRef, Input } from '@angular/core';
export interface RowContext<T> {
$implicit: T;
index: number;
isLast: boolean;
}
@Directive({
selector: '[appRow]',
standalone: true,
})
export class RowDirective<T> {
@Input('appRow') data!: T;
constructor(public template: TemplateRef<RowContext<T>>) {}
static ngTemplateContextGuard<T>(
dir: RowDirective<T>,
ctx: unknown
): ctx is RowContext<T> {
return true;
}
}
// usage in host component template:
// <ng-container *ngFor="let item of items; let i = index; let last = last">
// <ng-template [appRow]="item" let-row let-idx="index" let-isLast="isLast">
// {{ row.name }} - {{ idx }} - {{ isLast }}
// </ng-template>
// </ng-container>
angular
Breakdown
1
export interface RowContext<T> { $implicit: T; index: number; isLast: boolean; }
`$implicit` is the reserved key Angular uses for the unnamed `let-row` binding; other keys must match their `let-x="key"` names exactly.
2
static ngTemplateContextGuard<T>(dir: RowDirective<T>, ctx: unknown): ctx is RowContext<T>
A type predicate the Ivy compiler calls at compile time — its return type, not its runtime body, is what narrows the template context type.
3
constructor(public template: TemplateRef<RowContext<T>>) {}
Binding the generic to `TemplateRef` ties the directive's type parameter to the actual embedded view, so `T` flows from `[appRow]="item"` into the context.