Yes, this is the magic that happens during template compilation.
Template below
<div *ngIf="user$ | async as user"></div>
- it's just sugar for:
<ng-template [ngIf]="user$ | async" let-user="ngIf"> <div></div> </ng-template>
So the answer is: the following line passes the value of this variable:
this._context.$implicit = this._context.ngIf = condition; ^^^^^^^^^^^^^
https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_if.ts#L115
For example, we can create the ngVar structure directive:
@Directive({ selector: '[ngVar]', }) export class VarDirective { @Input() set ngVar(context: any) { this.context.$implicit = this.context.ngVar = context; ^^^^^^^^^^^^^^^^ this.updateView(); } context: any = {}; constructor(private vcRef: ViewContainerRef, private templateRef: TemplateRef<any>) {} updateView() { this.vcRef.clear(); this.vcRef.createEmbeddedView(this.templateRef, this.context); } }
and use it either as:
<ng-template [ngVar]="true" let-x="ngVar"><div>{{x}}</div></ng-template>
or
<div *ngVar="true as x">{{x}}</div>
What kind of magic?
If you want to understand where the magic is in the compiler, let's look at an example:
<div *ngVar="true as x"></div>
1) Angular compiler tokenizes this line, therefore
<div *ngVar="true as x"></div> (1) (2) (3) (4) (5) (1) - TAG_OPEN_START (2) - ATTR_NAME (3) - ATTR_VALUE (4) - TAG_OPEN_END (5) - TAG_CLOSE
2) HtmlParser creates a tree of elements based on these these tokens
Element div attrs: name: *ngIf value: true as x
3) TemplateParser builds an AST tree (abstract node syntax). For this, TemplateParser uses a special visitor called TemplateParseVisitor
This visitor goes through the whole tree obtained in the previous step. And let's see how this works when the compiler arrives at visitElement :

So, we can see any template with a structural directive, for example:
*dir="someValue as someVar"
represents the following:
<ng-template [dir]="someValue" let-someVar="dir">
See also: