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.

  1. Use @Input binding to pass values into a directive
  2. 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>