javascript / expert
Snippet
Custom Structural Directives with Typed Template Guards
A custom structural directive is only as safe as its template context typing. `ngTemplateContextGuard` is a static type-guard method the Angular compiler calls at compile time (via the Ivy template type-checker) to verify that variables declared with `let-foo` inside the directive's template actually match the context shape emitted from `createEmbeddedView`. Without it, `<ng-template let-x>` compiles even if `x` doesn't exist on the context object, and the mismatch only surfaces at runtime. Pairing this with `ngTemplateGuard_ifRole: 'binding'` also tells the compiler to narrow the type of the bound expression itself, so the template body sees `role` as `string`, not `string | null`.
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
32
33
34
35
36
37
38
39
40
41
42
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';interface IfRoleContext {$implicit: boolean;ifRole: string;}@Directive({selector: '[ifRole]',standalone: true,})export class IfRoleDirective {private hasView = false;constructor(private templateRef: TemplateRef<IfRoleContext>,private viewContainer: ViewContainerRef,) {}@Input() set ifRole(role: string) {const allowed = role === 'admin';if (allowed && !this.hasView) {this.viewContainer.createEmbeddedView(this.templateRef, {$implicit: true,ifRole: role,});this.hasView = true;} else if (!allowed && this.hasView) {this.viewContainer.clear();this.hasView = false;}}static ngTemplateGuard_ifRole: 'binding';static ngTemplateContextGuard(dir: IfRoleDirective,ctx: unknown,): ctx is IfRoleContext {return true;}}
angular
Breakdown
1
interface IfRoleContext { $implicit: boolean; ifRole: string; }
Defines the exact shape exposed to `let-` variables inside the template — `$implicit` is what `let-x` binds to without a name.
2
static ngTemplateGuard_ifRole: 'binding';
Instructs the compiler to type-check and narrow the bound `ifRole` expression itself, not just the template context.
3
static ngTemplateContextGuard(dir: IfRoleDirective, ctx: unknown): ctx is IfRoleContext
A compile-time-only type predicate; the compiler calls this signature during type-checking, it never runs in the browser.
4
this.viewContainer.createEmbeddedView(this.templateRef, { $implicit: true, ifRole: role })
The actual runtime context object — its shape must match `IfRoleContext` exactly or the guard becomes a lie the compiler trusts.