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?
There is special section for grammar of structual directives in Angular docs.
You can check out my interactive DEMO
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With