docs: rewrite built-in directives section (#27273)

PR Close #27273
This commit is contained in:
Kapunahele Wong 2018-10-09 14:32:58 -04:00 committed by Andrew Kushnir
parent 7e3a60ad31
commit 2c2135d331
20 changed files with 924 additions and 231 deletions

View File

@ -0,0 +1,78 @@
'use strict';
import { browser, element, by } from 'protractor';
describe('Built-in Directives', function () {
beforeAll(function () {
browser.get('');
});
it('should have title Built-in Directives', function () {
let title = element.all(by.css('h1')).get(0);
expect(title.getText()).toEqual('Built-in Directives');
});
it('should change first Teapot header', async () => {
let firstLabel = element.all(by.css('p')).get(0);
let firstInput = element.all(by.css('input')).get(0);
expect(firstLabel.getText()).toEqual('Current item name: Teapot');
firstInput.sendKeys('abc');
expect(firstLabel.getText()).toEqual('Current item name: Teapotabc');
});
it('should modify sentence when modified checkbox checked', function () {
let modifiedChkbxLabel = element.all(by.css('input[type="checkbox"]')).get(1);
let modifiedSentence = element.all(by.css('div')).get(1);
modifiedChkbxLabel.click();
expect(modifiedSentence.getText()).toContain('modified');
});
it('should modify sentence when normal checkbox checked', function () {
let normalChkbxLabel = element.all(by.css('input[type="checkbox"]')).get(4);
let normalSentence = element.all(by.css('div')).get(7);
normalChkbxLabel.click();
expect(normalSentence.getText()).toContain('normal weight and, extra large');
});
it('should toggle app-item-detail', function () {
let toggleButton = element.all(by.css('button')).get(3);
let toggledDiv = element.all(by.css('app-item-detail')).get(0);
toggleButton.click();
expect(toggledDiv.isDisplayed()).toBe(true);
});
it('should hide app-item-detail', function () {
let hiddenMessage = element.all(by.css('p')).get(11);
let hiddenDiv = element.all(by.css('app-item-detail')).get(2);
expect(hiddenMessage.getText()).toContain('in the DOM');
expect(hiddenDiv.isDisplayed()).toBe(true);
});
it('should have 10 lists each containing the string Teapot', function () {
let listDiv = element.all(by.cssContainingText('.box', 'Teapot'));
expect(listDiv.count()).toBe(10);
});
it('should switch case', function () {
let tvRadioButton = element.all(by.css('input[type="radio"]')).get(3);
let tvDiv = element(by.css('app-lost-item'));
let fishbowlRadioButton = element.all(by.css('input[type="radio"]')).get(4);
let fishbowlDiv = element(by.css('app-unknown-item'));
tvRadioButton.click();
expect(tvDiv.getText()).toContain('Television');
fishbowlRadioButton.click();
expect(fishbowlDiv.getText()).toContain('mysterious');
});
});

View File

@ -0,0 +1,75 @@
button {
font-size: 100%;
margin: 0 2px;
}
div[ng-reflect-ng-switch], app-unknown-item {
margin: .5rem 0;
display: block;
}
#noTrackByCnt,
#withTrackByCnt {
color: darkred;
max-width: 450px;
margin: 4px;
}
img {
height: 100px;
}
.box {
border: 1px solid black;
padding: 6px;
max-width: 450px;
}
.child-div {
margin-left: 1em;
font-weight: normal;
}
.context {
margin-left: 1em;
}
.hidden {
display: none;
}
.parent-div {
margin-top: 1em;
font-weight: bold;
}
.course {
font-weight: bold;
font-size: x-large;
}
.helpful {
color: red;
}
.saveable {
color: limegreen;
}
.study,
.modified {
font-family: "Brush Script MT";
font-size: 2rem;
}
.toe {
margin-left: 1em;
font-style: italic;
}
.to-toc {
margin-top: 10px;
display: block;
}

View File

@ -0,0 +1,253 @@
<h1>Built-in Directives</h1>
<h2>Built-in attribute directives</h2>
<h3 id="ngModel">NgModel (two-way) Binding</h3>
<fieldset><h4>NgModel examples</h4>
<p>Current item name: {{currentItem.name}}</p>
<p>
<!-- #docregion without-NgModel -->
<label for="without">without NgModel:</label>
<input [value]="currentItem.name" (input)="currentItem.name=$event.target.value" id="without">
<!-- #enddocregion without-NgModel -->
</p>
<p>
<!-- #docregion NgModel-1 -->
<label for="example-ngModel">[(ngModel)]:</label>
<input [(ngModel)]="currentItem.name" id="example-ngModel">
<!-- #enddocregion NgModel-1 -->
</p>
<p>
<label for="example-bindon">bindon-ngModel: </label>
<input bindon-ngModel="currentItem.name" id="example-bindon">
</p>
<p>
<!-- #docregion NgModelChange -->
<label for="example-change">(ngModelChange)="...name=$event":</label>
<input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change">
<!-- #enddocregion NgModelChange -->
</p>
<p>
<label for="example-uppercase">(ngModelChange)="setUppercaseName($event)"
<!-- #docregion uppercase -->
<input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">
<!-- #enddocregion uppercase -->
</label>
</p>
</fieldset>
<hr><h2 id="ngClass">NgClass Binding</h2>
<p>currentClasses is {{currentClasses | json}}</p>
<!-- #docregion NgClass-1 -->
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div>
<!-- #enddocregion NgClass-1 -->
<ul>
<li>
<label for="saveable">saveable</label>
<input type="checkbox" [(ngModel)]="canSave" id="saveable">
</li>
<li>
<label for="modified">modified:</label>
<input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li>
<li>
<label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label>
</li>
</ul>
<button (click)="setCurrentClasses()">Refresh currentClasses</button>
<div [ngClass]="currentClasses">
This div should be {{ canSave ? "": "not"}} saveable,
{{ isUnchanged ? "unchanged" : "modified" }} and
{{ isSpecial ? "": "not"}} special after clicking "Refresh".</div>
<br><br>
<!-- #docregion special-div -->
<!-- toggle the "special" class on/off with a property -->
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
<!-- #enddocregion special-div -->
<div class="helpful study course">Helpful study course</div>
<div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div>
<!-- NgStyle binding -->
<hr><h3>NgStyle Binding</h3>
<!-- #docregion without-ng-style -->
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">
This div is x-large or smaller.
</div>
<!-- #enddocregion without-ng-style -->
<h4>[ngStyle] binding to currentStyles - CSS property names</h4>
<p>currentStyles is {{currentStyles | json}}</p>
<!-- #docregion NgStyle-2 -->
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>
<!-- #enddocregion NgStyle-2 -->
<br>
<label>italic: <input type="checkbox" [(ngModel)]="canSave"></label> |
<label>normal: <input type="checkbox" [(ngModel)]="isUnchanged"></label> |
<label>xlarge: <input type="checkbox" [(ngModel)]="isSpecial"></label>
<button (click)="setCurrentStyles()">Refresh currentStyles</button>
<br><br>
<div [ngStyle]="currentStyles">
This div should be {{ canSave ? "italic": "plain"}},
{{ isUnchanged ? "normal weight" : "bold" }} and,
{{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div>
<hr>
<h2>Built-in structural directives</h2>
<h3 id="ngIf">NgIf Binding</h3>
<div>
<p>If isActive is true, app-item-detail will render: </p>
<!-- #docregion NgIf-1 -->
<app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>
<!-- #enddocregion NgIf-1 -->
<button (click)="isActiveToggle()">Toggle app-item-detail</button>
</div>
<p>If currentCustomer isn't null, say hello to Laura:</p>
<!-- #docregion NgIf-2 -->
<div *ngIf="currentCustomer">Hello, {{currentCustomer.name}}</div>
<!-- #enddocregion NgIf-2 -->
<p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p>
<!-- #docregion NgIf-2b -->
<div *ngIf="nullCustomer">Hello, <span>{{nullCustomer}}</span></div>
<!-- #enddocregion NgIf-2b -->
<button (click)="giveNullCustomerValue()">Give nullCustomer a value</button>
<h4>NgIf binding with template (no *)</h4>
<ng-template [ngIf]="currentItem">Add {{currentItem.name}} with template</ng-template>
<hr>
<h4>Show/hide vs. NgIf</h4>
<!-- #docregion NgIf-3 -->
<!-- isSpecial is true -->
<div [class.hidden]="!isSpecial">Show with class</div>
<div [class.hidden]="isSpecial">Hide with class</div>
<p>ItemDetail is in the DOM but hidden</p>
<app-item-detail [class.hidden]="isSpecial"></app-item-detail>
<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
<div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>
<!-- #enddocregion NgIf-3 -->
<hr>
<h2 id="ngFor">NgFor Binding</h2>
<div class="box">
<!-- #docregion NgFor-1, NgFor-1-2 -->
<div *ngFor="let item of items">{{item.name}}</div>
<!-- #enddocregion NgFor-1, NgFor-1-2 -->
</div>
<p>*ngFor with ItemDetailComponent element</p>
<div class="box">
<!-- #docregion NgFor-2, NgFor-1-2 -->
<app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail>
<!-- #enddocregion NgFor-2, NgFor-1-2 -->
</div>
<h4 id="ngFor-index">*ngFor with index</h4>
<p>with <i>semi-colon</i> separator</p>
<div class="box">
<!-- #docregion NgFor-3 -->
<div *ngFor="let item of items; let i=index">{{i + 1}} - {{item.name}}</div>
<!-- #enddocregion NgFor-3 -->
</div>
<p>with <i>comma</i> separator</p>
<div class="box">
<div *ngFor="let item of items, let i=index">{{i + 1}} - {{item.name}}</div>
</div>
<h4 id="ngFor-trackBy">*ngFor trackBy</h4>
<button (click)="resetList()">Reset items</button>
<button (click)="changeIds()">Change ids</button>
<button (click)="clearTrackByCounts()">Clear counts</button>
<p><i>without</i> trackBy</p>
<div class="box">
<div #noTrackBy *ngFor="let item of items">({{item.id}}) {{item.name}}</div>
<div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" >
Item DOM elements change #{{itemsNoTrackByCount}} without trackBy
</div>
</div>
<p>with trackBy</p>
<div class="box">
<div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{item.id}}) {{item.name}}</div>
<div id="withTrackByCnt" *ngIf="itemsWithTrackByCount">
Item DOM elements change #{{itemsWithTrackByCount}} with trackBy
</div>
</div>
<br><br><br>
<p>with trackBy and <i>semi-colon</i> separator</p>
<div class="box">
<!-- #docregion trackBy -->
<div *ngFor="let item of items; trackBy: trackByItems">
({{item.id}}) {{item.name}}
</div>
<!-- #enddocregion trackBy -->
</div>
<p>with trackBy and <i>comma</i> separator</p>
<div class="box">
<div *ngFor="let item of items, trackBy: trackByItems">({{item.id}}) {{item.name}}</div>
</div>
<p>with trackBy and <i>space</i> separator</p>
<div class="box">
<div *ngFor="let item of items trackBy: trackByItems">({{item.id}}) {{item.name}}</div>
</div>
<p>with <i>generic</i> trackById function</p>
<div class="box">
<div *ngFor="let item of items, trackBy: trackById">({{item.id}}) {{item.name}}</div>
</div>
<hr><h2>NgSwitch Binding</h2>
<p>Pick your favorite item</p>
<div>
<label *ngFor="let i of items">
<div><input type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{i.name}}
</div>
</label>
</div>
<!-- #docregion NgSwitch -->
<div [ngSwitch]="currentItem.feature">
<app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item>
<app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item>
<app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item>
<app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item>
<!-- #enddocregion NgSwitch -->
<!-- #docregion NgSwitch-div -->
<div *ngSwitchCase="'bright'"> Are you as bright as {{currentItem.name}}?</div>
<!-- #enddocregion NgSwitch-div -->
<!-- #docregion NgSwitch -->
<app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item>
</div>
<!-- #enddocregion NgSwitch -->

View File

@ -0,0 +1,115 @@
import { Component, OnInit } from '@angular/core';
import { Item } from './item';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
canSave = true;
isSpecial = true;
isUnchanged = true;
isActive = true;
nullCustomer = null;
currentCustomer = {
name: 'Laura'
};
item: Item; // defined to demonstrate template context precedence
items: Item[];
currentItem: Item;
// trackBy change counting
itemsNoTrackByCount = 0;
itemsWithTrackByCount = 0;
itemsWithTrackByCountReset = 0;
itemIdIncrement = 1;
ngOnInit() {
this.resetItems();
this.setCurrentClasses();
this.setCurrentStyles();
this.itemsNoTrackByCount = 0;
}
setUppercaseName(name: string) {
this.currentItem.name = name.toUpperCase();
}
// #docregion setClasses
currentClasses: {};
setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
'saveable': this.canSave,
'modified': !this.isUnchanged,
'special': this.isSpecial
};
}
// #enddocregion setClasses
// #docregion setStyles
currentStyles: {};
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}
// #enddocregion setStyles
isActiveToggle() {
this.isActive = !this.isActive;
}
giveNullCustomerValue() {
!(this.nullCustomer = null) ? (this.nullCustomer = 'Kelly') : (this.nullCustomer = null);
}
resetNullItem() {
this.nullCustomer = null;
}
resetItems() {
this.items = Item.items.map(item => item.clone());
this.currentItem = this.items[0];
this.item = this.currentItem;
}
resetList() {
this.resetItems()
this.itemsWithTrackByCountReset = 0;
this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;
}
changeIds() {
this.items.forEach(i => i.id += 1 * this.itemIdIncrement);
this.itemsWithTrackByCountReset = -1;
this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;
this.itemsWithTrackByCount = ++this.itemsWithTrackByCount;
}
clearTrackByCounts() {
this.resetItems();
this.itemsNoTrackByCount = 0;
this.itemsWithTrackByCount = 0;
this.itemIdIncrement = 1;
}
// #docregion trackByItems
trackByItems(index: number, item: Item): number { return item.id; }
// #enddocregion trackByItems
trackById(index: number, item: any): number { return item['id']; }
}

View File

@ -0,0 +1,34 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
// #docregion import-forms-module
import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular
// #enddocregion import-forms-module
import { AppComponent } from './app.component';
import { ItemDetailComponent } from './item-detail/item-detail.component';
import { ItemSwitchComponents } from './item-switch.component';
// #docregion import-forms-module
@NgModule({
// #enddocregion import-forms-module
declarations: [
AppComponent,
ItemDetailComponent,
ItemSwitchComponents
],
// #docregion import-forms-module
imports: [
BrowserModule,
FormsModule // <--- import into the NgModule
],
// #enddocregion import-forms-module
providers: [],
bootstrap: [AppComponent]
// #docregion import-forms-module
})
export class AppModule { }
// #enddocregion import-forms-module

View File

@ -0,0 +1,3 @@
<div>
<span>{{item?.name}}</span>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ItemDetailComponent } from './item-detail.component';
describe('ItemDetailComponent', () => {
let component: ItemDetailComponent;
let fixture: ComponentFixture<ItemDetailComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ItemDetailComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,17 @@
import { Component, Input } from '@angular/core';
import { Item } from '../item';
@Component({
selector: 'app-item-detail',
templateUrl: './item-detail.component.html',
styleUrls: ['./item-detail.component.css']
})
export class ItemDetailComponent {
@Input() item: Item;
constructor() { }
}

View File

@ -0,0 +1,50 @@
import { Component, Input } from '@angular/core';
import { Item } from './item';
@Component({
selector: 'app-stout-item',
template: `I'm a little {{item.name}}, short and stout!`
})
export class StoutItemComponent {
@Input() item: Item;
}
@Component({
selector: 'app-best-item',
template: `This is the brightest {{item.name}} in town.`
})
export class BestItemComponent {
@Input() item: Item;
}
@Component({
selector: 'app-device-item',
template: `Which is the slimmest {{item.name}}?`
})
export class DeviceItemComponent {
@Input() item: Item;
}
@Component({
selector: 'app-lost-item',
template: `Has anyone seen my {{item.name}}?`
})
export class LostItemComponent {
@Input() item: Item;
}
@Component({
selector: 'app-unknown-item',
template: `{{message}}`
})
export class UnknownItemComponent {
@Input() item: Item;
get message() {
return this.item && this.item.name ?
`${this.item.name} is strange and mysterious.` :
'A mystery wrapped in a fishbowl.';
}
}
export const ItemSwitchComponents =
[ StoutItemComponent, BestItemComponent, DeviceItemComponent, LostItemComponent, UnknownItemComponent ];

View File

@ -0,0 +1,30 @@
export class Item {
static nextId = 0;
static items: Item[] = [
new Item(
null,
'Teapot',
'stout'
),
new Item(1, 'Lamp', 'bright'),
new Item(2, 'Phone', 'slim' ),
new Item(3, 'Television', 'vintage' ),
new Item(4, 'Fishbowl')
];
constructor(
public id?: number,
public name?: string,
public feature?: string,
public url?: string,
public rate = 100,
) {
this.id = id ? id : Item.nextId++;
}
clone(): Item {
return Object.assign(new Item(), this);
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>BuiltInDirectives</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

View File

@ -0,0 +1,10 @@
{
"description": "Built-in Directives",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["Built-in Directives"]
}

View File

@ -1238,80 +1238,64 @@ Angular [NgModel](guide/template-syntax#ngModel).
## Built-in directives ## Built-in directives
Earlier versions of Angular included over seventy built-in directives. Angular offers two kinds of built-in directives: attribute
The community contributed many more, and countless private directives directives and structural directives. This segment reviews some of the most common built-in directives,
have been created for internal applications. classified as either [_attribute_ directives](guide/template-syntax#attribute-directives) or [_structural_ directives](guide/template-syntax#structural-directives) and has its own <live-example name="built-in-directives">built-in directives example</live-example>.
You don't need many of those directives in Angular. For more detail, including how to build your own custom directives, see [Attribute Directives](guide/attribute-directives) and [Structural Directives](guide/structural-directives).
You can often achieve the same results with the more capable and expressive Angular binding system.
Why create a directive to handle a click when you can write a simple binding such as this?
<code-example path="template-syntax/src/app/app.component.html" region="event-binding-1" header="src/app/app.component.html" linenums="false">
</code-example>
You still benefit from directives that simplify complex tasks.
Angular still ships with built-in directives; just not as many.
You'll write your own directives, just not as many.
This segment reviews some of the most frequently used built-in directives,
classified as either [_attribute_ directives](guide/template-syntax#attribute-directives) or [_structural_ directives](guide/template-syntax#structural-directives).
<hr/> <hr/>
{@a attribute-directives} {@a attribute-directives}
## Built-in _attribute_ directives ### Built-in attribute directives
Attribute directives listen to and modify the behavior of Attribute directives listen to and modify the behavior of
other HTML elements, attributes, properties, and components. other HTML elements, attributes, properties, and components.
They are usually applied to elements as if they were HTML attributes, hence the name. You usually apply them to elements as if they were HTML attributes, hence the name.
Many details are covered in the [_Attribute Directives_](guide/attribute-directives) guide.
Many NgModules such as the [`RouterModule`](guide/router "Routing and Navigation") Many NgModules such as the [`RouterModule`](guide/router "Routing and Navigation")
and the [`FormsModule`](guide/forms "Forms") define their own attribute directives. and the [`FormsModule`](guide/forms "Forms") define their own attribute directives.
This section is an introduction to the most commonly used attribute directives: The most common attribute directives are as follows:
* [`NgClass`](guide/template-syntax#ngClass) - add and remove a set of CSS classes
* [`NgStyle`](guide/template-syntax#ngStyle) - add and remove a set of HTML styles
* [`NgModel`](guide/template-syntax#ngModel) - two-way data binding to an HTML form element
* [`NgClass`](guide/template-syntax#ngClass)&mdash;adds and removes a set of CSS classes.
* [`NgStyle`](guide/template-syntax#ngStyle)&mdash;adds and removes a set of HTML styles.
* [`NgModel`](guide/template-syntax#ngModel)&mdash;adds two-way data binding to an HTML form element.
<hr/> <hr/>
{@a ngClass} {@a ngClass}
### NgClass ### `NgClass`
You typically control how elements appear Add or remove several CSS classes simultaneously with `ngClass`.
by adding and removing CSS classes dynamically.
You can bind to the `ngClass` to add or remove several classes simultaneously.
A [class binding](guide/template-syntax#class-binding) is a good way to add or remove a *single* class. <code-example path="built-in-directives/src/app/app.component.html" region="special-div" header="src/app/app.component.html" linenums="false">
<code-example path="template-syntax/src/app/app.component.html" region="class-binding-3a" header="src/app/app.component.html" linenums="false">
</code-example>
To add or remove *many* CSS classes at the same time, the `NgClass` directive may be the better choice.
Try binding `ngClass` to a key:value control object.
Each key of the object is a CSS class name; its value is `true` if the class should be added,
`false` if it should be removed.
Consider a `setCurrentClasses` component method that sets a component property,
`currentClasses` with an object that adds or removes three classes based on the
`true`/`false` state of three other component properties:
<code-example path="template-syntax/src/app/app.component.ts" region="setClasses" header="src/app/app.component.ts" linenums="false">
</code-example>
Adding an `ngClass` property binding to `currentClasses` sets the element's classes accordingly:
<code-example path="template-syntax/src/app/app.component.html" region="NgClass-1" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
<div class="alert is-helpful"> <div class="alert is-helpful">
It's up to you to call `setCurrentClasses()`, both initially and when the dependent properties change. To add or remove a *single* class, use [class binding](guide/template-syntax#class-binding) rather than `NgClass`.
</div>
Consider a `setCurrentClasses()` component method that sets a component property,
`currentClasses`, with an object that adds or removes three classes based on the
`true`/`false` state of three other component properties. Each key of the object is a CSS class name; its value is `true` if the class should be added,
`false` if it should be removed.
<code-example path="built-in-directives/src/app/app.component.ts" region="setClasses" header="src/app/app.component.ts" linenums="false">
</code-example>
Adding an `ngClass` property binding to `currentClasses` sets the element's classes accordingly:
<code-example path="built-in-directives/src/app/app.component.html" region="NgClass-1" header="src/app/app.component.html" linenums="false">
</code-example>
<div class="alert is-helpful">
Remember that in this situation you'd call `setCurrentClasses()`,
both initially and when the dependent properties change.
</div> </div>
@ -1319,35 +1303,34 @@ It's up to you to call `setCurrentClasses()`, both initially and when the depend
{@a ngStyle} {@a ngStyle}
### NgStyle ### `NgStyle`
You can set inline styles dynamically, based on the state of the component. Use `NgStyle` to set many inline styles simultaneously and dynamically, based on the state of the component.
With `NgStyle` you can set many inline styles simultaneously.
A [style binding](guide/template-syntax#style-binding) is an easy way to set a *single* style value. #### Without `NgStyle`
<code-example path="template-syntax/src/app/app.component.html" region="NgStyle-1" header="src/app/app.component.html" linenums="false"> For context, consider setting a *single* style value with [style binding](guide/template-syntax#style-binding), without `NgStyle`.
<code-example path="built-in-directives/src/app/app.component.html" region="without-ng-style" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
To set *many* inline styles at the same time, the `NgStyle` directive may be the better choice. However, to set *many* inline styles at the same time, use the `NgStyle` directive.
Try binding `ngStyle` to a key:value control object. The following is a `setCurrentStyles()` method that sets a component
Each key of the object is a style name; its value is whatever is appropriate for that style. property, `currentStyles`, with an object that defines three styles,
based on the state of three other component properties:
Consider a `setCurrentStyles` component method that sets a component property, `currentStyles` <code-example path="built-in-directives/src/app/app.component.ts" region="setStyles" header="src/app/app.component.ts" linenums="false">
with an object that defines three styles, based on the state of three other component properties:
<code-example path="template-syntax/src/app/app.component.ts" region="setStyles" header="src/app/app.component.ts" linenums="false">
</code-example> </code-example>
Adding an `ngStyle` property binding to `currentStyles` sets the element's styles accordingly: Adding an `ngStyle` property binding to `currentStyles` sets the element's styles accordingly:
<code-example path="template-syntax/src/app/app.component.html" region="NgStyle-2" header="src/app/app.component.html" linenums="false"> <code-example path="built-in-directives/src/app/app.component.html" region="NgStyle-2" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
<div class="alert is-helpful"> <div class="alert is-helpful">
It's up to you to call `setCurrentStyles()`, both initially and when the dependent properties change. Remember to call `setCurrentStyles()`, both initially and when the dependent properties change.
</div> </div>
@ -1356,92 +1339,78 @@ It's up to you to call `setCurrentStyles()`, both initially and when the depende
{@a ngModel} {@a ngModel}
### NgModel - Two-way binding to form elements with <span class="syntax">[(ngModel)]</span> ### `[(ngModel)]`: Two-way binding
When developing data entry forms, you often both display a data property and The `NgModel` directive allows you to display a data property and
update that property when the user makes changes. update that property when the user makes changes. Here's an example:
Two-way data binding with the `NgModel` directive makes that easy. Here's an example: <code-example path="built-in-directives/src/app/app.component.html" linenums="false" header="src/app/app.component.html (NgModel example)" region="NgModel-1">
<code-example path="template-syntax/src/app/app.component.html" linenums="false" header="src/app/app.component.html (NgModel-1)" region="NgModel-1">
</code-example> </code-example>
#### _FormsModule_ is required to use _ngModel_
#### Import `FormsModule` to use `ngModel`
Before using the `ngModel` directive in a two-way data binding, Before using the `ngModel` directive in a two-way data binding,
you must import the `FormsModule` and add it to the NgModule's `imports` list. you must import the `FormsModule` and add it to the NgModule's `imports` list.
Learn more about the `FormsModule` and `ngModel` in the Learn more about the `FormsModule` and `ngModel` in [Forms](guide/forms#ngModel).
[Forms](guide/forms#ngModel) guide.
Here's how to import the `FormsModule` to make `[(ngModel)]` available. Remember to import the `FormsModule` to make `[(ngModel)]` available as follows:
<code-example path="template-syntax/src/app/app.module.1.ts" linenums="false" header="src/app/app.module.ts (FormsModule import)"> <code-example path="built-in-directives/src/app/app.module.ts" linenums="false" header="src/app/app.module.ts (FormsModule import)" region="import-forms-module">
</code-example> </code-example>
#### Inside <span class="syntax">[(ngModel)]</span>
Looking back at the `name` binding, note that You could achieve the same result with separate bindings to
you could have achieved the same result with separate bindings to the `<input>` element's `value` property and `input` event:
the `<input>` element's `value` property and `input` event.
<code-example path="template-syntax/src/app/app.component.html" region="without-NgModel" header="src/app/app.component.html" linenums="false"> <code-example path="built-in-directives/src/app/app.component.html" region="without-NgModel" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
That's cumbersome. Who can remember which element property to set and which element event emits user changes? To streamline the syntax, the `ngModel` directive hides the details behind its own `ngModel` input and `ngModelChange` output properties:
How do you extract the currently displayed text from the input box so you can update the data property?
Who wants to look that up each time?
That `ngModel` directive hides these onerous details behind its own `ngModel` input and `ngModelChange` output properties. <code-example path="built-in-directives/src/app/app.component.html" region="NgModelChange" header="src/app/app.component.html" linenums="false">
<code-example path="template-syntax/src/app/app.component.html" region="NgModel-3" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
<div class="alert is-helpful">
The `ngModel` data property sets the element's value property and the `ngModelChange` event property The `ngModel` data property sets the element's value property and the `ngModelChange` event property
listens for changes to the element's value. listens for changes to the element's value.
#### `NgModel` and value accessors
The details are specific to each kind of element and therefore the `NgModel` directive only works for an element The details are specific to each kind of element and therefore the `NgModel` directive only works for an element
supported by a [ControlValueAccessor](api/forms/ControlValueAccessor) supported by a [ControlValueAccessor](api/forms/ControlValueAccessor)
that adapts an element to this protocol. that adapts an element to this protocol.
The `<input>` box is one of those elements.
Angular provides *value accessors* for all of the basic HTML form elements and the Angular provides *value accessors* for all of the basic HTML form elements and the
[_Forms_](guide/forms) guide shows how to bind to them. [Forms](guide/forms) guide shows how to bind to them.
You can't apply `[(ngModel)]` to a non-form native element or a third-party custom component You can't apply `[(ngModel)]` to a non-form native element or a
until you write a suitable *value accessor*, third-party custom component until you write a suitable value accessor. For more information, see
a technique that is beyond the scope of this guide. the API documentation on [DefaultValueAccessor](https://angular.io/api/forms/DefaultValueAccessor).
You don't need a _value accessor_ for an Angular component that you write because you You don't need a value accessor for an Angular component that
can name the value and event properties you write because you can name the value and event properties
to suit Angular's basic [two-way binding syntax](guide/template-syntax#two-way) and skip `NgModel` altogether. to suit Angular's basic [two-way binding syntax](guide/template-syntax#two-way)
The [`sizer` shown above](guide/template-syntax#two-way) is an example of this technique. and skip `NgModel` altogether.
The `sizer` in the
[Two-way Binding](guide/template-syntax#two-way) section is an example of this technique.
</div> Separate `ngModel` bindings are an improvement over binding to the
element's native properties, but you can streamline the binding with a
single declaration using the `[(ngModel)]` syntax:
Separate `ngModel` bindings is an improvement over binding to the element's native properties. You can do better. <code-example path="built-in-directives/src/app/app.component.html" region="NgModel-1" header="src/app/app.component.html" linenums="false">
You shouldn't have to mention the data property twice. Angular should be able to capture
the component's data property and set it
with a single declaration, which it can with the `[(ngModel)]` syntax:
<code-example path="template-syntax/src/app/app.component.html" region="NgModel-1" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
Is `[(ngModel)]` all you need? Is there ever a reason to fall back to its expanded form? This `[(ngModel)]` syntax can only _set_ a data-bound property.
If you need to do something more, you can write the expanded form;
for example, the following changes the `<input>` value to uppercase:
The `[(ngModel)]` syntax can only _set_ a data-bound property. <code-example path="built-in-directives/src/app/app.component.html" region="uppercase" header="src/app/app.component.html" linenums="false">
If you need to do something more or something different, you can write the expanded form.
The following contrived example forces the input value to uppercase:
<code-example path="template-syntax/src/app/app.component.html" region="NgModel-4" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
Here are all variations in action, including the uppercase version: Here are all variations in action, including the uppercase version:
<figure> <figure>
<img src='generated/images/guide/template-syntax/ng-model-anim.gif' alt="NgModel variations"> <img src='generated/images/guide/built-in-directives/ng-model-anim.gif' alt="NgModel variations">
</figure> </figure>
<hr/> <hr/>
@ -1451,25 +1420,29 @@ Here are all variations in action, including the uppercase version:
## Built-in _structural_ directives ## Built-in _structural_ directives
Structural directives are responsible for HTML layout. Structural directives are responsible for HTML layout.
They shape or reshape the DOM's _structure_, typically by adding, removing, and manipulating They shape or reshape the DOM's structure, typically by adding, removing, and manipulating
the host elements to which they are attached. the host elements to which they are attached.
This section is an introduction to the common built-in structural directives:
* [`NgIf`](guide/template-syntax#ngIf)&mdash;conditionally creates or destroys subviews from the template.
* [`NgFor`](guide/template-syntax#ngFor)&mdash;repeat a node for each item in a list.
* [`NgSwitch`](guide/template-syntax#ngSwitch)&mdash;a set of directives that switch among alternative views.
<div class="alert is-helpful">
The deep details of structural directives are covered in the The deep details of structural directives are covered in the
[_Structural Directives_](guide/structural-directives) guide [Structural Directives](guide/structural-directives) guide,
where you'll learn: which explains the following:
* why you * Why you
[_prefix the directive name with an asterisk_ (\*)](guide/structural-directives#asterisk "The * in *ngIf"). [prefix the directive name with an asterisk (\*)](guide/structural-directives#the-asterisk--prefix).
* to use [`<ng-container>`](guide/structural-directives#ngcontainer "<ng-container>") * Using [`<ng-container>`](guide/structural-directives#ngcontainer "<ng-container>")
to group elements when there is no suitable host element for the directive. to group elements when there is no suitable host element for the directive.
* how to write your own structural directive. * How to write your own structural directive.
* that you can only apply [one structural directive](guide/structural-directives#one-per-element "one per host element") to an element. * That you can only apply [one structural directive](guide/structural-directives#one-per-element "one per host element") to an element.
_This_ section is an introduction to the common structural directives: </div>
* [`NgIf`](guide/template-syntax#ngIf) - conditionally add or remove an element from the DOM
* [`NgSwitch`](guide/template-syntax#ngSwitch) - a set of directives that switch among alternative views
* [NgForOf](guide/template-syntax#ngFor) - repeat a template for each item in a list
<hr/> <hr/>
@ -1478,85 +1451,93 @@ _This_ section is an introduction to the common structural directives:
### NgIf ### NgIf
You can add or remove an element from the DOM by applying an `NgIf` directive to You can add or remove an element from the DOM by applying an `NgIf` directive to
that element (called the _host element_). a host element.
Bind the directive to a condition expression like `isActive` in this example. Bind the directive to a condition expression like `isActive` in this example.
<code-example path="template-syntax/src/app/app.component.html" region="NgIf-1" header="src/app/app.component.html" linenums="false"> <code-example path="built-in-directives/src/app/app.component.html" region="NgIf-1" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
<div class="alert is-critical"> <div class="alert is-helpful">
Don't forget the asterisk (`*`) in front of `ngIf`. Don't forget the asterisk (`*`) in front of `ngIf`. For more information
on the asterisk, see the [asterisk (*) prefix](guide/structural-directives#the-asterisk--prefix) section of
[Structural Directives](guide/structural-directives).
</div> </div>
When the `isActive` expression returns a truthy value, `NgIf` adds the `HeroDetailComponent` to the DOM. When the `isActive` expression returns a truthy value, `NgIf` adds the
When the expression is falsy, `NgIf` removes the `HeroDetailComponent` `ItemDetailComponent` to the DOM.
When the expression is falsy, `NgIf` removes the `ItemDetailComponent`
from the DOM, destroying that component and all of its sub-components. from the DOM, destroying that component and all of its sub-components.
#### Show/hide is not the same thing
You can control the visibility of an element with a #### Show/hide vs. `NgIf`
[class](guide/template-syntax#class-binding) or [style](guide/template-syntax#style-binding) binding:
<code-example path="template-syntax/src/app/app.component.html" region="NgIf-3" header="src/app/app.component.html" linenums="false"> Hiding an element is different from removing it with `NgIf`.
For comparison, the following example shows how to control
the visibility of an element with a
[class](guide/template-syntax#class-binding) or [style](guide/template-syntax#style-binding) binding.
<code-example path="built-in-directives/src/app/app.component.html" region="NgIf-3" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
Hiding an element is quite different from removing an element with `NgIf`. When you hide an element, that element and all of its descendants remain in the DOM.
When you hide an element, that element and all of its descendents remain in the DOM.
All components for those elements stay in memory and All components for those elements stay in memory and
Angular may continue to check for changes. Angular may continue to check for changes.
You could be holding onto considerable computing resources and degrading performance, You could be holding onto considerable computing resources and degrading performance
for something the user can't see. unnecessarily.
When `NgIf` is `false`, Angular removes the element and its descendents from the DOM. `NgIf` works differently. When `NgIf` is `false`, Angular removes the element and its descendants from the DOM.
It destroys their components, potentially freeing up substantial resources, It destroys their components, freeing up resources, which
resulting in a more responsive user experience. results in a better user experience.
The show/hide technique is fine for a few elements with few children. If you are hiding large component trees, consider `NgIf` as a more
You should be wary when hiding large component trees; `NgIf` may be the safer choice. efficient alternative to showing/hiding.
<div class="alert is-helpful">
**Note:** For more information on `NgIf` and `ngIfElse`, see the [API documentation about NgIf](api/common/NgIf).
</div>
#### Guard against null #### Guard against null
The `ngIf` directive is often used to guard against null. Another advantage of `ngIf` is that you can use it to guard against null. Show/hide
Show/hide is useless as a guard. is best suited for very simple use cases, so when you need a guard, opt instead for `ngIf`. Angular will throw an error if a nested expression tries to access a property of `null`.
Angular will throw an error if a nested expression tries to access a property of `null`.
Here we see `NgIf` guarding two `<div>`s. The following shows `NgIf` guarding two `<div>`s.
The `currentHero` name will appear only when there is a `currentHero`. The `currentCustomer` name appears only when there is a `currentCustomer`.
The `nullHero` will never be displayed. The `nullCustomer` will not be displayed as long as it is `null`.
<code-example path="template-syntax/src/app/app.component.html" region="NgIf-2" header="src/app/app.component.html" linenums="false"> <code-example path="built-in-directives/src/app/app.component.html" region="NgIf-2" header="src/app/app.component.html" linenums="false">
</code-example>
<code-example path="built-in-directives/src/app/app.component.html" region="NgIf-2b" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
<div class="alert is-helpful"> <div class="alert is-helpful">
See also the See also the
[_safe navigation operator_](guide/template-syntax#safe-navigation-operator "Safe navigation operator (?.)") [safe navigation operator](guide/template-syntax#safe-navigation-operator "Safe navigation operator (?.)") below.
described below.
</div> </div>
<hr/> <hr/>
{@a ngFor} {@a ngFor}
### `NgFor`
### NgForOf `NgFor` is a repeater directive&mdash;a way to present a list of items.
You define a block of HTML that defines how a single item should be displayed
and then you tell Angular to use that block as a template for rendering each item in the list.
`NgForOf` is a _repeater_ directive &mdash; a way to present a list of items. Here is an example of `NgFor` applied to a simple `<div>`:
You define a block of HTML that defines how a single item should be displayed.
You tell Angular to use that block as a template for rendering each item in the list.
Here is an example of `NgForOf` applied to a simple `<div>`: <code-example path="built-in-directives/src/app/app.component.html" region="NgFor-1" header="src/app/app.component.html" linenums="false">
<code-example path="template-syntax/src/app/app.component.html" region="NgFor-1" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
You can also apply an `NgForOf` to a component element, as in this example: You can also apply an `NgFor` to a component element, as in this example:
<code-example path="template-syntax/src/app/app.component.html" region="NgFor-2" header="src/app/app.component.html" linenums="false"> <code-example path="built-in-directives/src/app/app.component.html" region="NgFor-2" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
<div class="alert is-critical"> <div class="alert is-critical">
@ -1569,50 +1550,53 @@ The text assigned to `*ngFor` is the instruction that guides the repeater proces
{@a microsyntax} {@a microsyntax}
#### *ngFor microsyntax #### `*ngFor` microsyntax
The string assigned to `*ngFor` is not a [template expression](guide/template-syntax#template-expressions). The string assigned to `*ngFor` is not a [template expression](guide/template-syntax#template-expressions). Rather,
It's a *microsyntax* &mdash; a little language of its own that Angular interprets. it's a *microsyntax*&mdash;a little language of its own that Angular interprets.
The string `"let hero of heroes"` means: The string `"let item of items"` means:
> *Take each hero in the `heroes` array, store it in the local `hero` looping variable, and > *Take each item in the `items` array, store it in the local `item` looping variable, and
make it available to the templated HTML for each iteration.* make it available to the templated HTML for each iteration.*
Angular translates this instruction into a `<ng-template>` around the host element, Angular translates this instruction into an `<ng-template>` around the host element,
then uses this template repeatedly to create a new set of elements and bindings for each `hero` then uses this template repeatedly to create a new set of elements and bindings for each `item`
in the list. in the list.
Learn about the _microsyntax_ in the [_Structural Directives_](guide/structural-directives#microsyntax) guide. For more information about microsyntax, see the [Structural Directives](guide/structural-directives#microsyntax) guide.
{@a template-input-variable} {@a template-input-variable}
{@a template-input-variables} {@a template-input-variables}
### Template input variables
The `let` keyword before `hero` creates a _template input variable_ called `hero`. #### Template input variables
The `NgForOf` directive iterates over the `heroes` array returned by the parent component's `heroes` property
and sets `hero` to the current item from the array during each iteration.
You reference the `hero` input variable within the `NgForOf` host element The `let` keyword before `item` creates a template input variable called `item`.
(and within its descendants) to access the hero's properties. The `ngFor` directive iterates over the `items` array returned by the parent component's `items` property
Here it is referenced first in an interpolation and sets `item` to the current item from the array during each iteration.
and then passed in a binding to the `hero` property of the `<hero-detail>` component.
<code-example path="template-syntax/src/app/app.component.html" region="NgFor-1-2" header="src/app/app.component.html" linenums="false"> Reference `item` within the `ngFor` host element
as well as within its descendants to access the item's properties.
The following example references `item` first in an interpolation
and then passes in a binding to the `item` property of the `<app-item-detail>` component.
<code-example path="built-in-directives/src/app/app.component.html" region="NgFor-1-2" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
Learn more about _template input variables_ in the For more information about template input variables, see
[_Structural Directives_](guide/structural-directives#template-input-variable) guide. [Structural Directives](guide/structural-directives#template-input-variable).
#### *ngFor with _index_ #### `*ngFor` with `index`
The `index` property of the `NgForOf` directive context returns the zero-based index of the item in each iteration. The `index` property of the `NgFor` directive context
returns the zero-based index of the item in each iteration.
You can capture the `index` in a template input variable and use it in the template. You can capture the `index` in a template input variable and use it in the template.
The next example captures the `index` in a variable named `i` and displays it with the hero name like this. The next example captures the `index` in a variable named `i` and displays it with the item name.
<code-example path="template-syntax/src/app/app.component.html" region="NgFor-3" header="src/app/app.component.html" linenums="false"> <code-example path="built-in-directives/src/app/app.component.html" region="NgFor-3" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
<div class="alert is-helpful"> <div class="alert is-helpful">
@ -1623,87 +1607,79 @@ and `odd` in the [NgForOf API reference](api/common/NgForOf).
</div> </div>
{@a trackBy} {@a trackBy}
#### *ngFor with `trackBy`
#### *ngFor with _trackBy_ If you use `NgFor` with large lists, a small change to one item, such as removing or adding an item, can trigger a cascade of DOM manipulations. For example, re-querying the server could reset a list with all new item objects, even when those items were previously displayed. In this case, Angular sees only a fresh list of new object references and has no choice but to replace the old DOM elements with all new DOM elements.
The `NgForOf` directive may perform poorly, especially with large lists. You can make this more efficient with `trackBy`.
A small change to one item, an item removed, or an item added can trigger a cascade of DOM manipulations. Add a method to the component that returns the value `NgFor` should track.
In this case, that value is the hero's `id`. If the `id` has already been rendered,
Angular keeps track of it and doesn't re-query the server for the same `id`.
For example, re-querying the server could reset the list with all new hero objects. <code-example path="built-in-directives/src/app/app.component.ts" region="trackByItems" header="src/app/app.component.ts" linenums="false">
Most, if not all, are previously displayed heroes.
*You* know this because the `id` of each hero hasn't changed.
But Angular sees only a fresh list of new object references.
It has no choice but to tear down the old DOM elements and insert all new DOM elements.
Angular can avoid this churn with `trackBy`.
Add a method to the component that returns the value `NgForOf` _should_ track.
In this case, that value is the hero's `id`.
<code-example path="template-syntax/src/app/app.component.ts" region="trackByHeroes" header="src/app/app.component.ts" linenums="false">
</code-example> </code-example>
In the microsyntax expression, set `trackBy` to this method. In the microsyntax expression, set `trackBy` to the `trackByItems()` method.
<code-example path="template-syntax/src/app/app.component.html" region="trackBy" header="src/app/app.component.html" linenums="false"> <code-example path="built-in-directives/src/app/app.component.html" region="trackBy" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
Here is an illustration of the _trackBy_ effect. Here is an illustration of the `trackBy` effect.
"Reset heroes" creates new heroes with the same `hero.id`s. "Reset items" creates new items with the same `item.id`s.
"Change ids" creates new heroes with new `hero.id`s. "Change ids" creates new items with new `item.id`s.
* With no `trackBy`, both buttons trigger complete DOM element replacement. * With no `trackBy`, both buttons trigger complete DOM element replacement.
* With `trackBy`, only changing the `id` triggers element replacement. * With `trackBy`, only changing the `id` triggers element replacement.
<figure> <figure>
<img src="generated/images/guide/template-syntax/ng-for-track-by-anim.gif" alt="trackBy"> <img src="generated/images/guide/built-in-directives/ngfor-trackby.gif" alt="Animation of trackBy">
</figure> </figure>
<hr/> <hr/>
{@a ngSwitch} {@a ngSwitch}
## The `NgSwitch` directives
### The _NgSwitch_ directives NgSwitch is like the JavaScript `switch` statement.
It displays one element from among several possible elements, based on a switch condition.
Angular puts only the selected element into the DOM.
<!-- API Flagged -->
`NgSwitch` is actually a set of three, cooperating directives:
`NgSwitch`, `NgSwitchCase`, and `NgSwitchDefault` as in the following example.
*NgSwitch* is like the JavaScript `switch` statement. <code-example path="built-in-directives/src/app/app.component.html" region="NgSwitch" header="src/app/app.component.html" linenums="false">
It can display _one_ element from among several possible elements, based on a _switch condition_.
Angular puts only the *selected* element into the DOM.
*NgSwitch* is actually a set of three, cooperating directives:
`NgSwitch`, `NgSwitchCase`, and `NgSwitchDefault` as seen in this example.
<code-example path="template-syntax/src/app/app.component.html" region="NgSwitch" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
<figure> <figure>
<img src="generated/images/guide/template-syntax/switch-anim.gif" alt="trackBy"> <img src="generated/images/guide/built-in-directives/ngswitch.gif" alt="Animation of NgSwitch">
</figure> </figure>
`NgSwitch` is the controller directive. Bind it to an expression that returns the *switch value*. `NgSwitch` is the controller directive. Bind it to an expression that returns
The `emotion` value in this example is a string, but the switch value can be of any type. the *switch value*, such as `feature`. Though the `feature` value in this
example is a string, the switch value can be of any type.
**Bind to `[ngSwitch]`**. You'll get an error if you try to set `*ngSwitch` because **Bind to `[ngSwitch]`**. You'll get an error if you try to set `*ngSwitch` because
`NgSwitch` is an *attribute* directive, not a *structural* directive. `NgSwitch` is an *attribute* directive, not a *structural* directive.
It changes the behavior of its companion directives. Rather than touching the DOM directly, it changes the behavior of its companion directives.
It doesn't touch the DOM directly.
**Bind to `*ngSwitchCase` and `*ngSwitchDefault`**. **Bind to `*ngSwitchCase` and `*ngSwitchDefault`**.
The `NgSwitchCase` and `NgSwitchDefault` directives are _structural_ directives The `NgSwitchCase` and `NgSwitchDefault` directives are _structural_ directives
because they add or remove elements from the DOM. because they add or remove elements from the DOM.
* `NgSwitchCase` adds its element to the DOM when its bound value equals the switch value. * `NgSwitchCase` adds its element to the DOM when its bound value equals the switch value and removes
its bound value when it doesn't equal the switch value.
* `NgSwitchDefault` adds its element to the DOM when there is no selected `NgSwitchCase`. * `NgSwitchDefault` adds its element to the DOM when there is no selected `NgSwitchCase`.
The switch directives are particularly useful for adding and removing *component elements*. The switch directives are particularly useful for adding and removing *component elements*.
This example switches among four "emotional hero" components defined in the `hero-switch.components.ts` file. This example switches among four `item` components defined in the `item-switch.components.ts` file.
Each component has a `hero` [input property](guide/template-syntax#inputs-outputs "Input property") Each component has an `item` [input property](guide/template-syntax#inputs-outputs "Input property")
which is bound to the `currentHero` of the parent component. which is bound to the `currentItem` of the parent component.
Switch directives work as well with native elements and web components too. Switch directives work as well with native elements and web components too.
For example, you could replace the `<confused-hero>` switch case with the following. For example, you could replace the `<app-best-item>` switch case with the following.
<code-example path="template-syntax/src/app/app.component.html" region="NgSwitch-div" header="src/app/app.component.html" linenums="false"> <code-example path="built-in-directives/src/app/app.component.html" region="NgSwitch-div" header="src/app/app.component.html" linenums="false">
</code-example> </code-example>
<hr/> <hr/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB