Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the exact grammar for Angulars structural directives

Angulars documentation explains, that structural directives, such as <p *ngIf="a as b"></p> are "desugared" into <p [ngIf]="a" [ngIfAs]="b">.

The desugaring makes use of the microsyntax, that allows for expressions like

let node; when: hasChild
a as b
let x of y; index = i; trackBy: f

The documentation provides some examples of the microsyntax, and suggest studying the sources of ngIf, but it provides no formal definition.

What is the grammar for the microsyntax of angular's structural directives?

like image 362
keppla Avatar asked Mar 04 '23 00:03

keppla


1 Answers

There is special section for grammar of structual directives in Angular docs.

You can check out my interactive DEMO

enter image description here

So, what's the logic here?

Once Angular meets structural directive it tries to parse it:

 *dir="..."
/\
indicates that it's a structural directive

At the beginning there are 3 cases:

*dir="expr
       \/
   [dir]="expr"

*dir="let var  // also var can have value
       \/ // starts with keyword 'let', indicates variable
   [dir] let-x="$impicit"


*dir="as var
      \/ // starts with keyword 'as'
  let-x="$implicit" // it won't fail but your directive won't be instantiated
      

After an expression you can use either as keyword to define template input variable for that expression or a separator such as whitespace, colon, semicolon or comma:

*dir="expr as var
 \/
[dir]="exp" let-var="dir"

*dir="expr[ ]
*dir="expr:
*dir="expr;
*dir="expr,

Note that dir is considered here as the first template binding key.

Now it's time to another key or variable:

*dir="expr key2
*dir="expr:key2
*dir="expr;key2
*dir="expr,key2

And we can assign value to that key through whitespace or semicolon:

*dir="expr key2 exp2
*dir="expr:key2 exp2
*dir="expr;key2 exp2
*dir="expr,key2 exp2
or
*dir="expr key2:exp2

And this way we can produce other keys. These key are capitalized and concatenated with the first key.

*dir="expr key2 exp2 key3 exp3 ...
\/
[dir]="expr " [dirKey2]="exp2 " [dirKey3]="exp3"


let node; when: hasChild; otherKey: otherValue
  \/       \/      \/
  var     key    value
                         \/
dir [dirWhen]="hasChild" [dirOtherKey]="otherValue" let-node="$implicit"


*dir="let x of y; index = i; trackBy: f"
           \/
dir [dirOf]="y" [dirIndex]="= i" [dirTrackBy]="f" let-x="$implicit"

*dir="let x  of   y;  let index = i; trackBy: f"
        \/   \/   \/      \/           \/    \/
        var  key  value     var          key   value
                   \/
dir [dirOf]="y" [dirTrackBy]="f" let-x="$implicit" let-index="i"
               

As you can see we can either define key-value or set template input variables through let or as keywords

If you think that Angular docs doesn't completely explain it then you can follow the source code

  // Parses the AST for `<some-tag *tplKey=AST>`
  parseTemplateBindings(tplKey: string): TemplateBindingParseResult {
    let firstBinding = true;
    const bindings: TemplateBinding[] = [];
    const warnings: string[] = [];
    do {
      const start = this.inputIndex;
      let rawKey: string;
      let key: string;
      let isVar: boolean = false;
      if (firstBinding) {
        rawKey = key = tplKey;
        firstBinding = false;
      } else {
        isVar = this.peekKeywordLet();
        if (isVar) this.advance();
        rawKey = this.expectTemplateBindingKey();
        key = isVar ? rawKey : tplKey + rawKey[0].toUpperCase() + rawKey.substring(1);
        this.optionalCharacter(chars.$COLON);
      }

      let name: string = null !;
      let expression: ASTWithSource|null = null;
      if (isVar) {
        if (this.optionalOperator('=')) {
          name = this.expectTemplateBindingKey();
        } else {
          name = '\$implicit';
        }
      } else if (this.peekKeywordAs()) {
        this.advance();  // consume `as`
        name = rawKey;
        key = this.expectTemplateBindingKey();  // read local var name
        isVar = true;
      } else if (this.next !== EOF && !this.peekKeywordLet()) {
        const start = this.inputIndex;
        const ast = this.parsePipe();
        const source = this.input.substring(start - this.offset, this.inputIndex - this.offset);
        expression = new ASTWithSource(ast, source, this.location, this.errors);
      }

      bindings.push(new TemplateBinding(this.span(start), key, isVar, name, expression));
      if (this.peekKeywordAs() && !isVar) {
        const letStart = this.inputIndex;
        this.advance();                                   // consume `as`
        const letName = this.expectTemplateBindingKey();  // read local var name
        bindings.push(new TemplateBinding(this.span(letStart), letName, true, key, null !));
      }
      if (!this.optionalCharacter(chars.$SEMICOLON)) {
        this.optionalCharacter(chars.$COMMA);
      }
    } while (this.index < this.tokens.length);

    return new TemplateBindingParseResult(bindings, warnings, this.errors);
}

The code above describes the algorithm of how a structural directive is parsed.

See also

  • https://angular.io/guide/structural-directives#structural-directive-syntax-reference
like image 60
yurzui Avatar answered Mar 24 '23 04:03

yurzui