Where does Angular define "as local-var" behavior for *ngIf?
Asked Answered
W

1

13

I am trying to understand where is the "as local-var" optional behavior of ngIf defined, e.g.: *ngIf="user$ | async as user"

Tried looking into obvious places in source, like https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_if.ts

But nothing in the code, only docs.

Does anyone know where in the code this magic is happening ?

Wornout answered 7/10, 2017 at 3:18 Comment(4)
Could this be inside async pipe?Focalize
Nope :) checked it aswell.Wornout
I think then it should be somewhere in the parser because we can do as on any value directive it does not really belongs to ngIfFocalize
Surely it will be somewhere in the compiler module?Sangraal
L
35

Yeah, it's magic that happens during template compilation.

The template below

<div *ngIf="user$ | async as user"></div>

is just sugar for:

<ng-template [ngIf]="user$ | async" let-user="ngIf">
  <div></div>
</ng-template>

So the answer: the following string passes value to 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 structural directive ngVar:

@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 like:

<ng-template [ngVar]="true" let-x="ngVar"><div>{{x}}</div></ng-template>

or

<div *ngVar="true as x">{{x}}</div>

What's the magic?

If you want to understand where is the magic in compiler then let's take a look at an example:

<div *ngVar="true as x"></div>

1) Angular compiler tokenizes this string like:

<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 element's tree based on these tokens:

Element div
       attrs: name:  *ngIf
              value: true as x

3) TemplateParser builds AST(abstract syntax node) tree. To do this TemplateParser uses special visitor called TemplateParseVisitor

This visitor goes through all tree received in the previous step. And let's look at how it works when compiler comes to visitElement:

enter image description here

So as we can see any template with structural directive like:

*dir="someValue as someVar"

represents the following:

<ng-template [dir]="someValue" let-someVar="dir">

See also:

Lucie answered 7/10, 2017 at 3:46 Comment(9)
Wow.. so its actually "let-ANYTHING" directive ? Where anything will be the name of local var ?Wornout
Looks like your post is not finished :) please continue. (I found the "as" keyword inside of the lexer, but cannot connect the dots on how "this.context.$implicit" is actually putting the variable in scope... because this IS the scope ?Wornout
There is no "let-ANYTHING" directive. Compiler just creates variable let-anything internally based on structural syntaxLucie
This way *dir="someValue as someVar" will be <ng-template [dir]="someValue" let-someVar="dir">Lucie
O, i've got it. Thanks for the thorough explanation !Wornout
This is AWESOME. BUT I just cannot understand why it is not already in the framework. And I have to believe there is a reason why it isn't there - like perhaps it wouldn't work well with change detection or some other such weirdness. Does needing such a feature mean we're doing something fundamentally wrong with out templates? Or should we all be using this?Suksukarno
since its implicit why can't I do this <ng-template [ngVar]="true" let-x><div>{{x}}</div></ng-template> notice I had removed let-x=ngVarWhorl
@Lucie thanks, for quick response, So this line this.context.ngVar = context; is required, the way as syntax is de-sugared. Pretty cool.Whorl
Great explanation... but this *ngVar looks ugly. Asterisk usually means structural directive which is not in this case. Should be something out of the box in my opinion.Coquetry

© 2022 - 2024 — McMap. All rights reserved.