All topics
Directivesadvanced

Custom Structural Directive Creation

Explain how to build a custom structural directive using TemplateRef and ViewContainerRef.

Building a custom structural directive — something like *appUnless or a permission-based *appHasRole — is a favorite advanced interview exercise because it requires you to demonstrate real understanding of TemplateRef and ViewContainerRef, the two primitives that every structural directive (including Angular's own NgIf/NgFor) is built on top of.

TemplateRef is like a stencil that knows what shape to cut, and ViewContainerRef is the wall where you decide to spray-paint that shape or wipe it clean — the directive is simply the hand deciding, based on a rule, whether to spray or wipe at any given moment.

Key Concepts

1
TemplateRef represents the <ng-template> content that the asterisk syntax wraps your element in — it's a factory for creating "embedded views," not a view itself. ViewContainerRef represents a location in the DOM where views can be inserted, and it's injected into the directive automatically because Angular knows the directive is applied to an ng-template. The directive's entire job is deciding when to call viewContainerRef.createEmbeddedView(templateRef) to insert the content, and when to call viewContainerRef.clear() to remove it.
TemplateRef<ng-template>ViewContainerRefng-templateviewContainerRef.createEmbeddedView(templateRef)
2
A well-designed custom structural directive also uses a static type guard method (ngTemplateContextGuard or ngTemplateGuard_xxx conventions) so that TypeScript can correctly narrow types inside the template when using template input variables — this is exactly how NgIf's as syntax achieves type narrowing, and mentioning this pattern signals deeper-than-surface knowledge.
ngTemplateContextGuardngTemplateGuard_xxxNgIfas
3
A good interview answer also touches on the input naming convention: *appUnless="condition" requires the directive's selector to be [appUnless] and to expose an @Input('appUnless') property with a name matching the directive's selector exactly, because that's the microsyntax rule the structural directive parser relies on to map the asterisk shorthand's value to the correct input.
*appUnless="condition"[appUnless]@Input('appUnless')