skip to content

Asterisk Microsyntax

The * prefix desugars an element into an <ng-template> that a directive stamps out through TemplateRef and ViewContainerRef. Interviewers test microsyntax keys and template type guards.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

5

What does the asterisk prefix on an Angular structural directive, as in `<li *ngIf="user as u">`, desugar to at compile time?

level: juniorimportance: must knowfreq 60%

answer

  1. a compile-time rewrite, not a runtime flag
  2. one extra wrapper element
  3. only the directive and its bindings move
  4. the directive decides what gets stamped out

basics

~10 s

The Angular compiler wraps the element in an ng-template that carries only the structural directive and its microsyntax bindings; the directive then renders that template, or not, through TemplateRef and ViewContainerRef.

solid answer

~40 s

The `*` is template sugar the Angular compiler expands. `<li class="row" *ngIf="user as u">` becomes `<ng-template [ngIf]="user" let-u="ngIf"><li class="row">…</li></ng-template>`: the directive and its microsyntax bindings move onto the `<ng-template>`, while every other attribute stays on the `<li>`. An `<ng-template>` renders nothing on its own, so the directive injects `TemplateRef` (the template's content) and `ViewContainerRef` (the place where views go) and calls `createEmbeddedView()` to render it or `clear()` to remove it. In the DOM a comment node marks the spot. `NgIf` itself has been deprecated since v20 in favour of `@if`, but the `*` shorthand is still how any custom structural directive is applied.

code

html · 7 lines
html
<!-- Shorthand -->
<li class="row" *ngIf="user as u">{{ u.name }}</li>

<!-- What the compiler produces -->
<ng-template [ngIf]="user" let-u="ngIf">
  <li class="row">{{ u.name }}</li>
</ng-template>

go deeper

for a junior

Recall that the asterisk wraps the element in an ng-template and that a false condition removes the element from the DOM rather than hiding it.

for a middle

Explain which parts move onto the ng-template, how as and let become template variables, and how TemplateRef and ViewContainerRef turn the template into embedded views.

for a senior

Show that removal destroys child components and their state, and explain why the asterisk stays relevant for custom directives even though NgIf, NgFor and NgSwitch are deprecated.

for a principal

Frame the shorthand as a compile-time contract: it lets teams ship reusable rendering policies as directives while control-flow blocks cover everyday conditions and loops.

## What the asterisk is In an Angular template, a **structural directive** is a directive that decides whether, and how many times, a piece of template is rendered. Structural directives work on an `<ng-template>` element: a template definition that Angular parses and compiles but does **not** render by default. Writing the `<ng-template>` by hand every time would be verbose, so Angular offers a shorthand: prefix the directive's attribute selector with an asterisk (`*`). The Angular documentation calls the value syntax after the asterisk **microsyntax**. The key point for an interview is that the asterisk is resolved by the **compiler**, not at runtime. There is no runtime "star" behaviour and no CSS hiding. The compiler rewrites the element into the long form and then compiles that long form like any other template. ## The rewrite, step by step Take `<li class="row" *ngIf="user as u">{{ u.name }}</li>`. The compiler: 1. Creates an `<ng-template>` element in the position of the `<li>`. 2. Moves the directive onto it: the naked expression `user` becomes the property binding `[ngIf]="user"`. 3. Turns the rest of the microsyntax into bindings or template variables: `user as u` declares `let-u="ngIf"`, a template variable read from the `ngIf` property of the template's context object. 4. Leaves the original `<li>`, with `class="row"` and any other attributes, event bindings or property bindings, **inside** the `<ng-template>` as its content. The result is equivalent to writing: ```html <ng-template [ngIf]="user" let-u="ngIf"> <li class="row">{{ u.name }}</li> </ng-template> ``` Only the structural directive and its microsyntax bindings are moved. A common wrong answer is that all attributes move to the template; they do not, and that is why `class="row"` still lands on the rendered `<li>`. ## What happens at runtime Once the template exists, the directive does the actual work: - It injects **`TemplateRef`**, a handle to the content of the `<ng-template>` it sits on. - It injects **`ViewContainerRef`**, a container anchored at the template's position, into which views can be inserted. - To show the content, it calls `viewContainerRef.createEmbeddedView(templateRef, context)`, which instantiates the template as an **embedded view** with a context object whose properties `let-` variables read. - To hide it, it calls `viewContainerRef.clear()`, which destroys the views in the container, or `remove(index)` for one view. - Nothing is rendered for the `<ng-template>` element itself. The container is anchored by a comment node (dev builds label it `container`), and embedded views are inserted next to it. So "hiding" with a structural directive means the element is **not in the DOM at all** and its child components are destroyed, not merely invisible. ## Shorthand versus long form | Aspect | `*` shorthand | Explicit `<ng-template>` | | --- | --- | --- | | Who writes the wrapper | The compiler | You | | Where other attributes go | Stay on the inner element | Wherever you put them | | Directives per element | One starred directive | Nest templates or `<ng-container>` as you like | | Typical use | Applying a structural directive in markup | Templates passed around by reference | The long form and the shorthand compile to the same thing; the shorthand simply saves you from writing the wrapper and the `let-`/`[prefixKey]` attributes by hand. ## Common points interviewers probe - **Timing of evaluation.** Because the content sits in a template, nothing inside it is created until the directive asks for a view. Child components are not constructed, their inputs are not bound and their lifecycle hooks do not run while the condition is false. - **Template variables are scoped.** A variable declared with `let` or `as` exists only inside the generated `<ng-template>`, so it cannot be read by a sibling element outside the starred one. - **Bindings on the inner element still work.** Event bindings such as `(click)` and property bindings such as `[disabled]` stay on the element and are compiled as part of the template's content, so they behave exactly as they would without the asterisk. - **The long form is legal.** Writing the `<ng-template>` by hand is valid and is what you do when you need a template reference, for example an `else` template, instead of the sugar. - **Selectors match the expanded form.** `NgFor`'s selector is `[ngFor][ngForOf]`; the expanded template satisfies it with a plain `ngFor` attribute and a bound `ngForOf` property, which is why the directive still matches. ## Where the asterisk stands in Angular 22 Since Angular v17 the built-in control-flow blocks `@if`, `@for` and `@switch` are the recommended way to write conditions and loops, and `NgIf`, `NgFor` and `NgSwitch` have been **deprecated since v20**. That deprecation covers those three directives, not the asterisk. The shorthand remains the way to apply any structural directive, including ones you write yourself, such as a permission check, and interviewers still use `*ngIf` as the classic example because most existing codebases contain it. When you explain it, say that new conditional code uses `@if`, and that `*` is the sugar every structural directive, built-in or custom, relies on.

  • In `*ngIf="user as u"`, what does `u` read, and would `let u` give the same value?
    `user as u` becomes `let-u="ngIf"`, so `u` reads the `ngIf` property of the template context. `NgIf` sets both `$implicit` and `ngIf` on its context to the condition value, so `let u` alone, which reads `$implicit`, would give the same value here. Other directives may put different values on those two keys.
  • When a structural directive hides its content, are child components destroyed or just hidden?
    Destroyed. Hiding means the directive calls `clear()` or `remove()` on its `ViewContainerRef`, which destroys the embedded view: its DOM nodes leave the document, and components inside it run `ngOnDestroy`. Showing again creates a fresh view with fresh component instances and state.

saying these in an interview costs you the question

  • The asterisk tells Angular to hide the element with CSS when the condition is false.
  • Every attribute on the starred element moves onto the generated ng-template.
  • The ng-template wrapper is rendered into the DOM as an extra element.
  • The asterisk is a runtime feature evaluated by the browser, not a compiler rewrite.
  • Because NgIf is deprecated, the asterisk syntax itself is deprecated too.
open as a page

How would you write an Angular `*appHasRole` structural directive that renders its content only when the current user holds a given role?

level: middleimportance: must knowfreq 55%

basics

~10 s

Inject TemplateRef and ViewContainerRef, read a role input named after the selector, and call createEmbeddedView when the role is held or clear when it is not, guarding so the view is never created twice.

open as a page

Why does Angular reject `*ngIf` and `*ngFor` on the same element, and how do you apply both structural directives?

level: juniorimportance: should knowfreq 48%

basics

~20 s

Each asterisk expands into an ng-template around the element, and one element can only have one such wrapper, so the compiler rejects a second starred directive; nest them, putting the outer one on an ng-container.

open as a page

In Angular's structural-directive microsyntax, how do `let`, `as` and keys like `of` or `trackBy` map onto the directive's inputs and template variables?

level: middleimportance: should knowfreq 38%

basics

~20 s

A naked first expression binds to the input named after the selector; other keys get the selector prefix and a capital letter, so of becomes ngForOf; let x reads $implicit, and as or let x = y read named context properties.

open as a page

How do `ngTemplateContextGuard` and `ngTemplateGuard_` let Angular's template type checker type a custom structural directive's context and narrow its input?

level: seniorimportance: should knowfreq 24%

basics

~20 s

Both are static members read only by Angular's template type checker: ngTemplateContextGuard is a type predicate that gives the template's let variables a real context type, and ngTemplateGuard_<input> narrows the bound expression inside the template.

open as a page