Structural Directives 6.0


Angular has a powerful template engine that lets us easily manipulate the DOM structure of our elements.

This guide looks at how Angular manipulates the DOM with structural directives and how you can write your own structural directives to do the same thing.

Try the live example (view source).

What are structural directives?

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

As with other directives, you apply a structural directive to a host element. The directive then does whatever it’s supposed to do with that host element and its descendents.

Structural directives are easy to recognize. An asterisk (*) precedes the directive attribute name as in this example.

<div *ngIf="hero != null" >{{hero.name}}</div>

No brackets. No parentheses. Just *ngIf set to a string.

You’ll learn in this guide that the asterisk (*) is a convenience notation and the string is a microsyntax rather than the usual template expression. Angular desugars this notation into a marked-up <template> that surrounds the host element and its descendents. Each structural directive does something different with that template.

Three of the common, built-in structural directives — NgIf, NgFor, and NgSwitch… — are described in the Template Syntax guide and seen in samples throughout the Angular documentation. Here’s an example of them in a template:

<div *ngIf="hero != null" >{{hero.name}}</div>

<ul>
  <li *ngFor="let hero of heroes">{{hero.name}}</li>
</ul>

<div [ngSwitch]="hero?.emotion">
  <happy-hero    *ngSwitchCase="'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      *ngSwitchCase="'sad'"      [hero]="hero"></sad-hero>
  <confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  *ngSwitchDefault           [hero]="hero"></unknown-hero>
</div>

This guide won’t repeat how to use them. But it does explain how they work and how to write your own structural directive.

Directive spelling

Throughout this guide, you’ll see a directive spelled in both UpperCamelCase and lowerCamelCase. Already you’ve seen NgIf and ngIf. There’s a reason. NgIf refers to the directive class; ngIf refers to the directive’s attribute name.

A directive class is spelled in UpperCamelCase (NgIf). A directive’s attribute name is spelled in lowerCamelCase (ngIf). The guide refers to the directive class when talking about its properties and what the directive does. The guide refers to the attribute name when describing how you apply the directive to an element in the HTML template.

There are two other kinds of Angular directives, described extensively elsewhere: (1) components and (2) attribute directives.

A component manages a region of HTML in the manner of a native HTML element. Technically it’s a directive with a template.

An attribute directive changes the appearance or behavior of an element, component, or another directive. For example, the built-in NgStyle directive changes several element styles at the same time.

You can apply many attribute directives to one host element. You can only apply one structural directive to a host element.

NgIf case study

NgIf is the simplest structural directive and the easiest to understand. It takes a boolean expression and makes an entire chunk of the DOM appear or disappear.

<p *ngIf="true">
  Expression is true and ngIf is true.
  This paragraph is in the DOM.
</p>
<p *ngIf="false">
  Expression is false and ngIf is false.
  This paragraph is not in the DOM.
</p>

The ngIf directive doesn’t hide elements with CSS. It adds and removes them physically from the DOM. Confirm that fact using browser developer tools to inspect the DOM.

ngIf=false element not in DOM

The top paragraph is in the DOM. The bottom, disused paragraph is not; in its place is a comment about “template bindings” (more about that later).

When the condition is false, NgIf removes its host element from the DOM, detaches it from DOM events (the attachments that it made), detaches the component from Angular change detection, and destroys it. The component and DOM nodes can be garbage-collected and free up memory.

Why remove rather than hide?

A directive could hide the unwanted paragraph instead by setting its display style to none.

<p [style.display]="'block'">
  Expression sets display to "block".
  This paragraph is visible.
</p>
<p [style.display]="'none'">
  Expression sets display to "none".
  This paragraph is hidden but still in the DOM.
</p>

While invisible, the element remains in the DOM.

hidden element still in DOM

The difference between hiding and removing doesn’t matter for a simple paragraph. It does matter when the host element is attached to a resource intensive component. Such a component’s behavior continues even when hidden. The component stays attached to its DOM element. It keeps listening to events. Angular keeps checking for changes that could affect data bindings. Whatever the component was doing, it keeps doing.

Although invisible, the component—and all of its descendant components—tie up resources. The performance and memory burden can be substantial, responsiveness can degrade, and the user sees nothing.

On the positive side, showing the element again is quick. The component’s previous state is preserved and ready to display. The component doesn’t re-initialize—an operation that could be expensive. So hiding and showing is sometimes the right thing to do.

But in the absence of a compelling reason to keep them around, your preference should be to remove DOM elements that the user can’t see and recover the unused resources with a structural directive like NgIf .

These same considerations apply to every structural directive, whether built-in or custom. Before applying a structural directive, you might want to pause for a moment to consider the consequences of adding and removing elements and of creating and destroying components.

The asterisk (*) prefix

Surely you noticed the asterisk (*) prefix to the directive name and wondered why it is necessary and what it does.

Here is *ngIf displaying the hero’s name if hero exists.

<div *ngIf="hero != null" >{{hero.name}}</div>

The asterisk is syntactic sugar for something a bit more complicated. Internally, Angular desugars it into a template element, wrapped around the host element, like this.

<template [ngIf]="hero != null">
  <div>{{hero.name}}</div>
</template>
  • The *ngIf directive moved to the <template> element where it became a property binding,[ngIf].
  • The rest of the <div>, including its class attribute, moved inside the <template> element.

None of these forms are actually rendered. Only the finished product ends up in the DOM.

hero div in DOM

Angular consumed the <template> content during its actual rendering and replaced the <template> with a diagnostic comment.

The NgFor and NgSwitch directives follow the same pattern.

Inside *ngFor

Angular transforms the *ngFor in similar fashion from asterisk (*) syntax through template attribute to template element.

Here’s a full-featured app of NgFor, written all three ways:

<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackByHeroId"
     [class.odd]="odd">
  ({{i}}) {{hero.name}}
</div>

<template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd"
          [ngForTrackBy]="trackByHeroId">
  <div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</template>

This is manifestly more complicated than ngIf and rightly so. The NgFor directive has more features, both required and optional, than the NgIf shown in this guide. At minimum NgFor needs a looping variable (let hero) and a list (heroes).

You enable these features in the string assigned to ngFor, which you write in Angular’s microsyntax.

Everything outside the ngFor string stays with the host element (the <div>) as it moves inside the <template>. In this example, the [ngClass]="odd" stays on the <div>.

Microsyntax

The Angular microsyntax lets you configure a directive in a compact, friendly string. The microsyntax parser translates that string into attributes on the <template>:

  • The let keyword declares a template input variable that you reference within the template. The input variables in this example are hero, i, and odd. The parser translates let hero, let i, and let odd into variables named, let-hero, let-i, and let-odd.

  • The microsyntax parser takes of and trackby, title-cases them (of -> Of, trackBy -> TrackBy), and prefixes them with the directive’s attribute name (ngFor), yielding the names ngForOf and ngForTrackBy. Those are the names of two NgFor input properties . That’s how the directive learns that the list is heroes and the track-by function is trackById.

  • As the NgFor directive loops through the list, it sets and resets properties of its own context object. These properties include index and odd and a special property named $implicit.

  • The let-i and let-odd variables were defined as let i=index and let odd=odd. Angular sets them to the current value of the context’s index and odd properties.

  • The context property for let-hero wasn’t specified. It’s intended source is implicit. Angular sets let-hero to the value of the context’s $implicit property which NgFor has initialized with the hero for the current iteration.

  • The API guide describes additional NgFor directive properties and context properties.

These microsyntax mechanisms are available to you when you write your own structural directives. Studying the source code for NgIf and NgFor is a great way to learn more.

Template input variable

A template input variable is a variable whose value you can reference within a single instance of the template. There are several such variables in this example: hero, i, and odd. All are preceded by the keyword let.

A template input variable is not the same as a template reference variable, neither semantically nor syntactically.

You declare a template input variable using the let keyword (let hero). The variable’s scope is limited to a single instance of the repeated template. You can use the same variable name again in the definition of other structural directives.

You declare a template reference variable by prefixing the variable name with # (#var). A reference variable refers to its attached element, component or directive. It can be accessed anywhere in the entire template.

Template input and reference variable names have their own namespaces. The hero in let hero is never the same variable as the hero declared as #hero.

One structural directive per host element

Someday you’ll want to repeat a block of HTML but only when a particular condition is true. You’ll try to put both an *ngFor and an *ngIf on the same host element. Angular won’t let you. You may apply only one structural directive to an element.

The reason is simplicity. Structural directives can do complex things with the host element and its descendents. When two directives lay claim to the same host element, which one takes precedence? Which should go first, the NgIf or the NgFor? Can the NgIf cancel the effect of the NgFor? If so (and it seems like it should be so), how should Angular generalize the ability to cancel for other structural directives?

There are no easy answers to these questions. Prohibiting multiple structural directives makes them moot. There’s an easy solution for this use case: put the *ngIf on a container element that wraps the *ngFor element. One or both elements can be an template so you don’t have to introduce extra levels of HTML.

Inside NgSwitch directives

The Angular NgSwitch is actually a set of cooperating directives: NgSwitch, NgSwitchCase, and NgSwitchDefault.

Here’s an example.

<div [ngSwitch]="hero?.emotion">
  <happy-hero    *ngSwitchCase="'happy'"    [hero]="hero"></happy-hero>
  <sad-hero      *ngSwitchCase="'sad'"      [hero]="hero"></sad-hero>
  <confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero>
  <unknown-hero  *ngSwitchDefault           [hero]="hero"></unknown-hero>
</div>

You might come across an NgSwitchWhen directive in older code. That is the deprecated name for NgSwitchCase.

The switch value assigned to NgSwitch (hero.emotion) determines which (if any) of the switch cases are displayed.

NgSwitch itself is not a structural directive. It’s an attribute directive that controls the behavior of the other two switch directives. That’s why you write [ngSwitch], never *ngSwitch.

NgSwitchCase and NgSwitchDefault are structural directives. You attach them to elements using the asterisk (*) prefix notation. An NgSwitchCase displays its host element when its value matches the switch value. The NgSwitchDefault displays its host element when no sibling NgSwitchCase matches the switch value.

The element to which you apply a directive is its host element. The <happy-hero> is the host element for the happy *ngSwitchCase. The <unknown-hero> is the host element for the *ngSwitchDefault.

As with other structural directives, the NgSwitchCase and NgSwitchDefault can be desugared into the <template> element form.

<div [ngSwitch]="hero?.emotion">
  <template [ngSwitchCase]="'happy'">
    <happy-hero [hero]="hero"></happy-hero>
  </template>
  <template [ngSwitchCase]="'sad'">
    <sad-hero [hero]="hero"></sad-hero>
  </template>
  <template [ngSwitchCase]="'confused'">
    <confused-hero [hero]="hero"></confused-hero>
  </template >
  <template ngSwitchDefault>
    <unknown-hero [hero]="hero"></unknown-hero>
  </template>
</div>

Prefer the asterisk (*) syntax

The asterisk (*) syntax is more clear than the other desugared forms.

While there’s rarely a good reason to apply a structural directive in template attribute or element form, it’s still important to know that Angular creates a <template> and to understand how it works. You’ll refer to the <template> when you write your own structural directive.

The template element

The HTML 5 template element is a formula for rendering HTML. It is never displayed directly. In fact, before rendering the view, Angular replaces the <template> and its contents with a comment.

If there is no structural directive and you merely wrap some elements in a <template>, those elements disappear. That’s the fate of the middle “Hip!” in the phrase “Hip! Hip! Hooray!”.

<p>Hip!</p>
<template>
  <p>Hip!</p>
</template>
<p>Hooray!</p>

Angular erases the middle “Hip!”, leaving the cheer a bit less enthusiastic.

template tag rendering

A structural directive puts a <template> to work as you’ll see when you write your own structural directive.

Group sibling elements

There’s often a root element that can and should host the structural directive. The list element (<li>) is a typical host element of an NgFor repeater.

<li *ngFor="let hero of heroes">{{hero.name}}</li>

When there isn’t a host element, you can usually wrap the content in a native HTML container element, such as a <div>, and attach the directive to that wrapper.

<div *ngIf="hero != null" >{{hero.name}}</div>

Introducing another container element—typically a <span> or <div>—to group the elements under a single root is usually harmless. Usually … but not always.

The grouping element may break the template appearance because CSS styles neither expect nor accommodate the new layout. For example, suppose you have the following paragraph layout.

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

You also have a CSS style rule that happens to apply to a <span> within a <p>aragraph.

p span { color: red; font-size: 70%; }

The constructed paragraph renders strangely.

spanned paragraph with bad style

The p span style, intended for use elsewhere, was inadvertently applied here.

Another problem: some HTML elements require all immediate children to be of a specific type. For example, the <select> element requires <option> children. You can’t wrap the options in a conditional <div> or a <span>.

When you try this,

<div>
  Pick your favorite hero
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <span *ngFor="let h of heroes">
    <span *ngIf="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </span>
  </span>
</select>

the drop down is empty.

spanned options don't work

The browser won’t display an <option> within a <span>.

template to the rescue

The Angular <template> is a grouping element that doesn’t interfere with styles or layout because Angular doesn’t put it in the DOM.

Here’s the conditional paragraph again, this time using <template>.

<p>
  I turned the corner
  <template [ngIf]="hero != null">
    and saw {{hero.name}}. I waved
  </template>
  and continued on my way. [template]
</p>

It renders properly. Notice the use of a desugared form of NgIf.

ngcontainer paragraph with proper style

Now conditionally exclude a select <option> with <template>.

<div>
  Pick your favorite hero 2
  (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>)
</div>
<select [(ngModel)]="hero">
  <template ngFor let-h [ngForOf]="heroes">
    <template [ngIf]="showSad || h.emotion !== 'sad'">
      <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option>
    </template>
  </template>
</select>

The drop down works properly.

ngcontainer options work properly

The <template> is a syntax element recognized by the Angular parser. It’s not a directive, component, class, or interface. It’s more like the curly braces in a Dart if-block:

if (someCondition) {
  statement1;
  statement2;
  statement3;
}

Without those braces, Dart would only execute the first statement when you intend to conditionally execute all of them as a single block. The <template> satisfies a similar need in Angular templates.

Write a structural directive

In this section, you write an UnlessDirective structural directive that does the opposite of NgIf. NgIf displays the template content when the condition is true. UnlessDirective displays the content when the condition is false.

<p *myUnless="condition">Show this sentence unless the condition is true.</p>

Creating a directive is similar to creating a component. Here’s how you might begin:

lib/src/unless_directive.dart (skeleton)

import 'package:angular/angular.dart';

@Directive(selector: '[myUnless]')
class UnlessDirective {
}

The directive’s selector is typically the directive’s attribute name in square brackets, [myUnless]. The brackets define a CSS attribute selector.

The directive attribute name should be spelled in lowerCamelCase and begin with a prefix. Don’t use ng. That prefix belongs to Angular. Pick something short that fits you or your company. In this example, the prefix is my.

The directive class name ends in Directive. Angular’s own directives do not.

TemplateRef and ViewContainerRef

A simple structural directive like this one creates an embedded view from the Angular-generated <template> and inserts that view in a view container adjacent to the directive’s original <p> host element.

You’ll acquire the <template> contents with a TemplateRef and access the view container through a ViewContainerRef.

You inject both in the directive constructor as private variables of the class.

TemplateRef _templateRef;
ViewContainerRef _viewContainer;

UnlessDirective(this._templateRef, this._viewContainer);

The myUnless property

The directive consumer expects to bind a true/false condition to [myUnless]. That means the directive needs a myUnless property, decorated with @Input

Read about @Input in the Template Syntax guide.

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

Angular sets the myUnless property whenever the value of the condition changes. Because the myUnless property does work, it needs a setter.

  • If the condition is false and the view hasn’t been created previously, tell the view container to create the embedded view from the template.

  • If the condition is true and the view is currently displayed, clear the container which also destroys the view.

Nobody reads the myUnless property so it doesn’t need a getter.

The completed directive code looks like this:

lib/src/unless_directive.dart (excerpt)

import 'package:angular/angular.dart';

@Directive(selector: '[myUnless]')
class UnlessDirective {
  bool _hasView = false;

  TemplateRef _templateRef;
  ViewContainerRef _viewContainer;

  UnlessDirective(this._templateRef, this._viewContainer);

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

Add this directive to the directives list of the AppComponent.

Then create some HTML to try it.

<p *myUnless="condition" class="unless a">
  (A) This paragraph is displayed because the condition is false.
</p>

<p *myUnless="!condition" class="unless b">
  (B) Although the condition is true,
  this paragraph is displayed because myUnless is set to false.
</p>

When the condition is false, the top (A) paragraph appears and the bottom (B) paragraph disappears. When the condition is true, the top (A) paragraph is removed and the bottom (B) paragraph appears.

UnlessDirective in action

Summary

You can both try and download the source code for this guide in the live example (view source).

Here is the source under the lib folder.

|import 'package:angular/angular.dart'; |import 'package:angular_forms/angular_forms.dart'; |import 'package:angular_components/angular_components.dart'; | |import 'src/hero.dart'; |import 'src/unless_directive.dart'; |import 'src/hero_switch_components.dart'; | |@Component( | selector: 'my-app', | templateUrl: 'app_component.html', | styleUrls: ['app_component.css'], | directives: [ | coreDirectives, | formDirectives, | heroSwitchComponents, | MaterialRadioComponent, | MaterialRadioGroupComponent, | UnlessDirective | ], | providers: [materialProviders], |) |class AppComponent { | final List<Hero> heroes = mockHeroes; | Hero hero; | bool condition = false; | final List<String> logs = []; | bool showSad = true; | String status = 'ready'; | | AppComponent() { | hero = heroes[0]; | } | | Object trackByHeroId(_, dynamic o) => o is Hero ? o.id : o; |} |<h1>Structural Directives</h1> | |<p>Conditional display of hero</p> | |<blockquote> |<div *ngIf="hero != null" >{{hero.name}}</div> |</blockquote> | |<p>List of heroes</p> | |<ul> | <li *ngFor="let hero of heroes">{{hero.name}}</li> |</ul> | | |<hr> | |<h2 id="ngIf">NgIf</h2> | |<p *ngIf="true"> | Expression is true and ngIf is true. | This paragraph is in the DOM. |</p> |<p *ngIf="false"> | Expression is false and ngIf is false. | This paragraph is not in the DOM. |</p> | |<p [style.display]="'block'"> | Expression sets display to "block". | This paragraph is visible. |</p> |<p [style.display]="'none'"> | Expression sets display to "none". | This paragraph is hidden but still in the DOM. |</p> | |<h4>NgIf with template</h4> |<p>&lt;template&gt; element</p> |<template [ngIf]="hero != null"> | <div>{{hero.name}}</div> |</template> | |<hr> | |<a id="ng-container"></a> |<h2 id="template">&lt;template&gt;</h2> | |<h4>*ngIf with a &lt;template&gt;</h4> | |<button (click)="hero = hero != null ? null : heroes[0]">Toggle hero</button> | |<p> | I turned the corner | <template [ngIf]="hero != null"> | and saw {{hero.name}}. I waved | </template> | and continued on my way. [template] |</p> |<!-- No ng-container yet: |<p> | I turned the corner | <ng-container *ngIf="hero != null"> | and saw {{hero.name}}. I waved | </ng-container> | and continued on my way. |</p> |--> |<p> | I turned the corner | <span *ngIf="hero != null"> | and saw {{hero.name}}. I waved | </span> | and continued on my way. |</p> | |<p><i>&lt;select&gt; with &lt;span&gt;</i></p> |<div> | Pick your favorite hero | (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>) |</div> |<select [(ngModel)]="hero"> | <span *ngFor="let h of heroes"> | <span *ngIf="showSad || h.emotion !== 'sad'"> | <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option> | </span> | </span> |</select> | |<p><i>&lt;select&gt; with &lt;template&gt;</i></p> |<div> | Pick your favorite hero 2 | (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>) |</div> |<select [(ngModel)]="hero"> | <template ngFor let-h [ngForOf]="heroes"> | <template [ngIf]="showSad || h.emotion !== 'sad'"> | <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option> | </template> | </template> |</select> | |<!-- |<p><i>&lt;select&gt; with &lt;ng-container&gt;</i></p> |<div> | Pick your favorite hero | (<label><input type="checkbox" checked (change)="showSad = !showSad">show sad</label>) |</div> |<select [(ngModel)]="hero"> | <ng-container *ngFor="let h of heroes"> | <ng-container *ngIf="showSad || h.emotion !== 'sad'"> | <option [ngValue]="h">{{h.name}} ({{h.emotion}})</option> | </ng-container> | </ng-container> |</select> |--> |<br><br> | |<hr> | |<h2 id="ngFor">NgFor</h2> | |<div class="box"> | |<p class="code">&lt;div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackByHeroId" [class.odd]="odd"&gt;</p> |<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackByHeroId" | [class.odd]="odd"> | ({{i}}) {{hero.name}} |</div> | |<p class="code">&lt;template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackByHeroId"&gt;</p> |<template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" | [ngForTrackBy]="trackByHeroId"> | <div [class.odd]="odd">({{i}}) {{hero.name}}</div> |</template> | |</div> |<hr> | |<h2 id="ngSwitch">NgSwitch</h2> | |<div>Pick your favorite hero</div> | |<material-radio-group [(ngModel)]="hero"> | <material-radio *ngFor="let h of heroes" [value]="h"> | {{h.name}} | </material-radio> | <material-radio>None of the above</material-radio> |</material-radio-group> | |<h4>NgSwitch</h4> | |<div [ngSwitch]="hero?.emotion"> | <happy-hero *ngSwitchCase="'happy'" [hero]="hero"></happy-hero> | <sad-hero *ngSwitchCase="'sad'" [hero]="hero"></sad-hero> | <confused-hero *ngSwitchCase="'confused'" [hero]="hero"></confused-hero> | <unknown-hero *ngSwitchDefault [hero]="hero"></unknown-hero> |</div> | |<h4>NgSwitch with &lt;template&gt;</h4> |<div [ngSwitch]="hero?.emotion"> | <template [ngSwitchCase]="'happy'"> | <happy-hero [hero]="hero"></happy-hero> | </template> | <template [ngSwitchCase]="'sad'"> | <sad-hero [hero]="hero"></sad-hero> | </template> | <template [ngSwitchCase]="'confused'"> | <confused-hero [hero]="hero"></confused-hero> | </template > | <template ngSwitchDefault> | <unknown-hero [hero]="hero"></unknown-hero> | </template> |</div> | |<hr> | |<h2>&lt;template&gt;</h2> |<p>Hip!</p> |<template> | <p>Hip!</p> |</template> |<p>Hooray!</p> | |<hr> | |<h2 id="myUnless">UnlessDirective</h2> |<p> | The condition is currently | <span [ngClass]="{ 'a': !condition, 'b': condition, 'unless': true }">{{condition}}</span>. | <button | (click)="condition = !condition" | [ngClass] = "{ 'a': condition, 'b': !condition }" > | Toggle condition to {{condition ? 'false' : 'true'}} | </button> |</p> |<p *myUnless="condition" class="unless a"> | (A) This paragraph is displayed because the condition is false. |</p> | |<p *myUnless="!condition" class="unless b"> | (B) Although the condition is true, | this paragraph is displayed because myUnless is set to false. |</p> | | |<h4>UnlessDirective with template</h4> | |<p *myUnless="condition">Show this sentence unless the condition is true.</p> | |<template [myUnless]="condition"> | <p class="code unless"> | (A) &lt;template [myUnless]="condition"&gt; | </p> |</template> |button { | min-width: 100px; | font-size: 100%; |} | |.box { | border: 1px solid gray; | max-width: 600px; | padding: 4px; |} |.choices { | font-style: italic; |} | |code, .code { | background-color: #eee; | color: black; | font-family: Courier, sans-serif; | font-size: 85%; |} | |div.code { | width: 400px; |} | |.heroic { | font-size: 150%; | font-weight: bold; |} | |hr { | margin: 40px 0 |} | |.odd { | background-color: palegoldenrod; |} | |td, th { | text-align: left; | vertical-align: top; |} | |p span { color: red; font-size: 70%; } | |.unless { | border: 2px solid; | padding: 6px; |} | |p.unless { | width: 500px; |} | |button.a, span.a, .unless.a { | color: red; | border-color: gold; | background-color: yellow; | font-size: 100%; |} | |button.b, span.b, .unless.b { | color: black; | border-color: green; | background-color: lightgreen; | font-size: 100%; |} class Hero { final int id; String name; /*@nullable*/ String emotion; Hero(this.id, this.name, [this.emotion]); @override String toString() => '$name'; } final List<Hero> mockHeroes = <Hero>[ Hero(1, 'Mr. Nice', 'happy'), Hero(2, 'Narco', 'sad'), Hero(3, 'Windstorm', 'confused'), Hero(4, 'Magneta') ]; import 'package:angular/angular.dart'; import 'hero.dart'; @Component( selector: 'happy-hero', template: 'Wow. You like {{hero.name}}. What a happy hero ... just like you.', ) class HappyHeroComponent { @Input() Hero hero; } @Component( selector: 'sad-hero', template: 'You like {{hero.name}}? Such a sad hero. Are you sad too?', ) class SadHeroComponent { @Input() Hero hero; } @Component( selector: 'confused-hero', template: 'Are you as confused as {{hero.name}}?', ) class ConfusedHeroComponent { @Input() Hero hero; } @Component( selector: 'unknown-hero', template: '{{message}}', ) class UnknownHeroComponent { @Input() Hero hero; String get message => hero != null && hero.name.isNotEmpty ? '${hero.name} is strange and mysterious.' : 'Are you feeling indecisive?'; } const List heroSwitchComponents = [ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ]; |import 'package:angular/angular.dart'; | |/// Add the template content to the DOM unless the condition is true. |/// |/// If the expression assigned to `myUnless` evaluates to a truthy value |/// then the templated elements are removed removed from the DOM, |/// the templated elements are (re)inserted into the DOM. |/// |/// <div *ngUnless="errorCount" class="success"> |/// Congrats! Everything is great! |/// </div> |/// |/// ### Syntax |/// |/// - `<div *myUnless="condition">...</div>` |/// - `<template [myUnless]="condition"><div>...</div></template>` |@Directive(selector: '[myUnless]') |class UnlessDirective { | bool _hasView = false; | | TemplateRef _templateRef; | ViewContainerRef _viewContainer; | | UnlessDirective(this._templateRef, this._viewContainer); | | @Input() | set myUnless(bool condition) { | if (!condition && !_hasView) { | _viewContainer.createEmbeddedView(_templateRef); | _hasView = true; | } else if (condition && _hasView) { | _viewContainer.clear(); | _hasView = false; | } | } |}

You learned