Structural Directives

Structural directives are responsible for HTML layout. They shape or reshape the DOM's structure, typically by adding, removing, or manipulating elements.

How Structural Directives Work

Structural directives use a * prefix on an element. AngularDart desugars this into an <ng-template> wrapper:

<!-- Shorthand -->
<div *ngIf="user">Hello, {{ user.name }}</div>

<!-- Desugared -->
<ng-template [ngIf]="user">
  <div>Hello, {{ user.name }}</div>
</ng-template>

*ngIf

Conditionally includes a template based on the truthiness of an expression.

Basic usage

<div *ngIf="isVisible">This is visible</div>

With else clause

<div *ngIf="user; else noUser">
  Hello, {{ user.name }}
</div>
<ng-template #noUser>
  <p>Please log in.</p>
</ng-template>

With then and else

<div *ngIf="isLoggedIn; then loggedIn; else loggedOut"></div>
<ng-template #loggedIn><p>Welcome back!</p></ng-template>
<ng-template #loggedOut><p>Please sign in.</p></ng-template>

Truthy and falsy values

In Dart, only null and false are falsy. All other values (including 0 and '') are truthy.

<!-- All of these are truthy -->
<div *ngIf="1">Shows</div>
<div *ngIf="'hello'">Shows</div>
<div *ngIf="[]">Shows</div>

<!-- These are falsy -->
<div *ngIf="null">Hidden</div>
<div *ngIf="false">Hidden</div>

*ngFor

Renders a template for each item in a collection.

Basic usage

<li *ngFor="let item of items">{{ item.name }}</li>

Template context variables

<li *ngFor="let item of items;
            let i = index;
            let isFirst = first;
            let isLast = last;
            let isEven = even;
            let isOdd = odd;
            let total = count">
  {{ i + 1 }}/{{ total }}. {{ item.name }}
  <span *ngIf="isFirst">(first)</span>
  <span *ngIf="isLast">(last)</span>
</li>

Track by function

Use trackBy to optimize re-rendering when the collection changes:

<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
dynamic trackById(int index, dynamic item) => item.id;

Empty template

Use *ngIf with *ngFor to handle empty collections:

<ul *ngIf="items.isNotEmpty; else emptyList">
  <li *ngFor="let item of items">{{ item.name }}</li>
</ul>
<ng-template #emptyList>
  <p>No items found.</p>
</ng-template>

*ngSwitch

Conditionally swaps elements based on a switch expression.

<div [ngSwitch]="transportState">
  <app-urgent-component *ngSwitchCase="'urgent'"></app-urgent-component>
  <app-happy-component *ngSwitchCase="'happy'"></app-happy-component>
  <app-normal-component *ngSwitchCase="'normal'"></app-normal-component>
  <app-unknown-component *ngSwitchDefault></app-unknown-component>
</div>

Creating Custom Structural Directives

@Directive(
  selector: '[myUnless]',
)
class MyUnlessDirective {
  final ViewContainerRef _viewContainer;
  final TemplateRef _templateRef;
  bool _condition = false;

  MyUnlessDirective(this._viewContainer, this._templateRef);

  @Input()
  set myUnless(bool condition) {
    _condition = condition;
    if (!_condition) {
      _viewContainer.createEmbeddedView(_templateRef);
    } else {
      _viewContainer.clear();
    }
  }
}

Usage:

<p *myUnless="isHidden">This paragraph is displayed when isHidden is false.</p>

ng-template

Use <ng-template> to define a template that is not rendered by default:

<ng-template #myTemplate>
  <p>This is a template</p>
</ng-template>

<ng-container *ngTemplateOutlet="myTemplate"></ng-container>

ng-container

<ng-container> is a grouping element that doesn't interfere with styles or layout because AngularDart doesn't put it in the DOM:

<p>
  I turned the corner
  <ng-container *ngIf="hero">
    and saw {{ hero.name }}. I waved
  </ng-container>
  and continued on my way.
</p>