Demystifying the syntax of structural directive
Structural directive is a special directive. Angular comes with a couple of them
such as ngIf
, ngFor
. Angular provides detailed
documentation about the
structural directive. There are two syntaxes to use a directive, the sugar syntax
( starting with an asterisk *
) and the <ng-template>
syntax. The sugar syntax
is shorter because it combines the following two features of <ng-template>
binding.
- Use
@Input
binding to pass values into a directive - Declare template variable used inside the template.
Let’s use built-in structural directives ngFor
and ngIf
for this demo.
The following is the skeleton of the ngIf
directive.
@Directive({
selector: "[ngIf]",
standalone: true,
})
export class NgIf<T = unknown> {
@Input()
set ngIf(condition: T) {}
@Input()
set ngIfThen(templateRef: TemplateRef<NgIfContext<T>> | null) {}
@Input()
set ngIfElse(templateRef: TemplateRef<NgIfContext<T>> | null) {}
}
The <ng-template>
syntax is as follows.
<ng-template
[ngIf]="user"
[ngIfElse]="elseBlock"
let-implicit
let-userAlias="ngIf"
>
<div>
<p>1. $implicit: <strong> {{implicit.first}}, {{implicit.last}}</strong></p>
<p>2. ngIf: <strong> {{userAlias.first}}, {{userAlias.last}}</strong></p>
<p>
3. property of parent template:
<strong> {{user.first}}, {{user.last}}</strong>
</p>
</div>
</ng-template>
The sugar syntax is as follows. It is a convention that all the input names
start with an attribute ngIf
, the remaining part is used in the sugar syntax.
For example, ngIfElse
is converted else
. ngIf
is a bit special, because
the remaining part is empty, this input binding is the first binding.
<div *ngIf="user; else: elseBlock; let implicit; let userAlias = ngIf">
<p>1. $implicit: <strong> {{implicit.first}}, {{implicit.last}}</strong></p>
<p>2. ngIf: <strong> {{userAlias.first}}, {{userAlias.last}}</strong></p>
<p>
3. property of parent template:
<strong> {{user.first}}, {{user.last}}</strong>
</p>
</div>
For the template binding, there is another syntax. alias as context_member
.
<div *ngIf="user; else: elseBlock; let implicit; ngIf as userAlias">
<p>1. $implicit: <strong> {{implicit.first}}, {{implicit.last}}</strong></p>
<p>2. ngIf: <strong> {{userAlias.first}}, {{userAlias.last}}</strong></p>
<p>
3. property of parent template:
<strong> {{user.first}}, {{user.last}}</strong>
</p>
</div>
In the case of ngIf
, it can be simplified as
<div *ngIf="user as userAlias; else: elseBlock; let implicit">
<p>1. $implicit: <strong> {{implicit.first}}, {{implicit.last}}</strong></p>
<p>2. ngIf: <strong> {{userAlias.first}}, {{userAlias.last}}</strong></p>
<p>
3. property of parent template:
<strong> {{user.first}}, {{user.last}}</strong>
</p>
</div>
Let’s take a look at how to use ngFor
. The skeleton of the ngFor
directive is as followed.
@Directive({
selector: "[ngFor][ngForOf]",
standalone: true,
})
export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>>
implements DoCheck
{
@Input()
set ngForOf(ngForOf: (U & NgIterable<T>) | undefined | null) {
//..
}
@Input()
set ngForTrackBy(fn: TrackByFunction<T>) {
//..
}
set ngForTemplate(value: TemplateRef<NgForOfContext<T, U>>) {
//..
}
}
The context for the directive is as follows.
export class NgForOfContext<T, U extends NgIterable<T> = NgIterable<T>> {
$implicit: T;
ngForOf: U;
index: number;
count: number;
first boolean;
last: boolean;
even: boolean;
odd: boolean;
}
The selector is [ngFor][ngForOf]
, which means you have to use both attributes at the same time
to use this directive. So in the following example, we use <ng-template>
, and
we use both ngFor
and ngForOf
together.
<ng-template ngFor [ngForOf]="hobbies" let-hobby let-i="index" let-odd="odd">
<div [class.odd]="odd">({{i}}) {{hobby}}</div>
</ng-template>
The sugar syntax is for ngFor
a little bit special. This is because the directive does not
have ngFor
@Input
, but we want still want to use *ngIf
. In this case, we need to use declare
template variable first, like the following.
<div
*ngFor="let hobby; let i=index; let odd=odd; of hobbies;"
[class.odd]="odd"
>
({{i}}) {{hobby}}
</div>
But the following seems to be more natural, and it is also acceptable.
<div *ngFor="let hobby of hobbies; let i=index; let odd=odd;" [class.odd]="odd">
({{i}}) {{hobby}}
</div>