TemplateRef & ViewContainerRef: Manual View Hosting
- Last updated
- Prerequisites:
- Angular directives and *ngIf/*ngFor
- Understanding of templates and views
- angular
- structural-directives
- TemplateRef
- ViewContainerRef
- directives
Read at your depth
The practical view
A structural directive (one with * prefix, like *myIf) is sugar for: <ng-template myIf [myIf]="cond">...</ng-template>. Inside the directive you inject TemplateRef (the ng-template content) and ViewContainerRef (the anchor where the view should be created). Then: if (cond) { this.vcr.createEmbeddedView(this.tpl); } else { this.vcr.clear(); }. This is exactly how ngIf/ngFor are implemented. You can also create dynamic components with this.vcr.createComponent(ComponentType). TemplateRef gives you the template's context (via createEmbeddedView(context)), so directives can pass data into the template.
The same idea in other frameworks
React render props / children-as-function and portals are the conceptual analogues.TemplateRef + ViewContainerRef — the current topic.Vue's <template> + v-if compilation and render functions are the analogue.Svelte's {#if}/{#each} compile to snippet/fragment logic; manual fragment APIs differ.Legacy vs modern
Repeating boilerplate with ngIf vs a custom structural directive
Hand-rolling permission checks with ngIf everywhere is repetitive; a custom structural directive encapsulates the logic and reuses the template.
@if (auth.can('admin')) { <admin-panel /> } <!-- ❌ OLD: repeated in every template -->@if true, render template via TemplateRef/ViewContainerRef — wrapped in *hasPermission="'admin'"Interview gotchas
Context
This tests the core mental model of structural directives — the ng-template desugaring and the two-primitive contract. Interviewers want the compiler expansion, not just 'it renders templates'.
The mechanical answer
The * prefix is compiler sugar: *myDirective="expr" expands to <ng-template myDirective="expr">...</ng-template> with the directive as the host on the ng-template element. Inside the directive, TemplateRef is the handle to that embedded template (the content the user wrote inside the * element), and ViewContainerRef is the container anchored at the host's location in the parent view. Rendering requires both: TemplateRef holds the template function + declaration view, and ViewContainerRef provides the location to insert the instantiated embedded view and manages its lifecycle (create/remove/destroy). Without ViewContainerRef you have no insertion point; without TemplateRef you have nothing to render. Together they give the directive full control over when and where content appears.
Trap
The naive answer is 'the * just makes a component render conditionally'. The * compiles to an ng-template + a directive hosting it. Another trap: thinking createEmbeddedView re-runs the template function on every CD — the view is created once and its bindings update reactively; creating it repeatedly without clearing leaks views. Candidates who describe the ng-template expansion and the insert/destroy lifecycle show real understanding.
Context
This probes the embedded-view context mechanism — the let- microsyntax that ngIf/ngFor/ngComponentOutlet use to expose values to the template body.
The mechanical answer
createEmbeddedView(tpl, context) accepts a context object whose properties become the template's let- bindings. In the template, let-item exposes context.item; the compiler lowers let-item to a read of the embedded view's context slot. This is how *ngFor="let item of items; index as i" works — the context carries item, index, count, etc. Custom directives implement the same by defining @Input() set with template-bound values and passing a context to createEmbeddedView. The context object is the contract between the directive logic and the template body; it is typed via the directive's ngTemplateContextGuard for strict templates, giving template authors type-checked let- variables.
Trap
The naive answer is 'let- variables are just local variables'. They are slots on the embedded view's context object, injected by the directive. Another trap: forgetting the ngTemplateContextGuard when using strictTemplates — without it, let- variables are typed as unknown/any and template type-checking degrades. Candidates who mention the context object and the type guard demonstrate real advanced knowledge.