docs(template-syntax/structural-directives): refresh both guides for style, accuracy, understanding (#3110)

Move details of structural directives from template-syntax to structural-directives guide
Add <ng-container> to structural-directives
Fix samples in both guides
Touch up glossary
Better conformance to google doc guidelines: we->you
closes #2303, #2885
This commit is contained in:
Ward Bell 2017-02-06 19:06:13 -08:00 committed by GitHub
parent 390e28080c
commit f24b830c8a
60 changed files with 2820 additions and 1378 deletions

View File

@ -0,0 +1,11 @@
{
"description": "<ng-container>",
"basePath": "src/",
"files": [
"!**/*.d.ts",
"!**/*.js"
],
"tags": [
"ngcontainer", "structural", "directives"
]
}

View File

@ -0,0 +1,34 @@
/* #docregion */
button {
min-width: 100px;
font-size: 100%;
}
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
}
td, th {
text-align: left;
vertical-align: top;
}
/* #docregion p-span */
p span { color: red; font-size: 70%; }
/* #enddocregion p-span */

View File

@ -0,0 +1,279 @@
<!-- #docplaster -->
<!-- #docregion -->
<h1>&lt;ng-container&gt;</h1>
<!-- #docregion ngif -->
<div *ngIf="hero">{{hero.name}}</div>
<!-- #enddocregion ngif -->
<hr>
<h3>&lt;ng-container&gt; and CSS</h3>
<p>Examples demonstrating issues with rigid CSS styles.</p>
<button (click)="hero = hero ? null : heroes[0]">Toggle hero</button>
<h4>#1 &lt;ng-container&gt; and &lt;p&gt;</h4>
<!-- #docregion ngif-ngcontainer -->
<p>
I turned the corner
<ng-container *ngIf="hero">
and saw {{hero.name}}. I waved
</ng-container>
and continued on my way.
</p>
<!-- #enddocregion ngif-ngcontainer -->
<!-- #docregion ngif-span -->
<p>
I turned the corner
<span *ngIf="hero">
and saw {{hero.name}}. I waved
</span>
and continued on my way.
</p>
<!-- #enddocregion ngif-span -->
<h4>#2 &lt;ng-container&gt; and &lt;p&gt;</h4>
<div *ngIf="hero">
<!-- #docregion ngif-ngcontainer-2 -->
<p>
{{hero.name}} is
<ng-container *ngFor="let trait of heroTraits; let first=first; let last=last">
<ng-container *ngIf="!first">,</ng-container>
<ng-container *ngIf="last">and</ng-container>
{{trait}}
</ng-container>.
</p>
<!-- #enddocregion ngif-ngcontainer-2 -->
<!-- #docregion ngif-span-2 -->
<p>
{{hero.name}} is
<span *ngFor="let trait of heroTraits; let first=first; let last=last">
<span *ngIf="!first">,</span>
<span *ngIf="last">and</span>
{{trait}}
</span>.
</p>
<!-- #enddocregion ngif-span-2 -->
<br>
<h4>#3 &lt;ng-container&gt; and &lt;p&gt;</h4>
<!-- #docregion ngif-span-3 -->
<p>
<label><input type="checkbox" [checked]="showId" (change)="showId=!showId">Show ID</label>
</p>
<!-- #enddocregion ngif-span-3 -->
<div>
The <code>hero.id</code> in the &lt;span&gt;
is caught by the <span>p-span</span> CSS:
<!-- #docregion ngif-span-3 -->
<p>
<span *ngIf="showId">
Id: ({{hero.id}})
</span>
Name: {{hero.name}}
</p>
<!-- #enddocregion ngif-span-3 -->
</div>
<div>
The <code>hero.id</code> in the &lt;ng-container&gt;
is unaffected by the <span>p-span</span> CSS:
<p>
<ng-container *ngIf="showId">
Id: ({{hero.id}})
</ng-container>
Name: {{hero.name}}
</p>
</div>
<div>
The <code>hero.id</code> in the &lt;template *ngIf&gt; disappears:
<p>
<template *ngIf="showId">
Id: ({{hero.id}})
</template>
Name: {{hero.name}}
</p>
</div>
<div>
The <code>hero.id</code> in the &lt;template [ngIf]&gt;
is unaffected by the <span>p-span</span> CSS:
<p>
<template [ngIf]="showId">
Id: ({{hero.id}})
</template>
Name: {{hero.name}}
</p>
</div>
</div>
<hr>
<h3>&lt;ng-container&gt; and layout-sensitive elements</h3>
<p>
Examples demonstrating issues with layout-sensitive elements
such as &lt;select&gt; and &lt;table&gt;.
</p>
<h4>#1 &lt;ng-container&gt; and &lt;options&gt;</h4>
<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>
<!-- #docregion select-span -->
<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>
<!-- #enddocregion select-span -->
<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>
<!-- #docregion select-ngcontainer -->
<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>
<!-- #enddocregion select-ngcontainer -->
<br><br><br><br>
<h4>#2 &lt;ng-container&gt; and &lt;options&gt;</h4>
<p>
<label (change)="traitPicker.value=showDefaultTraits ? 'loyal' : heroTraits[0]">
<input type="checkbox"
[checked]="showDefaultTraits"
(change)="showDefaultTraits=!showDefaultTraits">Show default traits
</label>
</p>
<p>Options with &lt;ng-container&gt;</p>
<select #traitPicker>
<!-- #docregion select-ngcontainer-2 -->
<ng-container *ngIf="showDefaultTraits">
<!-- Default traits -->
<option value="loyal">LOYAL</option>
<option value="clean">CLEAN</option>
<option value="reverent">REVERENT</option>
</ng-container>
<option *ngFor="let trait of heroTraits" [value]="trait">
{{ trait | uppercase }}
</option>
<!-- #enddocregion select-ngcontainer-2 -->
</select>
<p>Options with &lt;span&gt;</p>
<!-- #docregion select-span-2 -->
<select>
<!-- Default traits are always excluded because intermediate <span> is illegal -->
<span *ngIf="showDefaultTraits">
<option value="loyal">LOYAL</option>
<option value="clean">CLEAN</option>
<option value="reverent">REVERENT</option>
</span>
<option *ngFor="let trait of heroTraits" [value]="trait">
{{ trait | uppercase }}
</option>
</select>
<!-- #enddocregion select-span-2 -->
<br>
<h4>&lt;ng-container&gt; and &lt;table&gt;</h4>
<p>
<label><input type="checkbox" checked (change)="attrDirs=!attrDirs">Attribute directives</label>
<label><input type="checkbox" checked (change)="strucDirs=!strucDirs">Structural directives</label>
<label><input type="checkbox" (change)="divNgIf=!divNgIf">div with *ngIf</label>
</p>
<table>
<tr>
<th width="20%">Directive</th>
<th width="10%">Type</th>
<th>Description</th>
</tr>
<tr *ngIf="attrDirs">
<td>NgClass</td><td>A</td><td>Add or remove multiple CSS classes.</td>
</tr>
<ng-container *ngIf="divNgIf">
<!-- #docregion ngif-div -->
<div *ngIf="strucDirs">
<tr>
<td>xxx</td><td>S</td><td>div with *ngIf formats crazy.</td>
</tr>
<tr>
<td>yyy</td><td>S</td><td>div with *ngIf formats crazy.</td>
</tr>
</div>
<!-- #enddocregion ngif-div -->
</ng-container>
<!-- #docregion ngif-ngcontainer-4 -->
<ng-container *ngIf="strucDirs">
<tr>
<td>NgFor</td><td>S</td><td>Repeat the template for each item in a list.</td>
</tr>
<tr>
<td>NgIf</td><td>S</td><td>Add or remove DOM elements.</td>
</tr>
</ng-container>
<!-- #enddocregion ngif-ngcontainer-4 -->
<ng-container *ngIf="attrDirs">
<tr>
<td>NgStyle</td><td>A</td><td>Add or remove multiple style attributes.</td>
</tr>
</ng-container>
<!-- #docregion ngif-tr -->
<tr *ngIf="strucDirs">
<td>NgSwitch</td><td>S</td><td>Include in DOM if case matches the switch value.</td>
</tr>
<!-- #enddocregion ngif-tr -->
</table>
<hr>
<h3>Do not confuse &lt;ng-container&gt; with &lt;ng-content&gt;</h3>
<p>&lt;ng-container&gt;Inside ng-container&lt;/ng-container&gt;</p>
<!-- #docregion ngcontainer-bare -->
<ng-container>Inside ng-container</ng-container>
<!-- #enddocregion ngcontainer-bare -->
<p>&lt;ng-content&gt;this is an Angular parse error&lt;/ng-content&gt;</p>
<!-- #docregion ngcontent-bad -->
<!-- <ng-content>this is an Angular parse error</ng-content> -->
<!-- #enddocregion ngcontent-bad -->
<div class="code">Template parse errors:<br>
&lt;ng-content&gt; element cannot have content.</div>
<p>Demo of &lt;/ng-content&gt;</p>
<!-- #docregion content-comp -->
<content-comp>
Projected content
</content-comp>
<!-- #enddocregion content-comp -->

View File

@ -0,0 +1,25 @@
// #docregion
import { Component } from '@angular/core';
import { heroes } from './hero';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
heroes = heroes;
hero = this.heroes[0];
heroTraits = [ 'honest', 'brave', 'considerate' ];
// flags for the table
attrDirs = true;
strucDirs = true;
divNgIf = false;
showId = true;
showDefaultTraits = true;
showSad = true;
}

View File

@ -0,0 +1,19 @@
// #docregion
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ContentComponent } from './content.component';
import { heroComponents } from './hero.components';
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [
AppComponent,
ContentComponent,
heroComponents
],
bootstrap: [ AppComponent ]
})
export class AppModule { }

View File

@ -0,0 +1,16 @@
// #docregion
import { Component } from '@angular/core';
@Component({
selector: 'content-comp',
// #docregion template
template:
`<div>
<ng-content></ng-content>
</div>`,
// #enddocregion template
styles: [ `
div { border: medium dashed green; padding: 1em; width: 150px; text-align: center}
`]
})
export class ContentComponent { }

View File

@ -0,0 +1,43 @@
// #docregion
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'happy-hero',
template: `Wow. You like {{hero.name}}. What a happy hero ... just like you.`
})
export class HappyHeroComponent {
@Input() hero: Hero;
}
@Component({
selector: 'sad-hero',
template: `You like {{hero.name}}? Such a sad hero. Are you sad too?`
})
export class SadHeroComponent {
@Input() hero: Hero;
}
@Component({
selector: 'confused-hero',
template: `Are you as confused as {{hero.name}}?`
})
export class ConfusedHeroComponent {
@Input() hero: Hero;
}
@Component({
selector: 'unknown-hero',
template: `{{message}}`
})
export class UnknownHeroComponent {
@Input() hero: Hero;
get message() {
return this.hero && this.hero.name ?
`${this.hero.name} is strange and mysterious.` :
'Are you feeling indecisive?';
}
}
export const heroComponents =
[ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ];

View File

@ -0,0 +1,13 @@
// #docregion
export class Hero {
id: number;
name: string;
emotion?: string;
}
export const heroes: Hero[] = [
{ id: 1, name: 'Mr. Nice', emotion: 'happy'},
{ id: 2, name: 'Narco', emotion: 'sad' },
{ id: 3, name: 'Windstorm', emotion: 'confused' },
{ id: 4, name: 'Magneta'}
];

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<!-- #docregion -->
<html>
<head>
<title>Angular &lt;ng-container&gt;</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<!-- Polyfills -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('main.js').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading...</my-apps>
</body>
</html>

View File

@ -0,0 +1,6 @@
// #docregion
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -1,68 +1,58 @@
'use strict'; // necessary for es6 output in node
'use strict'; // necessary for es6 output in node
import { browser, element, by } from 'protractor';
describe('Structural Directives', function () {
// tests interact - so we need beforeEach instead of beforeAll
beforeEach(function () {
beforeAll(function () {
browser.get('');
});
it('should be able to use ngFor, ngIf and ngWhen together', function () {
let allDivEles = element.all(by.css('structural-directives > div'));
expect(allDivEles.get(0).getText()).toEqual('Mr. Nice');
expect(allDivEles.get(1).getText()).toEqual('Mr. Nice');
expect(allDivEles.get(4).getText()).toEqual('Ready');
it('first div should show hero name with *ngIf', function () {
const allDivs = element.all(by.tagName('div'));
expect(allDivs.get(0).getText()).toEqual('Mr. Nice');
});
it('should be able to toggle ngIf with a button', function () {
let setConditionButtonEle = element.all(by.css('button')).get(0);
let conditionTrueEles = element.all(by.cssContainingText('p', 'condition is true'));
let conditionFalseEles = element.all(by.cssContainingText('p', 'condition is false'));
expect(conditionTrueEles.count()).toBe(2, 'should be two condition true elements');
expect(conditionFalseEles.count()).toBe(0, 'should be no condition false elements');
setConditionButtonEle.click().then(function() {
expect(conditionTrueEles.count()).toBe(0, 'should be no condition true elements');
expect(conditionFalseEles.count()).toBe(2, 'should be two condition false elements');
it('first li should show hero name with *ngFor', function () {
const allLis = element.all(by.tagName('li'));
expect(allLis.get(0).getText()).toEqual('Mr. Nice');
});
it('ngSwitch have three <happy-hero> instances', function () {
const happyHeroEls = element.all(by.tagName('happy-hero'));
expect(happyHeroEls.count()).toEqual(3);
});
it('should toggle *ngIf="hero" with a button', function () {
const toggleHeroButton = element.all(by.cssContainingText('button', 'Toggle hero')).get(0);
const paragraph = element.all(by.cssContainingText('p', 'I turned the corner'));
expect(paragraph.get(0).getText()).toContain('I waved');
toggleHeroButton.click().then(() => {
expect(paragraph.get(0).getText()).not.toContain('I waved');
});
});
it('should be able to compare use of ngIf with changing css visibility', function () {
let setConditionButtonEle = element.all(by.css('button')).get(0);
let ngIfButtonEle = element(by.cssContainingText('button', 'if | !if'));
let ngIfParentEle = ngIfButtonEle.element(by.xpath('..'));
let ngIfSiblingEle = ngIfParentEle.element(by.css('heavy-loader'));
let cssButtonEle = element(by.cssContainingText('button', 'show | hide'));
let cssSiblingEle = cssButtonEle.element(by.xpath('..')).element(by.css('heavy-loader'));
let setConditionText: string;
setConditionButtonEle.getText().then(function(text: string) {
setConditionText = text;
expect(ngIfButtonEle.isPresent()).toBe(true, 'should be able to find ngIfButton');
expect(cssButtonEle.isPresent()).toBe(true, 'should be able to find cssButton');
expect(ngIfParentEle.isPresent()).toBe(true, 'should be able to find ngIfButton parent');
expect(ngIfSiblingEle.isPresent()).toBe(true, 'should be able to find ngIfButton sibling');
expect(cssSiblingEle.isPresent()).toBe(true, 'should be able to find cssButton sibling');
return ngIfButtonEle.click();
}).then(function() {
expect(ngIfSiblingEle.isPresent()).toBe(false, 'now should NOT be able to find ngIfButton sibling');
expect(setConditionButtonEle.getText()).not.toEqual(setConditionText);
return cssButtonEle.click();
}).then(function() {
expect(cssSiblingEle.isPresent()).toBe(true, 'now should still be able to find cssButton sibling');
expect(cssSiblingEle.isDisplayed()).toBe(false, 'now cssButton sibling should NOT be visible');
return ngIfButtonEle.click();
}).then(function() {
expect(setConditionButtonEle.getText()).toEqual(setConditionText);
});
it('should have only one "Hip!" (the other is erased)', function () {
const paragraph = element.all(by.cssContainingText('p', 'Hip!'));
expect(paragraph.count()).toEqual(1);
});
it('should be able to use *ngIf ', function () {
let setConditionButtonEle = element.all(by.css('button')).get(0);
let displayEles = element.all(by.cssContainingText('p', 'Our heroes are true!'));
expect(displayEles.count()).toBe(2, 'should be displaying two ngIf elements');
setConditionButtonEle.click().then(function() {
expect(displayEles.count()).toBe(0, 'should nog longer be displaying ngIf elements');
it('myUnless should show 3 paragraph (A)s and (B)s at the start', function () {
const paragraph = element.all(by.css('p.unless'));
expect(paragraph.count()).toEqual(3);
for (let i = 0; i < 3; i++) {
expect(paragraph.get(i).getText()).toContain('(A)');
}
});
it('myUnless should show 1 paragraph (B) after toggling condition', function () {
const toggleConditionButton = element.all(by.cssContainingText('button', 'Toggle condition')).get(0);
const paragraph = element.all(by.css('p.unless'));
toggleConditionButton.click().then(() => {
expect(paragraph.count()).toEqual(1);
expect(paragraph.get(0).getText()).toContain('(B)');
});
});
});

View File

@ -1,7 +1,11 @@
{
"description": "Structural directives",
"basePath": "src/",
"files": ["!**/*.d.ts", "!**/*.js"],
"files": [
"!**/*.d.ts",
"!**/*.js",
"!app/scrap.txt"
],
"tags": [
"structural", "directives", "template", "ngIf",
"ngSwitch", "ngFor"

View File

@ -0,0 +1,70 @@
/* #docregion */
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;
}
/* #docregion p-span */
p span { color: red; font-size: 70%; }
/* #enddocregion p-span */
.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%;
}

View File

@ -0,0 +1,252 @@
<!-- #docplaster -->
<!-- #docregion -->
<h1>Structural Directives</h1>
<p>Conditional display of hero</p>
<blockquote>
<!-- #docregion built-in, asterisk, ngif -->
<div *ngIf="hero" >{{hero.name}}</div>
<!-- #enddocregion built-in, asterisk, ngif -->
</blockquote>
<p>List of heroes</p>
<!-- #docregion built-in -->
<ul>
<!-- #docregion ngfor-li -->
<li *ngFor="let hero of heroes">{{hero.name}}</li>
<!-- #enddocregion ngfor-li -->
</ul>
<!-- #enddocregion built-in -->
<hr>
<h2 id="ngIf">NgIf</h2>
<!-- #docregion ngif-true -->
<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>
<!-- #enddocregion ngif-true -->
<!-- #docregion display-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>
<!-- #enddocregion display-none -->
<h4>NgIf with template</h4>
<p>&lt;template&gt; element</p>
<!-- #docregion ngif-template -->
<template [ngIf]="hero">
<div>{{hero.name}}</div>
</template>
<!-- #enddocregion ngif-template -->
<p>template attribute</p>
<!-- #docregion ngif-template-attr -->
<div template="ngIf hero">{{hero.name}}</div>
<!-- #enddocregion ngif-template-attr -->
<hr>
<h2 id="ng-container">&lt;ng-container&gt;</h2>
<h4>*ngIf with a &lt;ng-container&gt;</h4>
<button (click)="hero = hero ? null : heroes[0]">Toggle hero</button>
<!-- #docregion ngif-ngcontainer -->
<p>
I turned the corner
<ng-container *ngIf="hero">
and saw {{hero.name}}. I waved
</ng-container>
and continued on my way.
</p>
<!-- #enddocregion ngif-ngcontainer -->
<!-- #docregion ngif-span -->
<p>
I turned the corner
<span *ngIf="hero">
and saw {{hero.name}}. I waved
</span>
and continued on my way.
</p>
<!-- #enddocregion ngif-span -->
<p><i>&lt;select&gt; with &lt;span&gt;</i></p>
<!-- #docregion select-span -->
<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>
<!-- #enddocregion select-span -->
<p><i>&lt;select&gt; with &lt;ng-container&gt;</i></p>
<!-- #docregion select-ngcontainer -->
<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>
<!-- #enddocregion select-ngcontainer -->
<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: trackById" [class.odd]="odd"&gt;</p>
<!--#docregion inside-ngfor -->
<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
({{i}}) {{hero.name}}
</div>
<!--#enddocregion inside-ngfor -->
<p class="code">&lt;div template="ngFor let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"&gt;</p>
<!--#docregion inside-ngfor -->
<div template="ngFor let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">
({{i}}) {{hero.name}}
</div>
<!--#enddocregion inside-ngfor -->
<p class="code">&lt;template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"&gt;</p>
<!--#docregion inside-ngfor -->
<template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">
<div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</template>
<!--#enddocregion inside-ngfor -->
</div>
<hr>
<h2 id="ngSwitch">NgSwitch</h2>
<div>Pick your favorite hero</div>
<p>
<ng-container *ngFor="let h of heroes">
<label>
<input type="radio" name="heroes" [(ngModel)]="hero" [value]="h">{{h.name}}
</label>
</ng-container>
<label><input type="radio" name="heroes" (click)="hero = null">None of the above</label>
</p>
<h4>NgSwitch</h4>
<!-- #docregion built-in , ngswitch -->
<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>
<!-- #enddocregion built-in, ngswitch -->
<h4>NgSwitch with <i>template</i> attribute</h4>
<!-- #docregion ngswitch-template-attr -->
<div [ngSwitch]="hero?.emotion">
<happy-hero template="ngSwitchCase 'happy'" [hero]="hero"></happy-hero>
<sad-hero template="ngSwitchCase 'sad'" [hero]="hero"></sad-hero>
<confused-hero template="ngSwitchCase 'confused'" [hero]="hero"></confused-hero>
<unknown-hero template="ngSwitchDefault" [hero]="hero"></unknown-hero>
</div>
<!-- #enddocregion ngswitch-template-attr -->
<h4>NgSwitch with &lt;template&gt;</h4>
<!-- #docregion ngswitch-template -->
<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>
<!-- #enddocregion ngswitch-template -->
<hr>
<h2>&lt;template&gt;</h2>
<!-- #docregion template-tag -->
<p>Hip!</p>
<template>
<p>Hip!</p>
</template>
<p>Hooray!</p>
<!-- #enddocregion template-tag -->
<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>
<!-- #docregion myUnless-->
<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>
<!-- #enddocregion myUnless-->
<h4>UnlessDirective with template</h4>
<!-- #docregion myUnless-1 -->
<p *myUnless="condition">Show this sentence unless the condition is true.</p>
<!-- #enddocregion myUnless-1 -->
<p template="myUnless condition" class="code unless">
(A) &lt;p template="myUnless condition" class="code unless"&gt;
</p>
<template [myUnless]="condition">
<p class="code unless">
(A) &lt;template [myUnless]="condition"&gt;
</p>
</template>

View File

@ -0,0 +1,24 @@
// #docregion
import { Component } from '@angular/core';
import { Hero, heroes } from './hero';
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
heroes = heroes;
hero = this.heroes[0];
condition = false;
logs: string[] = [];
showSad = true;
status = 'ready';
// #docregion trackByHero
trackById(index: number, hero: Hero): number { return hero.id; }
// #enddocregion trackByHero
}

View File

@ -1,18 +1,19 @@
// #docregion
import { NgModule } from '@angular/core';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { StructuralDirectivesComponent } from './structural-directives.component';
import { UnlessDirective } from './unless.directive';
import { HeavyLoaderComponent } from './heavy-loader.component';
import { AppComponent } from './app.component';
import { heroSwitchComponents } from './hero-switch.components';
import { UnlessDirective } from './unless.directive';
@NgModule({
imports: [ BrowserModule ],
imports: [ BrowserModule, FormsModule ],
declarations: [
StructuralDirectivesComponent,
UnlessDirective,
HeavyLoaderComponent
AppComponent,
heroSwitchComponents,
UnlessDirective
],
bootstrap: [ StructuralDirectivesComponent ]
bootstrap: [ AppComponent ]
})
export class AppModule { }

View File

@ -1,35 +0,0 @@
// #docregion
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
let nextId = 1;
@Component({
selector: 'heavy-loader',
template: '<span>heavy loader #{{id}} on duty!</span>'
})
export class HeavyLoaderComponent implements OnDestroy, OnInit {
id = nextId++;
@Input() logs: string[];
ngOnInit() {
// Mock todo: get 10,000 rows of data from the server
this.log(`heavy-loader ${this.id} initialized,
loading 10,000 rows of data from the server`);
}
ngOnDestroy() {
// Mock todo: clean-up
this.log(`heavy-loader ${this.id} destroyed, cleaning up`);
}
private log(msg: string) {
this.logs.push(msg);
this.tick();
}
// Triggers the next round of Angular change detection
// after one turn of the browser event loop
// ensuring display of msg added in onDestroy
private tick() { setTimeout(() => { }, 0); }
}
// #enddocregion

View File

@ -0,0 +1,43 @@
// #docregion
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'happy-hero',
template: `Wow. You like {{hero.name}}. What a happy hero ... just like you.`
})
export class HappyHeroComponent {
@Input() hero: Hero;
}
@Component({
selector: 'sad-hero',
template: `You like {{hero.name}}? Such a sad hero. Are you sad too?`
})
export class SadHeroComponent {
@Input() hero: Hero;
}
@Component({
selector: 'confused-hero',
template: `Are you as confused as {{hero.name}}?`
})
export class ConfusedHeroComponent {
@Input() hero: Hero;
}
@Component({
selector: 'unknown-hero',
template: `{{message}}`
})
export class UnknownHeroComponent {
@Input() hero: Hero;
get message() {
return this.hero && this.hero.name ?
`${this.hero.name} is strange and mysterious.` :
'Are you feeling indecisive?';
}
}
export const heroSwitchComponents =
[ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ];

View File

@ -0,0 +1,13 @@
// #docregion
export class Hero {
id: number;
name: string;
emotion?: string;
}
export const heroes: Hero[] = [
{ id: 1, name: 'Mr. Nice', emotion: 'happy'},
{ id: 2, name: 'Narco', emotion: 'sad' },
{ id: 3, name: 'Windstorm', emotion: 'confused' },
{ id: 4, name: 'Magneta'}
];

View File

@ -0,0 +1,21 @@
// interesting but unused code
heroChooser(picker: HTMLFieldSetElement) {
let choices = picker.children;
this.favoriteHero = undefined;
for (let i = 0; i < choices.length; i++) {
let choice = choices[i].children[0] as HTMLInputElement;
if (choice.checked) { this.favoriteHero = this.heroes[i]; }
}
}
<h4>Switch with *ngFor repeated switchCases using &lt;ng-container&gt;</h4>
<!-- #docregion NgSwitch-ngFor -->
<div [ngSwitch]="hero.id">
Your favorite hero is ...
<ng-container *ngFor="let hero of heroes">
<ng-container *ngSwitchCase="hero.id">{{hero.name}}</ng-container>
</ng-container>
<ng-container *ngSwitchDefault>None of the above</ng-container>
</div>
<!-- #enddocregion NgSwitch-ngFor -->

View File

@ -1,109 +0,0 @@
<!-- #docplaster -->
<!-- #docregion -->
<h1>Structural Directives</h1>
<!-- #docregion structural-directives -->
<!-- #docregion asterisk -->
<div *ngIf="hero">{{hero}}</div>
<div *ngFor="let hero of heroes">{{hero}}</div>
<!-- #enddocregion asterisk -->
<!-- #docregion ngSwitch -->
<div [ngSwitch]="status">
<template [ngSwitchCase]="'in-mission'">In Mission</template>
<template [ngSwitchCase]="'ready'">Ready</template>
<template ngSwitchDefault>Unknown</template>
</div>
<!-- #enddocregion ngSwitch -->
<!-- #enddocregion structural-directives -->
<hr>
<button
(click)="condition = !condition"
[style.background] = "condition ? 'orangered': 'lightgreen'"
>
Set 'condition' to {{condition ? 'False': 'True'}}
</button>
<!-- #docregion ngIf -->
<p *ngIf="condition">
condition is true and ngIf is true.
</p>
<p *ngIf="!condition">
condition is false and ngIf is false.
</p>
<!-- #enddocregion ngIf -->
<!-- #docregion myUnless-->
<p *myUnless="condition">
condition is false and myUnless is true.
</p>
<p *myUnless="!condition">
condition is true and myUnless is false.
</p>
<!-- #enddocregion myUnless-->
<hr>
<!-- #docregion message-log -->
<div><!-- Visibility -->
<button (click)="isVisible = !isVisible">show | hide</button>
<heavy-loader [style.display]="isVisible ? 'inline' : 'none'" [logs]="logs"></heavy-loader>
</div>
<div><!-- NgIf -->
<button (click)="condition = !condition">if | !if</button>
<heavy-loader *ngIf="condition" [logs]="logs"></heavy-loader>
</div>
<h4>heavy-loader log:</h4>
<div *ngFor="let message of logs">{{message}}</div>
<!-- #enddocregion message-log -->
<hr>
<!-- #docregion template-tag -->
<p>
Hip!
</p>
<template>
<p>
Hip!
</p>
</template>
<p>
Hooray!
</p>
<!-- #enddocregion template-tag -->
<hr>
<!-- #docregion ngIf-template -->
<!-- Examples (A) and (B) are the same -->
<!-- (A) *ngIf paragraph -->
<p *ngIf="condition">
Our heroes are true!
</p>
<!-- (B) [ngIf] with template -->
<template [ngIf]="condition">
<p>
Our heroes are true!
</p>
</template>
<!-- #enddocregion ngIf-template -->
<hr>
<!-- #docregion ngFor-template -->
<!-- Examples (A) and (B) are the same -->
<!-- (A) *ngFor div -->
<div *ngFor="let hero of heroes">{{ hero }}</div>
<!-- (B) ngFor with template -->
<template ngFor let-hero [ngForOf]="heroes">
<div>{{ hero }}</div>
</template>
<!-- #enddocregion ngFor-template -->
<!-- #enddocregion -->

View File

@ -1,19 +0,0 @@
// #docplaster
// #docregion
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
selector: 'structural-directives',
templateUrl: './structural-directives.component.html',
styles: ['button { min-width: 100px; }']
})
export class StructuralDirectivesComponent {
heroes = ['Mr. Nice', 'Narco', 'Bombasto'];
hero = this.heroes[0];
condition = true;
isVisible = true;
logs: string[] = [];
status = 'ready';
}
// #enddocregion

View File

@ -1,33 +1,55 @@
// #docplaster
// #docregion
// #docregion unless-declaration
import { Directive, Input } from '@angular/core';
// #docregion no-docs
// #docregion skeleton
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
// #enddocregion unless-declaration
import { TemplateRef, ViewContainerRef } from '@angular/core';
// #docregion unless-declaration
@Directive({ selector: '[myUnless]' })
// #enddocregion skeleton
/**
* Add the template content to the DOM unless the condition is true.
// #enddocregion no-docs
*
* 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>`
* - `<div template="myUnless condition">...</div>`
* - `<template [myUnless]="condition"><div>...</div></template>`
*
// #docregion no-docs
*/
// #docregion skeleton
@Directive({ selector: '[myUnless]'})
export class UnlessDirective {
// #enddocregion unless-declaration
// #enddocregion skeleton
private hasView = false;
// #docregion unless-constructor
// #docregion ctor
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) { }
// #enddocregion unless-constructor
private viewContainer: ViewContainerRef) { }
// #enddocregion ctor
// #docregion unless-set
// #docregion set
@Input() set myUnless(condition: boolean) {
if (!condition) {
if (!condition && !this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
// #enddocregion unless-set
// #docregion unless-declaration
// #enddocregion set
// #docregion skeleton
}
// #enddocregion unless-declaration
// #enddocregion skeleton
// #enddocregion no-docs
// #enddocregion

View File

@ -21,7 +21,7 @@
</head>
<body>
<structural-directives>Loading...</structural-directives>
<my-app>Loading...</my-apps>
</body>
</html>

View File

@ -0,0 +1,17 @@
a.to-toc { margin: 30px 0; }
button { font-size: 100%; margin: 0 2px; }
div[clickable] {cursor: pointer; max-width: 200px; margin: 16px 0}
#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}
.special {font-weight:bold; font-size: x-large}
.bad {color: red;}
.saveable {color: limegreen;}
.curly, .modified {font-family: "Brush Script MT"}
.toe {margin-left: 1em; font-style: italic;}
little-hero {color:blue; font-size: smaller; background-color: Turquoise }
.to-toc {margin-top: 10px; display: block}

View File

@ -2,6 +2,8 @@
<a id="toc"></a>
<h1>Template Syntax</h1>
<a href="#interpolation">Interpolation</a><br>
<a href="#expression-context">Expression context</a><br>
<a href="#statement-context">Statement context</a><br>
<a href="#mental-model">Mental Model</a><br>
<a href="#buttons">Buttons</a><br>
<a href="#prop-vs-attrib">Properties vs. Attributes</a><br>
@ -22,15 +24,14 @@
<a href="#ngClass">NgClass Binding</a><br>
<a href="#ngStyle">NgStyle Binding</a><br>
<a href="#ngIf">NgIf</a><br>
<a href="#ngSwitch">NgSwitch</a><br>
<a href="#ngFor">NgFor</a><br>
<div style="margin-left:8px">
<a href="#ngFor-index">NgFor with index</a><br>
<a href="#ngFor-trackBy">NgFor with trackBy</a><br>
</div>
<a href="#ngSwitch">NgSwitch</a><br>
</div>
<br>
<a href="#star-prefix">* prefix and &lt;template&gt;</a><br>
<a href="#ref-vars">Template reference variables</a><br>
<a href="#inputs-and-outputs">Inputs and outputs</a><br>
<a href="#pipes">Pipes</a><br>
@ -41,7 +42,7 @@
<hr><h2 id="interpolation">Interpolation</h2>
<!-- #docregion first-interpolation -->
<p>My current hero is {{currentHero.firstName}}</p>
<p>My current hero is {{currentHero.name}}</p>
<!-- #enddocregion first-interpolation -->
<!-- #docregion title+image -->
@ -63,6 +64,68 @@
<a class="to-toc" href="#toc">top</a>
<hr><h2 id="expression-context">Expression context</h2>
<p>Component expression context (&#123;&#123;title&#125;&#125;, [hidden]="isUnchanged")</p>
<div class="context">
<!-- #docregion context-component-expression -->
{{title}}
<span [hidden]="isUnchanged">changed</span>
<!-- #enddocregion context-component-expression -->
</div>
<p>Template input variable expression context (let hero)</p>
<!-- template hides the following; plenty of examples later -->
<template>
<!-- #docregion context-var -->
<div *ngFor="let hero of heroes">{{hero.name}}</div>
<!-- #enddocregion context-var -->
</template>
<p>Template reference variable expression context (#heroInput)</p>
<div (keyup)="0" class="context">
Type something:
<!-- #docregion context-var -->
<input #heroInput> {{heroInput.value}}
<!-- #enddocregion context-var -->
</div>
<a class="to-toc" href="#toc">top</a>
<hr><h2 id="statement-context">Statement context</h2>
<p>Component statement context ( (click)="onSave() )
<div class="context">
<!-- #docregion context-component-statement -->
<button (click)="deleteHero()">Delete hero</button>
<!-- #enddocregion context-component-statement -->
</div>
<p>Template $event statement context</p>
<div class="context">
<!-- #docregion context-var-statement -->
<button (click)="onSave($event)">Save</button>
<!-- #enddocregion context-var-statement -->
</div>
<p>Template input variable statement context (let hero)</p>
<!-- template hides the following; plenty of examples later -->
<div class="context">
<!-- #docregion context-var-statement -->
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
<!-- #enddocregion context-var-statement -->
</div>
<p>Template reference variable statement context (#heroForm)</p>
<div class="context">
<!-- #docregion context-var-statement -->
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>
<!-- #enddocregion context-var-statement -->
</div>
<a class="to-toc" href="#toc">top</a>
<!-- New Mental Model -->
<hr><h2 id="mental-model">New Mental Model</h2>
@ -105,16 +168,17 @@
<!-- #docregion event-binding-syntax-1 -->
<button (click) = "onSave()">Save</button>
<hero-detail (deleteRequest)="deleteHero()"></hero-detail>
<div (myClick)="clicked=$event">click me</div>
<div (myClick)="clicked=$event" clickable>click me</div>
<!-- #enddocregion event-binding-syntax-1 -->
{{clicked}}
<br><br>
<div>
Hero Name:
<!-- #docregion 2-way-binding-syntax-1 -->
<input [(ngModel)]="heroName">
<!-- #enddocregion 2-way-binding-syntax-1 -->
Hero Name: {{heroName}}
{{heroName}}
</div>
<br><br>
@ -205,6 +269,10 @@ button</button>
<!-- #enddocregion property-binding-vs-interpolation -->
<!-- #docregion property-binding-vs-interpolation-sanitization -->
<!--
Angular generates warnings for these two lines as it sanitizes them
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
-->
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
<!-- #enddocregion property-binding-vs-interpolation-sanitization -->
@ -307,7 +375,7 @@ button</button>
<!-- #docregion event-binding-3 -->
<!-- `myClick` is an event on the custom `ClickDirective` -->
<!-- #docregion myClick -->
<div (myClick)="clickMessage=$event">click with myClick</div>
<div (myClick)="clickMessage=$event" clickable>click with myClick</div>
<!-- #enddocregion myClick -->
<!-- #enddocregion event-binding-3 -->
{{clickMessage}}
@ -326,26 +394,25 @@ button</button>
</big-hero-detail>
<!-- #docregion event-binding-bubbling -->
<div class="parent-div" (click)="onClickMe($event)">Click me
<div class="parent-div" (click)="onClickMe($event)" clickable>Click me
<div class="child-div">Click me too!</div>
</div>
<!-- #enddocregion event-binding-bubbling -->
<br><br>
<!-- #docregion event-binding-no-propagation -->
<!-- Will save only once -->
<div (click)="onSave()">
<div (click)="onSave()" clickable>
<button (click)="onSave()">Save, no propagation</button>
</div>
<!-- #enddocregion event-binding-no-propagation -->
<br><br>
<!-- #docregion event-binding-propagation -->
<!-- Will save twice -->
<div (click)="onSave()">
<div (click)="onSave()" clickable>
<button (click)="onSave() || true">Save w/ propagation</button>
</div>
<!-- #enddocregion event-binding-propagation -->
<br><br>
<a class="to-toc" href="#toc">top</a>
<hr><h2 id="two-way">Two-way Binding</h2>
@ -363,7 +430,6 @@ button</button>
<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>
<!-- #enddocregion two-way-2 -->
</div>
<br><br>
<a class="to-toc" href="#toc">top</a>
@ -371,38 +437,37 @@ button</button>
passing the changed display value to the event handler via `$event` -->
<hr><h2 id="ngModel">NgModel (two-way) Binding</h2>
<h3>Result: {{currentHero.firstName}}</h3>
<h3>Result: {{currentHero.name}}</h3>
<!-- #docregion without-NgModel -->
<input [value]="currentHero.firstName"
(input)="currentHero.firstName=$event.target.value" >
<input [value]="currentHero.name"
(input)="currentHero.name=$event.target.value" >
<!-- #enddocregion without-NgModel -->
without NgModel
<br>
<!-- #docregion NgModel-1 -->
<input [(ngModel)]="currentHero.firstName">
<input [(ngModel)]="currentHero.name">
<!-- #enddocregion NgModel-1 -->
[(ngModel)]
<br>
<!-- #docregion NgModel-2 -->
<input bindon-ngModel="currentHero.firstName">
<input bindon-ngModel="currentHero.name">
<!-- #enddocregion NgModel-2 -->
bindon-ngModel
<br>
<!-- #docregion NgModel-3 -->
<input
[ngModel]="currentHero.firstName"
(ngModelChange)="currentHero.firstName=$event">
[ngModel]="currentHero.name"
(ngModelChange)="currentHero.name=$event">
<!-- #enddocregion NgModel-3 -->
(ngModelChange) = "...firstName=$event"
(ngModelChange) = "...name=$event"
<br>
<!-- #docregion NgModel-4 -->
<input
[ngModel]="currentHero.firstName"
(ngModelChange)="setUpperCaseFirstName($event)">
[ngModel]="currentHero.name"
(ngModelChange)="setUppercaseName($event)">
<!-- #enddocregion NgModel-4 -->
(ngModelChange) = "setUpperCaseFirstName($event)"
<br>
(ngModelChange) = "setUppercaseName($event)"
<a class="to-toc" href="#toc">top</a>
@ -417,7 +482,7 @@ bindon-ngModel
<!-- not used in chapter -->
<br>
<label>saveable <input type="checkbox" [(ngModel)]="canSave"></label> |
<label>modified: <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged"></label> |
<label>modified: <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged"></label> |
<label>special: <input type="checkbox" [(ngModel)]="isSpecial"></label>
<button (click)="setCurrentClasses()">Refresh currentClasses</button>
<br><br>
@ -462,7 +527,6 @@ bindon-ngModel
This div should be {{ canSave ? "italic": "plain"}},
{{ isUnchanged ? "normal weight" : "bold" }} and,
{{ isSpecial ? "extra large": "normal size"}} after clicking "refresh".</div>
<br>
<a class="to-toc" href="#toc">top</a>
@ -470,21 +534,17 @@ bindon-ngModel
<hr><h2 id="ngIf">NgIf Binding</h2>
<!-- #docregion NgIf-1 -->
<div *ngIf="currentHero">Hello, {{currentHero.firstName}}</div>
<hero-detail *ngIf="isActive"></hero-detail>
<!-- #enddocregion NgIf-1 -->
<!-- #docregion NgIf-2 -->
<!-- because of the ngIf guard
`nullHero.firstName` never has a chance to fail -->
<div *ngIf="nullHero">Hello, {{nullHero.firstName}}</div>
<!-- Hero Detail is not in the DOM because isActive is false-->
<hero-detail *ngIf="isActive"></hero-detail>
<div *ngIf="currentHero">Hello, {{currentHero.name}}</div>
<div *ngIf="nullHero">Hello, {{nullHero.name}}</div>
<!-- #enddocregion NgIf-2 -->
<!-- NgIf binding with template (no *) -->
<template [ngIf]="currentHero">Add {{currentHero.firstName}} with template</template>
<template [ngIf]="currentHero">Add {{currentHero.name}} with template</template>
<!-- Does not show because isActive is false! -->
<div>Hero Detail removed from DOM (via template) because isActive is false</div>
@ -506,170 +566,117 @@ bindon-ngModel
<a class="to-toc" href="#toc">top</a>
<!-- NgSwitch binding -->
<hr><h2 id="ngSwitch">NgSwitch Binding</h2>
<fieldset #toePicker (click)="toeChooser(toePicker)" >
<input type="radio" name="toes" value="Eenie">Eenie
<input type="radio" name="toes" value="Meanie">Meanie
<input type="radio" name="toes" value="Miney">Miney
<input type="radio" name="toes" value="Moe">Moe
<input type="radio" name="toes" value="???">???
</fieldset>
<div class="toe">
<div *ngIf="!toeChoice">Pick a toe</div>
<div *ngIf="toeChoice">
You picked ...
<!-- #docregion NgSwitch, NgSwitch-expanded -->
<span [ngSwitch]="toeChoice">
<!-- #enddocregion NgSwitch -->
<!-- with *NgSwitch -->
<!-- #docregion NgSwitch -->
<span *ngSwitchCase="'Eenie'">Eenie</span>
<span *ngSwitchCase="'Meanie'">Meanie</span>
<span *ngSwitchCase="'Miney'">Miney</span>
<span *ngSwitchCase="'Moe'">Moe</span>
<span *ngSwitchDefault>other</span>
<!-- #enddocregion NgSwitch -->
<!-- with <template> -->
<template [ngSwitchCase]="'Eenie'"><span>Eenie</span></template>
<template [ngSwitchCase]="'Meanie'"><span>Meanie</span></template>
<template [ngSwitchCase]="'Miney'"><span>Miney</span></template>
<template [ngSwitchCase]="'Moe'"><span>Moe</span></template>
<template ngSwitchDefault><span>other</span></template>
<!-- #docregion NgSwitch -->
</span>
<!-- #enddocregion NgSwitch, NgSwitch-expanded -->
</div>
</div>
<a class="to-toc" href="#toc">top</a>
<!-- NgFor binding -->
<hr><h2 id="ngFor">NgFor Binding</h2>
<div class="box">
<!-- #docregion NgFor-1 -->
<div *ngFor="let hero of heroes">{{hero.fullName}}</div>
<!-- #enddocregion NgFor-1 -->
<!-- #docregion NgFor-1, NgFor-1-2 -->
<div *ngFor="let hero of heroes">{{hero.name}}</div>
<!-- #enddocregion NgFor-1, NgFor-1-2 -->
</div>
<br>
<div class="box">
<!-- *ngFor w/ hero-detail Component -->
<!-- #docregion NgFor-2 -->
<!-- #docregion NgFor-2, NgFor-1-2 -->
<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>
<!-- #enddocregion NgFor-2 -->
<!-- #enddocregion NgFor-2, NgFor-1-2 -->
</div>
<a class="to-toc" href="#toc">top</a>
<h4 id="ngFor-index">NgFor with index</h4>
<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 hero of heroes; let i=index">{{i + 1}} - {{hero.fullName}}</div>
<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.name}}</div>
<!-- #enddocregion NgFor-3 -->
</div>
<p>with <i>comma</i> separator</p>
<div class="box">
<!-- Ex: "1 - Hercules Son of Zeus"" -->
<div *ngFor="let hero of heroes, let i=index">{{i + 1}} - {{hero.fullName}}</div>
<!-- Ex: "1 - Hercules" -->
<div *ngFor="let hero of heroes, let i=index">{{i + 1}} - {{hero.name}}</div>
</div>
<a class="to-toc" href="#toc">top</a>
<h4 id="ngFor-trackBy">NgForTrackBy</h4>
<button (click)="refreshHeroes()">Refresh heroes</button>
<p>First hero: <input [(ngModel)]="heroes[0].firstName"></p>
<h4 id="ngFor-trackBy">*ngFor trackBy</h4>
<button (click)="resetHeroes()">Reset heroes</button>
<button (click)="changeIds()">Change ids</button>
<button (click)="clearTrackByCounts()">Clear counts</button>
<p><i>without</i> trackBy</p>
<div #noTrackBy class="box">
<!-- #docregion NgForTrackBy-1 -->
<div *ngFor="let hero of heroes">({{hero.id}}) {{hero.fullName}}</div>
<!-- #enddocregion NgForTrackBy-1 -->
</div>
<div id="noTrackByCnt" *ngIf="heroesNoTrackByChangeCount" style="background-color:bisque">
Hero DOM elements change #<span style="background-color:gold">{{heroesNoTrackByChangeCount}}</span> without trackBy
<div class="box">
<div #noTrackBy *ngFor="let hero of heroes">({{hero.id}}) {{hero.name}}</div>
<div id="noTrackByCnt" *ngIf="heroesNoTrackByCount" >
Hero DOM elements change #{{heroesNoTrackByCount}} without trackBy
</div>
</div>
<p>with trackBy and <i>semi-colon</i> separator</p>
<div #withTrackBy class="box">
<!-- #docregion NgForTrackBy-2 -->
<div *ngFor="let hero of heroes; trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
<!-- #enddocregion NgForTrackBy-2 -->
<p>with trackBy</p>
<div class="box">
<div #withTrackBy *ngFor="let hero of heroes; trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
<div id="withTrackByCnt" *ngIf="heroesWithTrackByCount">
Hero DOM elements change #{{heroesWithTrackByCount}} with trackBy
</div>
</div>
<div id="withTrackByCnt" *ngIf="heroesWithTrackByChangeCount" style="background-color:bisque">
Hero DOM elements change #<span style="background-color:gold">{{heroesWithTrackByChangeCount}}</span> with trackBy
<br><br><br>
<p>with trackBy and <i>semi-colon</i> separator</p>
<div class="box">
<!-- #docregion trackBy -->
<div *ngFor="let hero of heroes; trackBy: trackByHeroes">
({{hero.id}}) {{hero.name}}
</div>
<!-- #enddocregion trackBy -->
</div>
<p>with trackBy and <i>comma</i> separator</p>
<div class="box">
<div *ngFor="let hero of heroes, trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
<div *ngFor="let hero of heroes, trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
</div>
<p>with trackBy and <i>space</i> separator</p>
<div class="box">
<div *ngFor="let hero of heroes trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
<div *ngFor="let hero of heroes trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
</div>
<p>with <i>generic</i> trackById function</p>
<div class="box">
<div *ngFor="let hero of heroes, trackBy:trackById">({{hero.id}}) {{hero.fullName}}</div>
<div *ngFor="let hero of heroes, trackBy: trackById">({{hero.id}}) {{hero.name}}</div>
</div>
<a class="to-toc" href="#toc">top</a>
<!-- * and template -->
<hr><h2 id="star-prefix">* prefix and &lt;template&gt;</h2>
<!-- NgSwitch binding -->
<hr><h2 id="ngSwitch">NgSwitch Binding</h2>
<h3>*ngIf expansion</h3>
<p><i>*ngIf</i></p>
<!-- #docregion Template-1 -->
<hero-detail *ngIf="currentHero" [hero]="currentHero"></hero-detail>
<!-- #enddocregion Template-1 -->
<div>Pick your favorite hero</div>
<p>
<ng-container *ngFor="let h of heroes">
<label>
<input type="radio" name="heroes" [(ngModel)]="currentHero" [value]="h">{{h.name}}
</label>
</ng-container>
</p>
<p><i>expand to template = "..."</i></p>
<!-- #docregion Template-2a -->
<hero-detail template="ngIf:currentHero" [hero]="currentHero"></hero-detail>
<!-- #enddocregion Template-2a -->
<p><i>expand to &lt;template&gt;</i></p>
<!-- #docregion Template-2 -->
<template [ngIf]="currentHero">
<hero-detail [hero]="currentHero"></hero-detail>
</template>
<!-- #enddocregion Template-2 -->
<h3>*ngFor expansion</h3>
<p><i>*ngFor</i></p>
<!-- *ngFor w/ hero-detail Component -->
<!-- #docregion Template-3a -->
<hero-detail *ngFor="let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>
<!-- #enddocregion Template-3a -->
<p><i>expand to template = "..."</i></p>
<div class="box">
<!-- ngFor w/ hero-detail Component and a template "attribute" directive -->
<!-- #docregion Template-3 -->
<hero-detail template="ngFor let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>
<!-- #enddocregion Template-3 -->
</div>
<p><i>expand to &lt;template&gt;</i></p>
<div class="box">
<!-- ngFor w/ hero-detail Component inside a template element -->
<!-- #docregion Template-4 -->
<template ngFor let-hero [ngForOf]="heroes" [ngForTrackBy]="trackByHeroes">
<hero-detail [hero]="hero"></hero-detail>
</template>
<!-- #enddocregion Template-4 -->
<!-- #docregion NgSwitch -->
<div [ngSwitch]="currentHero.emotion">
<happy-hero *ngSwitchCase="'happy'" [hero]="currentHero"></happy-hero>
<sad-hero *ngSwitchCase="'sad'" [hero]="currentHero"></sad-hero>
<confused-hero *ngSwitchCase="'confused'" [hero]="currentHero"></confused-hero>
<!-- #enddocregion NgSwitch -->
<!-- #docregion NgSwitch-div -->
<div *ngSwitchCase="'confused'">Are you as confused as {{currentHero.name}}?</div>
<!-- #enddocregion NgSwitch-div -->
<!-- #docregion NgSwitch -->
<unknown-hero *ngSwitchDefault [hero]="currentHero"></unknown-hero>
</div>
<!-- #enddocregion NgSwitch -->
<a class="to-toc" href="#toc">top</a>
@ -677,34 +684,27 @@ bindon-ngModel
<hr><h2 id="ref-vars">Template reference variables</h2>
<!-- #docregion ref-phone -->
<!-- phone refers to the input element; pass its `value` to an event handler -->
<!-- #docregion ref-var -->
<input #phone placeholder="phone number">
<button (click)="callPhone(phone.value)">Call</button>
<!-- #enddocregion ref-var -->
<!-- fax refers to the input element; pass its `value` to an event handler -->
<input ref-fax placeholder="fax number">
<button (click)="callFax(fax.value)">Fax</button>
<!-- lots of other elements -->
<!-- phone refers to the input element; pass its `value` to an event handler -->
<button (click)="callPhone(phone.value)">Call</button>
<!-- #enddocregion ref-phone -->
<h4>Example Form</h4>
<!-- #docregion ref-form -->
<!-- #docregion ref-form-a -->
<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm">
<!-- #enddocregion ref-form-a -->
<div class="form-group">
<label for="name">Name</label>
<input class="form-control" name="name" required [(ngModel)]="currentHero.firstName">
</div>
<!-- #docregion ref-form-a -->
<button type="submit" [disabled]="!theForm.form.valid">Submit</button>
</form>
<!-- #enddocregion ref-form-a -->
<!-- #enddocregion ref-form -->
<br><br>
<!-- #docregion ref-fax -->
<input ref-fax placeholder="fax number">
<button (click)="callFax(fax.value)">Fax</button>
<!-- #enddocregion ref-fax -->
<!-- btn refers to the button element; show its disabled state -->
<button #btn disabled [innerHTML]="'disabled by attribute: '+btn.disabled"></button>
<h4>Example Form</h4>
<hero-form [hero]="currentHero"></hero-form>
<a class="to-toc" href="#toc">top</a>
<!-- inputs and output -->
@ -720,7 +720,7 @@ bindon-ngModel
</hero-detail>
<!-- #enddocregion io-2 -->
<div (myClick)="clickMessage2=$event">myClick2</div>
<div (myClick)="clickMessage2=$event" clickable>myClick2</div>
{{clickMessage2}}
<a class="to-toc" href="#toc">top</a>
@ -769,43 +769,42 @@ bindon-ngModel
<div>
<!-- #docregion safe-2 -->
The current hero's name is {{currentHero?.firstName}}
The current hero's name is {{currentHero?.name}}
<!-- #enddocregion safe-2 -->
</div>
<div>
<!-- #docregion safe-3 -->
The current hero's name is {{currentHero.firstName}}
The current hero's name is {{currentHero.name}}
<!-- #enddocregion safe-3 -->
</div>
<!--
The null hero's name is {{nullHero.firstName}}
The null hero's name is {{nullHero.name}}
See console log:
TypeError: Cannot read property 'firstName' of null in [null]
TypeError: Cannot read property 'name' of null in [null]
-->
<!-- #docregion safe-4 -->
<!--No hero, div not displayed, no error -->
<div *ngIf="nullHero">The null hero's name is {{nullHero.firstName}}</div>
<div *ngIf="nullHero">The null hero's name is {{nullHero.name}}</div>
<!-- #enddocregion safe-4 -->
<div>
<!-- #docregion safe-5 -->
The null hero's name is {{nullHero && nullHero.firstName}}
The null hero's name is {{nullHero && nullHero.name}}
<!-- #enddocregion safe-5 -->
</div>
<div>
<!-- #docregion safe-6 -->
<!-- No hero, no problem! -->
The null hero's name is {{nullHero?.firstName}}
The null hero's name is {{nullHero?.name}}
<!-- #enddocregion safe-6 -->
</div>
<a class="to-toc" href="#toc">top</a>
<!-- TODO: discuss this in the Style binding section -->

View File

@ -2,7 +2,6 @@
// #docplaster
import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Hero } from './hero';
@ -19,20 +18,26 @@ export enum Color {Red, Green, Blue};
@Component({
moduleId: module.id,
selector: 'my-app',
templateUrl: './app.component.html'
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewInit, OnInit {
ngOnInit() {
this.refreshHeroes();
this.resetHeroes();
this.setCurrentClasses();
this.setCurrentStyles();
}
ngAfterViewInit() {
this.detectNgForTrackByEffects();
// Detect effects of NgForTrackBy
trackChanges(this.heroesNoTrackBy, () => this.heroesNoTrackByCount += 1);
trackChanges(this.heroesWithTrackBy, () => this.heroesWithTrackByCount += 1);
}
@ViewChildren('noTrackBy') heroesNoTrackBy: QueryList<ElementRef>;
@ViewChildren('withTrackBy') heroesWithTrackBy: QueryList<ElementRef>;
actionName = 'Go for it';
alert = alerter;
badCurly = 'bad curly';
@ -42,20 +47,40 @@ export class AppComponent implements AfterViewInit, OnInit {
callPhone(value: string) {this.alert(`Calling ${value} ...`); }
canSave = true;
changeIds() {
this.resetHeroes();
this.heroes.forEach(h => h.id += 10 * this.heroIdIncrement++);
this.heroesWithTrackByCountReset = -1;
}
clearTrackByCounts() {
const trackByCountReset = this.heroesWithTrackByCountReset;
this.resetHeroes();
this.heroesNoTrackByCount = -1;
this.heroesWithTrackByCount = trackByCountReset;
this.heroIdIncrement = 1;
}
clicked = '';
clickMessage = '';
clickMessage2 = '';
Color = Color;
color = Color.Red;
colorToggle() {this.color = (this.color === Color.Red) ? Color.Blue : Color.Red; }
currentHero = Hero.MockHeroes[0];
currentHero: Hero;
deleteHero(hero: Hero) {
this.alert('Deleted hero: ' + (hero && hero.firstName));
this.alert(`Delete ${hero ? hero.name : 'the hero'}.`);
}
// #docregion evil-title
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
// #enddocregion evil-title
fontSizePx = 16;
title = 'Template Syntax';
getStyles(el: Element) {
@ -69,22 +94,26 @@ export class AppComponent implements AfterViewInit, OnInit {
getVal() { return this.val; }
hero: Hero; // defined to demonstrate template context precedence
heroes: Hero[];
// trackBy change counting
heroesNoTrackByCount = 0;
heroesWithTrackByCount = 0;
heroesWithTrackByCountReset = 0;
heroIdIncrement = 1;
// heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png';
// Public Domain terms of use: http://www.wpclipart.com/terms.html
heroImageUrl = 'images/hero.png';
// iconUrl = 'https://angular.io/resources/images/logos/standard/shield-large.png';
clicked = '';
clickMessage = '';
clickMessage2 = '';
iconUrl = 'images/ng-logo.png';
isActive = false;
isSpecial = true;
isUnchanged = true;
nullHero: Hero = null; // or undefined
nullHero: Hero = null;
onCancel(event: KeyboardEvent) {
let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).innerHTML : '';
@ -101,26 +130,20 @@ export class AppComponent implements AfterViewInit, OnInit {
this.alert('Saved.' + evtMsg);
}
onSubmit(form: NgForm) {
let evtMsg = form.valid ?
' Form value is ' + JSON.stringify(form.value) :
' Form is invalid';
this.alert('Form submitted.' + evtMsg);
}
onSubmit() { /* referenced but not used */}
product = {
name: 'frimfram',
price: 42
};
// #docregion refresh-heroes
// update this.heroes with fresh set of cloned heroes
refreshHeroes() {
this.heroes = Hero.MockHeroes.map(hero => Hero.clone(hero));
// updates with fresh set of cloned heroes
resetHeroes() {
this.heroes = Hero.heroes.map(hero => hero.clone());
this.currentHero = this.heroes[0];
this.heroesWithTrackByCountReset = 0;
}
// #enddocregion refresh-heroes
// #docregion same-as-it-ever-was
private samenessCount = 5;
moreOfTheSame() { this.samenessCount++; };
get sameAsItEverWas() {
@ -131,11 +154,9 @@ export class AppComponent implements AfterViewInit, OnInit {
// return {id:id, text: 'same as it ever was ...'};
// });
}
// #enddocregion same-as-it-ever-was
setUpperCaseFirstName(firstName: string) {
// console.log(firstName);
this.currentHero.firstName = firstName.toUpperCase();
setUppercaseName(name: string) {
this.currentHero.name = name.toUpperCase();
}
// #docregion setClasses
@ -162,69 +183,31 @@ export class AppComponent implements AfterViewInit, OnInit {
}
// #enddocregion setStyles
toeChoice = '';
toeChooser(picker: HTMLFieldSetElement) {
let choices = picker.children;
for (let i = 0; i < choices.length; i++) {
let choice = <HTMLInputElement>choices[i];
if (choice.checked) {return this.toeChoice = choice.value; }
}
}
// #docregion trackByHeroes
trackByHeroes(index: number, hero: Hero) { return hero.id; }
trackByHeroes(index: number, hero: Hero): number { return hero.id; }
// #enddocregion trackByHeroes
// #docregion trackById
trackById(index: number, item: any): string { return item['id']; }
trackById(index: number, item: any): number { return item['id']; }
// #enddocregion trackById
val = 2;
// villainImageUrl = 'http://www.clker.com/cliparts/u/s/y/L/x/9/villain-man-hi.png'
// Public Domain terms of use http://www.clker.com/disclaimer.html
villainImageUrl = 'images/villain.png';
//////// Detect effects of NgForTrackBy ///////////////
@ViewChildren('noTrackBy') childrenNoTrackBy: QueryList<ElementRef>;
@ViewChildren('withTrackBy') childrenWithTrackBy: QueryList<ElementRef>;
private _oldNoTrackBy: HTMLElement[];
private _oldWithTrackBy: HTMLElement[];
heroesNoTrackByChangeCount = 0;
heroesWithTrackByChangeCount = 0;
private detectNgForTrackByEffects() {
this._oldNoTrackBy = toArray(this.childrenNoTrackBy);
this._oldWithTrackBy = toArray(this.childrenWithTrackBy);
this.childrenNoTrackBy.changes.subscribe((changes: any) => {
let newNoTrackBy = toArray(changes);
let isSame = this._oldNoTrackBy.every((v: any, i: number) => v === newNoTrackBy[i]);
if (!isSame) {
this._oldNoTrackBy = newNoTrackBy;
this.heroesNoTrackByChangeCount++;
}
});
this.childrenWithTrackBy.changes.subscribe((changes: any) => {
let newWithTrackBy = toArray(changes);
let isSame = this._oldWithTrackBy.every((v: any, i: number) => v === newWithTrackBy[i]);
if (!isSame) {
this._oldWithTrackBy = newWithTrackBy;
this.heroesWithTrackByChangeCount++;
}
});
}
///////////////////
}
// helper to convert viewChildren to an array of HTMLElements
function toArray(viewChildren: QueryList<ElementRef>) {
let result: HTMLElement[] = [];
let children = viewChildren.toArray()[0].nativeElement.children;
for (let i = 0; i < children.length; i++) { result.push(children[i]); }
return result;
// helper to track changes to viewChildren
function trackChanges(views: QueryList<ElementRef>, changed: () => void) {
let oldRefs = views.toArray();
views.changes.subscribe((changes: QueryList<ElementRef>) => {
const changedRefs = changes.toArray();
// Is every changed ElemRef the same as old and in the same position
const isSame = oldRefs.every((v, i) => v === changedRefs[i]);
if (!isSame) {
oldRefs = changedRefs;
// wait a tick because called after views are constructed
setTimeout(changed, 0);
}
});
}

View File

@ -1,18 +1,15 @@
// #docregion
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular
import { AppComponent } from './app.component';
/* Other imports */
@NgModule({
imports: [
BrowserModule,
FormsModule
FormsModule // <--- import into the NgModule
],
declarations: [
AppComponent
],
bootstrap: [ AppComponent ]
/* Other module metadata */
})
export class AppModule { }

View File

@ -1,11 +1,13 @@
import { NgModule } from '@angular/core';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { BigHeroDetailComponent, HeroDetailComponent } from './hero-detail.component';
import { ClickDirective, ClickDirective2 } from './click.directive';
import { SizerComponent } from './sizer.component';
import { HeroFormComponent } from './hero-form.component';
import { heroSwitchComponents } from './hero-switch.components';
import { SizerComponent } from './sizer.component';
@NgModule({
imports: [
@ -16,6 +18,8 @@ import { SizerComponent } from './sizer.component';
AppComponent,
BigHeroDetailComponent,
HeroDetailComponent,
HeroFormComponent,
heroSwitchComponents,
ClickDirective,
ClickDirective2,
SizerComponent

View File

@ -18,7 +18,7 @@ import { Hero } from './hero';
<div>
<img src="{{heroImageUrl}}">
<span [style.text-decoration]="lineThrough">
{{prefix}} {{hero?.fullName}}
{{prefix}} {{hero?.name}}
</span>
<button (click)="delete()">Delete</button>
</div>`
@ -27,7 +27,7 @@ import { Hero } from './hero';
})
// #enddocregion input-output-2
export class HeroDetailComponent {
hero: Hero = new Hero('', 'Zzzzzzzz'); // default sleeping hero
hero: Hero = new Hero(-1, '', 'Zzzzzzzz'); // default sleeping hero
// heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png';
// Public Domain terms of use: http://www.wpclipart.com/terms.html
heroImageUrl = 'images/hero.png';
@ -50,18 +50,22 @@ export class HeroDetailComponent {
@Component({
selector: 'big-hero-detail',
template: `
<div style="border: 1px solid black; padding:3px">
<img src="{{heroImageUrl}}" style="float:left; margin-right:8px;">
<div><b>{{hero?.fullName}}</b></div>
<div>First: {{hero?.firstName}}</div>
<div>Last: {{hero?.lastName}}</div>
<div class="detail">
<img src="{{heroImageUrl}}">
<div><b>{{hero?.name}}</b></div>
<div>Name: {{hero?.name}}</div>
<div>Emotion: {{hero?.emotion}}</div>
<div>Birthdate: {{hero?.birthdate | date:'longDate'}}</div>
<div>Web: <a href="{{hero?.url}}" target="_blank">{{hero?.url}}</a></div>
<div>Rate/hr: {{hero?.rate | currency:'EUR'}}</div>
<br clear="all">
<button (click)="delete()">Delete</button>
</div>
`
`,
styles: [`
.detail { border: 1px solid black; padding: 4px; max-width: 450px; }
img { float: left; margin-right: 8px; height: 100px; }
`]
})
export class BigHeroDetailComponent extends HeroDetailComponent {

View File

@ -0,0 +1,16 @@
<div id=heroForm>
<!-- #docregion -->
<form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
<div class="form-group">
<label for="name">Name
<input class="form-control" name="name" required [(ngModel)]="hero.name">
</label>
</div>
<button type="submit" [disabled]="!heroForm.form.valid">Submit</button>
</form>
<div [hidden]="!heroForm.form.valid">
{{submitMessage}}
<div>
<!-- #enddocregion -->
</div>

View File

@ -0,0 +1,31 @@
import { Component, Input, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Hero } from './hero';
@Component({
moduleId: module.id,
selector: 'hero-form',
templateUrl: './hero-form.component.html',
styles: [`
button { margin: 6px 0; }
#heroForm { border: 1px solid black; margin: 20px 0; padding: 8px; max-width: 350px; }
`]
})
export class HeroFormComponent {
@Input() hero: Hero;
@ViewChild('heroForm') form: NgForm;
private _submitMessage = '';
get submitMessage() {
if (!this.form.valid) {
this._submitMessage = '';
}
return this._submitMessage;
}
onSubmit(form: NgForm) {
this._submitMessage = 'Submitted. form value is ' + JSON.stringify(form.value);
}
}

View File

@ -0,0 +1,42 @@
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'happy-hero',
template: `Wow. You like {{hero.name}}. What a happy hero ... just like you.`
})
export class HappyHeroComponent {
@Input() hero: Hero;
}
@Component({
selector: 'sad-hero',
template: `You like {{hero.name}}? Such a sad hero. Are you sad too?`
})
export class SadHeroComponent {
@Input() hero: Hero;
}
@Component({
selector: 'confused-hero',
template: `Are you as confused as {{hero.name}}?`
})
export class ConfusedHeroComponent {
@Input() hero: Hero;
}
@Component({
selector: 'unknown-hero',
template: `{{message}}`
})
export class UnknownHeroComponent {
@Input() hero: Hero;
get message() {
return this.hero && this.hero.name ?
`${this.hero.name} is strange and mysterious.` :
'Are you feeling indecisive?';
}
}
export const heroSwitchComponents =
[ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ];

View File

@ -1,36 +1,33 @@
export class Hero {
static nextId = 1;
static MockHeroes = [
static heroes: Hero[] = [
new Hero(
325,
'Hercules',
'Son of Zeus',
'happy',
new Date(1970, 1, 25),
'http://www.imdb.com/title/tt0065832/',
325),
new Hero('eenie', 'toe'),
new Hero('Meanie', 'Toe'),
new Hero('Miny', 'Toe'),
new Hero('Moe', 'Toe')
'http://www.imdb.com/title/tt0065832/'
),
new Hero(1, 'Mr. Nice', 'happy'),
new Hero(2, 'Narco', 'sad' ),
new Hero(3, 'Windstorm', 'confused' ),
new Hero(4, 'Magneta')
];
public id: number;
static clone({firstName, lastName, birthdate, url, rate, id}: Hero) {
return new Hero(firstName, lastName, birthdate, url, rate, id);
}
constructor(
public firstName: string,
public lastName?: string,
public id?: number,
public name?: string,
public emotion?: string,
public birthdate?: Date,
public url?: string,
public rate = 100,
id?: number) {
this.id = id != null ? id : Hero.nextId++;
) {
this.id = id ? id : Hero.nextId++;
}
get fullName() { return `${this.firstName} ${this.lastName}`; }
clone(): Hero {
return Object.assign(new Hero(), this);
}
}

View File

@ -6,7 +6,6 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="template-syntax.css">
<!-- Polyfills -->
<script src="node_modules/core-js/client/shim.min.js"></script>

View File

@ -191,7 +191,7 @@ function heroModuleSetup() {
}));
// #docregion title-case-pipe
it('should convert hero name to Title Case', fakeAsync(() => {
it('should convert hero name to Title Case', () => {
const inputName = 'quick BROWN fox';
const titleCaseName = 'Quick Brown Fox';
@ -205,7 +205,7 @@ function heroModuleSetup() {
fixture.detectChanges();
expect(page.nameDisplay.textContent).toBe(titleCaseName);
}));
});
// #enddocregion title-case-pipe
// #enddocregion selected-tests
// #docregion route-good-id

View File

@ -54,8 +54,10 @@ a#aot
:marked
In practice, a synonym for [Decoration](#decorator).
a#attribute-directive
a#attribute-directives
:marked
## Attribute directive
## Attribute directives
.l-sub-section
:marked
A category of [directive](#directive) that can listen to and modify the behavior of
@ -64,6 +66,8 @@ a#aot
A good example of an attribute directive is the `ngClass` directive for adding and removing CSS class names.
Learn about them in the [_Attribute Directives_](!{docsLatest}/guide/attribute-directives.html) guide.
.l-main-section#B
+ifDocsFor('ts|js')
@ -141,6 +145,7 @@ a#aot
This form is also known as **lower camel case**, to distinguish it from **upper camel case**, which is [PascalCase](#pascalcase).
When you see "camelCase" in this documentation it always means *lower camel case*.
a#component
:marked
## Component
.l-sub-section
@ -245,7 +250,7 @@ a#aot
that "B" is a dependency of "A".
You can ask a "dependency injection system" to create "A"
for us and handle all the dependencies.
and it will handle all of "A"s dependencies.
If "A" needs "B" and "B" needs "C", the system resolves that chain of dependencies
and returns a fully prepared instance of "A".
@ -276,9 +281,12 @@ a#aot
Registering providers is a critical preparatory step.
Angular registers some of its own providers with every injector.
We can register our own providers.
You can register your own providers.
Read more in the [Dependency Injection](!{docsLatest}/guide/dependency-injection.html) page.
a#directive
a#directives
:marked
## Directive
.l-sub-section
@ -286,8 +294,7 @@ a#aot
An Angular class responsible for creating, reshaping, and interacting with HTML elements
in the browser DOM. Directives are Angular's most fundamental feature.
A Directive is almost always associated with an HTML element or attribute.
We often refer to such an element or attribute as the directive itself.
A directive is almost always associated with an HTML element or attribute.
When Angular finds a directive in an HTML template,
it creates the matching directive class instance
and gives the instance control over that portion of the browser DOM.
@ -297,20 +304,19 @@ a#aot
as if you were writing native HTML. In this way, directives become extensions of
HTML itself.
Directives fall into one of three categories:
Directives fall into three categories:
1. [Components](#component) that combine application logic with an HTML template to
render application [views]. Components are usually represented as HTML elements.
render application [views](#view). Components are usually represented as HTML elements.
They are the building blocks of an Angular application and the
developer can expect to write a lot of them.
1. [Attribute directives](#attribute-directive) that can listen to and modify the behavior of
other HTML elements, attributes, properties, and components. They are usually represented
HTML elements, components, and other directives. They are usually represented
as HTML attributes, hence the name.
1. [Structural directives](#structural-directive), a directive responsible for
shaping or reshaping HTML layout, typically by adding, removing, or manipulating
elements and their children.
1. [Structural directives](#structural-directive) that
shape or reshape HTML layout, typically by adding and removing elements in the DOM.
.l-main-section#E
@ -630,21 +636,23 @@ a#snake-case
Applications often require services such as a hero data service or a logging service.
A service is a class with a focused purpose.
We often create a service to implement features that are
You often create a service to implement features that are
independent from any specific view,
provide shared data or logic across components, or encapsulate external interactions.
For more information, see the [Services](!{docsLatest}/tutorial/toh-pt4.html) page of the [Tour of Heroes](!{docsLatest}/tutorial/) tutorial.
a#structural-directive
a#structural-directives
:marked
## Structural directive
## Structural directives
.l-sub-section
:marked
A category of [directive](#directive) that can
shape or reshape HTML layout, typically by adding, removing, or manipulating
elements and their children; for example, the `ngIf` "conditional element" directive and the `ngFor` "repeater" directive.
shape or reshape HTML layout, typically by adding and removing elements in the DOM.
The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive are well-known examples.
Read more in the [Structural Directives](!{docsLatest}/guide/structural-directives.html) page.
Read more in the [_Structural Directives_](!{docsLatest}/guide/structural-directives.html) guide.
.l-main-section#T
:marked
@ -699,8 +707,8 @@ a#snake-case
A version of JavaScript that supports most [ECMAScript 2015](#es2015)
language features such as [decorators](#decorator).
TypeScript is also noteable for its optional typing system, which gives
us compile-time type checking and strong tooling support (for example, "intellisense",
TypeScript is also noteable for its optional typing system, which enables
compile-time type checking and strong tooling support (for example, "intellisense",
code completion, refactoring, and intelligent search). Many code editors
and IDEs support TypeScript either natively or with plugins.

View File

@ -24,17 +24,20 @@ a#directive-overview
## Directives overview
There are three kinds of directives in Angular:
1. Components&mdash;directives with a template.
1. Structural directives&mdash;change the DOM layout by adding and removing DOM elements.
1. Attribute directives&mdash;change the appearance or behavior of an element.
1. Components &mdash; directives with a template.
1. Structural directives &mdash; change the DOM layout by adding and removing DOM elements.
1. Attribute directives &mdash; change the appearance or behavior of an element, component, or another directive.
*Components* are the most common of the three directives.
You saw a component for the first time in the [QuickStart](../quickstart.html) example.
*Structural Directives* change the structure of the view. Two examples are [NgFor](template-syntax.html#ngFor) and [NgIf](template-syntax.html#ngIf)
in the [Template Syntax](template-syntax.html) page.
*Structural Directives* change the structure of the view.
Two examples are [NgFor](template-syntax.html#ngFor) and [NgIf](template-syntax.html#ngIf).
Learn about them in the [Structural Directives](structural-directives.html) guide.
*Attribute directives* are used as attributes of elements. The built-in [NgStyle](template-syntax.html#ngStyle) directive in the [Template Syntax](template-syntax.html) page, for example,
*Attribute directives* are used as attributes of elements.
The built-in [NgStyle](template-syntax.html#ngStyle) directive in the [Template Syntax](template-syntax.html) guide, for example,
can change several element styles at the same time.
.l-main-section

View File

@ -5,6 +5,12 @@ block includes
The Angular documentation is a living document with continuous improvements.
This log calls attention to recent significant changes.
## Template Syntax/Structural Directives: refreshed (2017-02-06)
The [_Template-Syntax_](template-syntax.html) and [_Structural Directives_](structural-directives.html)
guides were significantly revised for clarity, accuracy, and current recommended practices.
Discusses `<ng-container>`.
Revised samples are more clear and cover all topics discussed.
## NEW: Samples re-structured with `src/` folder (2017-02-02)
All documentation samples have been realigned with the default folder structure of the angular-cli.
That's a step along the road to basing our sample in the angular-cli.
@ -39,7 +45,7 @@ block includes
## Hierarchical Dependency Injection: refreshed (2017-01-13)
[Hierarchical Dependency Injection](hierarchical-dependency-injection.html) guide significantly revised.
Closes issue #3086
Revised samples are more clear and cover all topics discussed
Revised samples are more clear and cover all topics discussed.
## Miscellaneous (2017-01-05)
* [Setup](setup.html) guide:

View File

@ -0,0 +1,218 @@
block includes
include ../_util-fns
// The docs standard h4 style uppercases, making code terms unreadable. Override it.
style.
h4 {font-size: 17px !important; text-transform: none !important;}
.syntax { font-family: Consolas, 'Lucida Sans', Courier, sans-serif; color: black; font-size: 85%; }
:marked
This guide has been withdrawn.
The essential information about this feature
is in the [Structural Directives](structural-directives.html#ngcontainer) guide.
The original draft has been retained for possible future use.
//
:marked
The `<ng-container>` tags are part of Angular template syntax.
They help you group HTML template content under a phantom _root_ element
that you can manipulate with [structural directives](#structural-directives.html).
This guide explains how `<ng-container>` solves certain layout problems.
### Table of contents
- [Introduction](#introduction)
- [The problem](#problem)
- [Troublesome CSS](#CSS)
- [Restrictive layout](#restrictive-layout)
- [&lt;ng-container&gt; is excluded from DOM](#excluded)
- [&lt;ng-container&gt; is not &lt;ng-content&gt;](#ng-content)
- [Summary](#summary)
Try the <live-example></live-example>.
a#introduction
.l-main-section
:marked
## Introduction
Structural directives are responsible for HTML layout.
They shape or reshape the DOM's _structure_, typically by adding and removing
other elements and their children.
Two of the common, built-in structural directives are
[NgIf](template-syntax.html#ngIf) and [NgFor](template-syntax.html#ngFor).
You apply these directives to elements that Angular should add and remove.
Usually there's _one_ obvious element to receive the directive.
Sometimes you need to add or remove a _group of sibling elements_.
You want to apply the directive to the group, not just one of them.
As this guide explains, you can wrap the elements in an `<ng-container>` and apply the directive to the `<ng-container>`.
The `<ng-container>` is Angular template syntax for grouping elements,
like the curly braces that group statements in a JavaScript `if` block:
code-example(language="javascript").
if (someCondition) {
statement1;
statement2;
statement3;
}
:marked
Without those braces JavaScript could only execute the first statement1
when you intend to conditionally execute all (or none) of the statements as a single block.
The `<ng-container>` satisfies a similar need in Angular templates.
The `<ng-container>` _is not_ a directive.
It's not a class or interface or anything you could find in the [API guide](../api "API guide").
It's just grouping syntax recognized by the Angular parser.
*Why bother?* Why not wrap the group in standard HTML container element
such as `<span>` or `<div>`?
The rest of this guide answers these questions after stepping back and reframing the problem in a bit more detail.
a#problem
.l-main-section
:marked
## The problem
There's often a _root_ element that can and should host the structural directive.
In the following example, the table row (`<tr>`) should appear only when showing structural directives (when `strucDirs` is `true`).
+makeExample('ngcontainer/ts/src/app/app.component.html', 'ngif-tr')(format=".")
:marked
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.
+makeExample('ngcontainer/ts/src/app/app.component.html', 'ngif')(format=".")
:marked
Introducing another container element is usually harmless.
_Usually_ ... but not _always_.
a#css
:marked
### Troublesome CSS
CSS styles can be persnickety about HTML structure, making it difficult to introduce intermediate container elements.
Suppose you want to display a paragraph with a single sentence that describes the traits of a hero.
figure.image-display
img(src='/resources/images/devguide/ngcontainer/hero-traits-good.png' alt="hero traits")
:marked
For reasons unknown, you can't do the obvious thing and construct the text in a component property.
You must build the sentence in the template instead.
You try a combination of `<span>` wrappers, `*ngIf`, and `*ngFor` and write this.
+makeExample('ngcontainer/ts/src/app/app.component.html', 'ngif-span-2')(format=".")
:marked
Unfortunately, there's a style that kicks in when a `<p>`aragraph contains a `<span>`.
+makeExample('ngcontainer/ts/src/app/app.component.css', 'p-span')(format=".")
:marked
So the sentence renders the spanned text in tiny, red type like this.
figure.image-display
img(src='/resources/images/devguide/ngcontainer/hero-traits-bad.png' alt="hero traits (oops)")
:marked
You could try to fix the CSS ... if you have permission to do so.
The far easier approach is to use `<ng-container>` instead of `<span>` like this.
+makeExample('ngcontainer/ts/src/app/app.component.html', 'ngif-ngcontainer-2')(format=".")
a#excluded
:marked
### &lt;ng-container&gt; is excluded from the DOM
That works. There are no `<span>` tags to trigger the unwanted style.
Does Angular add an `<ng-container>` to the DOM?
If it did, you'd trip over a similar problem someday, because the introduction of _any_
HTML for layout purposes is a potential hazard.
Fortunately, Angular _replaces_ `<ng-container>` _with comments_, which have no effect on styles or layout.
Inspect the rendered HTML in the browser tools.
You'll see many comments like this:
code-example(language="html" escape="html").
&lt;!--template bindings={
"ng-reflect-ng-if": "true"
}--&gt;
&lt;!--template bindings={} --&gt;
:marked
You won't find `<ng-container>`.
That's especially important when applying structural directives
to the children of certain troublesome HTML elements.
a#restrictive-layout
:marked
### &lt;ng-container&gt; and restrictive layout
Some HTML elements are picky about their children.
For example, all children of a `<select>` tag must be `<option>` elements.
You can't wrap a set of _options_ in an HTML container element and
conditionally include or exclude them.
The following example tries to use `<scan>` and fails.
Most browsers silently ignore the nested default trait options.
+makeExample('ngcontainer/ts/src/app/app.component.html', 'select-span-2')(format=".")
:marked
Use `<ng-container>` instead and `showDefaultTraits` has the intended effect.
+makeExample('ngcontainer/ts/src/app/app.component.html', 'select-ngcontainer-2')(format=".")
:marked
The <live-example></live-example> demonstrates this _options_ use case and a similar challenge with
conditional inclusion of `<table>` rows.
a#ng-content
:marked
### &lt;ng-container&gt; is not &lt;ng-content&gt;
**Do not confuse `<ng-container>` with `ng-content`.**
The `ng-content` element is an _anchor_ tag that identifies a location
where _projected_ content should go.
+makeExample('ngcontainer/ts/src/app/content.component.ts', 'template', 'content.component.ts (template)')(format=".")
:marked
You use the component as follows.
+makeExample('ngcontainer/ts/src/app/app.component.html', 'content-comp')(format=".")
:marked
Angular _projects_ the "Projected content" into the space marked by the `<ng-content>`.
Unlike `<ng-container>`, it is illegal to put anything between the `ng-content` tags.
+makeExample('ngcontainer/ts/src/app/app.component.html', 'ngcontent-bad')(format=".")
:marked
Whereas, placing content between `<ng-container>` is expected.
Of course you're supposed to apply a structural directive as well.
The following is perfectly legal, albeit pointless.
+makeExample('ngcontainer/ts/src/app/app.component.html', 'ngcontainer-bare')(format=".")
a#summary
.l-main-section
:marked
## Summary
You can both try and download the source code for this guide in the <live-example></live-example>.
Here is a subset of the source, all from the `app/` folder.
+makeTabs(`
ngcontainer/ts/src/app/app.component.ts,
ngcontainer/ts/src/app/app.component.html,
ngcontainer/ts/src/app/app.component.css,
ngcontainer/ts/src/app/hero.ts
`,
null,`
app.component.ts,
app.component.html,
app.component.css,
hero.ts
`)

View File

@ -1,336 +1,582 @@
block includes
include ../_util-fns
:marked
One of the defining features of a single page application is its manipulation
of the DOM tree. Instead of serving a whole new page every time a user
navigates, whole sections of the DOM appear and disappear according
to the application state. In this chapter we'll look at how Angular
manipulates the DOM and how we can do it ourselves in our own directives.
// The docs standard h4 style uppercases, making code terms unreadable. Override it.
style.
h4 {font-size: 17px !important; text-transform: none !important;}
.syntax { font-family: Consolas, 'Lucida Sans', Courier, sans-serif; color: black; font-size: 85%; }
In this chapter we will
- [learn what structural directives are](#definition)
- [study *ngIf*](#ngIf)
- [discover the &lt;template> element](#template)
- [understand the asterisk (\*) in **ngFor*](#asterisk)
- [write our own structural directive](#unless)
:marked
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.
### Table of contents
- [What are structural directives?](#definition)
- [*NgIf* case study](#ngIf)
- [Group sibling elements with &lt;ng-container&gt;](#ng-container)
- [The asterisk (\*) prefix](#asterisk)
- [Inside *NgFor*](#ngfor)
- [microsyntax](#microsyntax)
- [template input variables](#template-input-variable)
- [one structural directive per element](#one-per-element)
- [Inside the *NgSwitch* directives](#ngSwitch)
- [Prefer the (\*) prefix](#prefer-asterisk)
- [The &lt;template> element](#template)
- [Write a structural directive](#unless)
Try the <live-example></live-example>.
<a id="definition"></a>
a#definition
.l-main-section
:marked
## What are structural directives?
There are three kinds of Angular directives:
1. Components
1. Attribute directives
1. Structural directives
Structural directives are responsible for HTML layout.
They shape or reshape the DOM's _structure_, typically by adding, removing, or manipulating
elements.
The *Component* is really a directive with a template.
It's the most common of the three directives and we write lots of them as we build our application.
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.
The [*Attribute* directive](attribute-directives.html) changes the appearance or behavior of an element.
The built-in [NgStyle](template-syntax.html#ngStyle) directive, for example,
can change several element styles at the same time.
We can use it to render text bold, italic, and lime green by binding to a
component property that requests such a sickening result.
Structural directives are easy to recognize.
An asterisk (\*) precedes the directive attribute name as in this example.
+makeExample('structural-directives/ts/src/app/app.component.html', 'ngif')(format=".")
:marked
No brackets. No parentheses. Just `*ngIf` set to a string.
A *Structural* directive changes the DOM layout by adding and removing DOM elements.
We've seen three of the built-in structural directives in other chapters: [ngIf](template-syntax.html#ngIf),
[ngSwitch](template-syntax.html#ngSwitch) and [ngFor](template-syntax.html#ngFor).
You'll learn in this guide that the [asterisk (\*) is a convenience notation](#asterisk)
and the string is a [_microsyntax_](#microsyntax) rather than the usual [template expression](template-syntax.html#template-expressions).
Angular "de-sugars" this notation into a marked-up `<template>` that surrounds the
host element and its descendents.
Each structural directive does something different with that template.
+makeExample('structural-directives/ts/src/app/structural-directives.component.html', 'structural-directives')(format=".")
Three of the common, built-in structural directives&mdash;[NgIf](template-syntax.html#ngIf),
[NgFor](template-syntax.html#ngFor), and [NgSwitch...](template-syntax.html#ngSwitch)&mdash;are
described in the [_Template Syntax_](template-syntax.html) guide and seen in samples throughout the Angular documentation.
Here's an example of them in a template:
+makeExample('structural-directives/ts/src/app/app.component.html', 'built-in')(format=".")
:marked
This guide won't repeat how to _use_ them. But it does explain _how they work_
and how to [write your own](#unless) structural directive.
<a id="ngIf"></a>
.callout.is-helpful
header Directive spelling
:marked
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.
.l-sub-section
:marked
There are two other kinds of Angular directives, described extensively elsewhere: (1)&nbsp;components and (2)&nbsp;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](attribute-directives.html) changes the appearance or behavior
of an element, component, or another directive.
For example, the built-in [`NgStyle`](template-syntax.html#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](#one-per-element) _structural_ directive to a host element.
a#ngIf
.l-main-section
:marked
## NgIf Case Study
Let's focus on `ngIf`. It's a great example of a structural
directive: it takes a boolean and makes an entire chunk of DOM appear
or disappear.
`NgIf` is the simplest structural directive and the easiest to understand.
It takes a boolean value and makes an entire chunk of the DOM appear or disappear.
+makeExample('structural-directives/ts/src/app/structural-directives.component.html', 'ngIf')(format=".")
+makeExample('structural-directives/ts/src/app/app.component.html', 'ngif-true')(format=".")
:marked
The `ngIf` directive does not hide the element.
Using browser developer tools we can see that, when the condition is true, the top
paragraph is in the DOM and the bottom disused paragraph is completely
absent from the DOM! In its place are empty `<script>` tags.
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.
figure.image-display
img(src='/resources/images/devguide/structural-directives/element-not-in-dom.png' alt="element not in dom")
img(src='/resources/images/devguide/structural-directives/element-not-in-dom.png' alt="ngIf=false element not in DOM")
:marked
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](#asterisk)).
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*?
We could hide the unwanted paragraph by setting its css `display` style to `none`.
The element would remain in the DOM while invisible. Instead we removed it with `ngIf`.
The difference matters. When we hide an element,
the component's behavior continues.
It remains attached to its DOM element. It continues to listen to events.
Angular keeps checking for changes that could affect data bindings.
Whatever the component was doing, it keeps doing it.
A directive could hide the unwanted paragraph instead by setting its `display` style to `none`.
+makeExample('structural-directives/ts/src/app/app.component.html', 'display-none')(format=".")
Although invisible, the component &mdash; and all of its descendant components &mdash;
tie up resources that might be more useful elsewhere.
The performance and memory burden can be substantial and the user may not benefit at all.
On the positive side, showing the element again is very quick.
The component's previous state is preserved and ready to display.
The component doesn't re-initialize &mdash; an operation that could be expensive.
`ngIf` is different.
Setting `ngIf` to false **does** affect the component's resource consumption.
Angular removes the element from DOM, stops change detection for the associated component,
detaches it from DOM events (the attachments that it made) and destroys the component.
The component can be garbage-collected (we hope) and free up memory.
Components often have child components which themselves have children.
All of them are destroyed when `ngIf` destroys the common ancestor.
This cleanup effort is usually a good thing.
Of course it isn't *always* a good thing.
It might be a bad thing if we need that particular component again soon.
The component's state might be expensive to re-construct.
When `ngIf` becomes `true` again, Angular recreates the component and its subtree.
Angular runs every component's initialization logic again. That could be expensive ... as when
a component re-fetches data that had been in memory just moments ago.
.l-sub-section
:marked
*Design thought*: minimize initialization effort and consider caching state in a
companion service.
:marked
Although there are pros and cons to each approach,
in general it is best to use `ngIf` to remove unwanted components rather than
hide them.
While invisible, the element remains in the DOM.
figure.image-display
img(src='/resources/images/devguide/structural-directives/element-display-in-dom.png' alt="hidden element still in DOM")
:marked
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&mdash;and all of its descendant components&mdash;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&mdash;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.**
We should ask ourselves &mdash; and the users of our directives &mdash; to think carefully
about the consequences of adding and removing elements and of creating and destroying components.
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.
Let's see these dynamics at work. For fun, we'll stack the deck *against*
our recommendation and consider a component called `heavy-loader` that
***pretends*** to load a ton of data when initialized.
a#ngcontainer
a#ng-container
.l-main-section
:marked
## Group sibling elements with &lt;ng-container&gt;
We'll display two instances of the component. We toggle the visibility of the first one with CSS.
We toggle the second into and out of the DOM with `ngIf`.
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.
+makeTabs(
`structural-directives/ts/src/app/structural-directives.component.html,
structural-directives/ts/src/app/heavy-loader.component.ts`,
'message-log,',
'template (excerpt), heavy-loader.component.ts')
+makeExample('structural-directives/ts/src/app/app.component.html', 'ngfor-li')(format=".")
:marked
We also log when a component is created or destroyed
using the built-in `ngOnInit` and `ngOnDestroy` [lifecycle hooks](lifecycle-hooks.html).
Here it is in action:
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.
+makeExample('structural-directives/ts/src/app/app.component.html', 'ngif')(format=".")
:marked
Introducing another container element&mdash;typically a `<span>` or `<div>`&mdash;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.
+makeExample('structural-directives/ts/src/app/app.component.html', 'ngif-span')(format=".")
:marked
You also have a CSS style rule that happens to apply to a `<span>` within a `<p>`aragraph.
+makeExample('structural-directives/ts/src/app/app.component.css', 'p-span')(format=".")
:marked
The constructed paragraph renders strangely.
figure.image-display
img(src='/resources/images/devguide/structural-directives/heavy-loader-toggle.gif' alt="heavy loader toggle")
img(src='/resources/images/devguide/structural-directives/bad-paragraph.png' alt="spanned paragraph with bad style")
:marked
Both components are in the DOM at the start.
First we toggle the component's visibility repeatedly. The component never leaves the DOM.
When visible it's always the same instance and the log is quiet.
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>` tag requires `<option>` children.
You can't wrap the _options_ in a conditional `<div>` or a `<span>`.
Then we toggle the second component with `ngIf`.
We create a new instance every time and the log shows that we're paying
a heavy price to create and destroy it.
If we really expected to "wink" the component like this, toggling visibility would be the better choice.
In most UIs, when we "close" a component we're unlikely to see it again for a long time, if ever.
The `ngIf` would be preferred in that case.
<a id="template"></a>
.l-main-section
When you try this,
+makeExample('structural-directives/ts/src/app/app.component.html', 'select-span')(format=".")
:marked
## The *&lt;template>* tag
Structural directives, like `ngIf`, do their magic by using the
[HTML 5 template tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).
Outside of an Angular app, the `<template>` tag's default CSS `display` property is `none`.
It's contents are ***invisible*** within
a hidden [document fragment](https://developer.mozilla.org/en/docs/Web/API/DocumentFragment).
Inside of an app, Angular ***removes*** the`<template>` tags and their children.
The contents are gone &mdash; but not forgotten as we'll see soon.
We can confirm these effects by wrapping the middle "hip" of the phrase "Hip! Hip! Hooray!" within a `<template>` tag.
+makeExample('structural-directives/ts/src/app/structural-directives.component.html', 'template-tag')(format=".")
:marked
The display is a 'Hip! Hooray!', short of perfect enthusiasm. The DOM effects are different when Angular is in control.
the drop down is empty.
figure.image-display
img(src='/resources/images/devguide/structural-directives/template-in-out-of-a2.png' alt="template outside angular")
img(src='/resources/images/devguide/structural-directives/bad-select.png' alt="spanned options don't work")
:marked
The browser won't display an `<option>` within a `<span>`.
### &lt;ng-container&gt; to the rescue
The Angular `<ng-container>` 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 `<ng-container>`.
+makeExample('structural-directives/ts/src/app/app.component.html', 'ngif-ngcontainer')(format=".")
:marked
It renders properly.
figure.image-display
img(src='/resources/images/devguide/structural-directives/good-paragraph.png' alt="ngcontainer paragraph with proper style")
:marked
Now conditionally exclude a _select_ `<option>` with `<ng-container>`.
+makeExample('structural-directives/ts/src/app/app.component.html', 'select-ngcontainer')(format=".")
:marked
The drop down works properly.
figure.image-display
img(src='/resources/images/devguide/structural-directives/select-ngcontainer-anim.gif' alt="ngcontainer options work properly")
:marked
Evidently Angular replaces the `<template>` tag and its contents with empty `<script>` tags.
That's just its default behavior.
It can do something different as we saw when applying a variety of `ngSwitch` directives to `<template>` tags:
The `<ng-container>` 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 JavaScript `if`-block:
+makeExample('structural-directives/ts/src/app/structural-directives.component.html', 'ngSwitch')(format=".")
code-example(language="javascript").
if (someCondition) {
statement1;
statement2;
statement3;
}
:marked
When one of those `ngSwitch` conditions is true, Angular inserts the template's content into the DOM.
Without those braces JavaScript could only execute the first statement
when you intend to conditionally execute all of them as a single block.
The `<ng-container>` satisfies a similar need in Angular templates.
What does this have to do with `ngIf` and `ngFor`? We didn't use a `<template>` tag with those directives.
<a id="asterisk"></a>
a#asterisk
.l-main-section
:marked
## The asterisk (\*) effect
Here are those directives again. See the difference?
## 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.
+makeExample('structural-directives/ts/src/app/structural-directives.component.html', 'asterisk')(format=".")
+makeExample('structural-directives/ts/src/app/app.component.html', 'asterisk')(format=".")
:marked
We're prefixing these directive names with an asterisk (\*).
The asterisk is "syntactic sugar" for something a bit more complicated.
Internally, Angular "de-sugars" it in two stages.
First, it translates the `*ngIf="..."` into a template _attribute_, `template="ngIf ..."`,&nbsp; like this.
+makeExample('structural-directives/ts/src/app/app.component.html', 'ngif-template-attr')(format=".")
:marked
Then it translates the template _attribute_ into a template _element_, wrapped around the host element, like this.
+makeExample('structural-directives/ts/src/app/app.component.html', 'ngif-template')(format=".")
The asterisk is "syntactic sugar". It simplifies `ngIf` and `ngFor` for both the writer and the reader.
Under the hood, Angular replaces the asterisk version with a more verbose `<template>` form.
The next two `ngIf` examples are effectively the same and we may write in either style:
+makeExample('structural-directives/ts/src/app/structural-directives.component.html', 'ngIf-template')(format=".")
// We should discourage writing <template> syntax in favor of <ng-container>
block remember-the-brackets
.alert.is-critical
:marked
Write `[ngIf]="hero"`, not `ngIf="hero"`!
The latter incorrectly assigns the *string* `"hero"` to `ngIf`.
A non-empty string is _truthy_, so `ngIf` is always `true` and Angular
always tries to show the content … even when there is no `hero`.
:marked
Most of us would rather write in style (A).
* The `*ngIf` directive moved to the `<template>` tag where it became a property binding,`[ngIf]`.
* The rest of the `<div>`, including its class attribute, moved inside the `<template>` tag.
None of these forms are actually rendered.
Only the finished product ends up in the DOM.
figure.image-display
img(src='/resources/images/devguide/structural-directives/hero-div-in-dom.png' alt="hero div in DOM")
It's worth knowing that Angular expands style (A) into style (B).
It moves the paragraph and its contents inside a `<template>` tag.
It moves the directive up to the `<template>` tag where it becomes a property binding,
surrounded in square brackets. The boolean value of the host component's `condition` property
determines whether the templated content is displayed or not.
Angular transforms `*ngFor` in a similar manner:
+makeExample('structural-directives/ts/src/app/structural-directives.component.html', 'ngFor-template')(format=".")
:marked
The basic pattern is the same:&nbsp; create a `<template>`, relocate the content,
and move the directive onto the `<template>`.
Angular consumed the `<template>` content during its actual rendering and
replaced the `<template>` with a diagnostic comment.
There are extra nuances stemming from
Angular's [ngFor micro-syntax](template-syntax.html#ngForMicrosyntax) which expands
into an additional `ngForOf` property binding (the iterable) and
the `hero` template input variable (the current item in each iteration).
<a id="unless"></a>
The [`NgFor`](#ngfor) and [`NgSwitch...`](#ngswitch) directives follow the same pattern.
a#ngfor
.l-main-section
:marked
## Make a structural directive
Let's write our own structural directive, an `Unless` directive, the not-so-evil twin of `ngIf`.
## Inside _*ngFor_
Unlike `ngIf` which displays the template content when `true`,
our directive displays the content when the condition is ***false***.
Angular transforms the `*ngFor` in similar fashion from asterisk (\*) syntax through
template _attribute_ to template _element_.
Here's a full-featured application of `NgFor`, written all three ways:
+makeExample('structural-directives/ts/src/app/app.component.html', 'inside-ngfor')(format=".")
:marked
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](microsyntax).
.alert.is-helpful
:marked
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>`.
a#microsyntax
:marked
### 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_](#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](../api/common/index/NgFor-directive.html "API: NgFor")
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.
a#template-input-variable
a#template-input-variables
:marked
### 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`, `li`, and `odd`.
All are preceded by the keyword `let`.
A _template input variable_ is **_not_** the same as a
[template _reference_ variable](template-syntax.html#ref-vars),
neither _semantically_ nor _syntactically_.
You declare a template _input_ variable declaration with 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 declaration 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`.
a#one-per-element
:marked
### One structural directive per host element
Someday you'll want to 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 [`ng-container`](#ngcontainer) so you don't have to introduce extra levels of HTML.
a#ngswitch
.l-main-section
:marked
## Inside the _NgSwitch_ directives
The Angular _NgSwitch_ is actually a set of cooperating directives: `NgSwitch`, `NgSwitchCase`, and `NgSwitchDefault`.
Here's an example.
+makeExample('structural-directives/ts/src/app/app.component.html', 'ngswitch')(format=".")
:marked
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.
.l-sub-section
:marked
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`.
:marked
As with other structural directives, the `NgSwitchCase` and `NgSwitchDefault`
can be "de-sugared" into the template _attribute_ form.
+makeExample('structural-directives/ts/src/app/app.component.html', 'ngswitch-template-attr')(format=".")
:marked
That, in turn, can be "de-sugared" into the `<template>` element form.
+makeExample('structural-directives/ts/src/app/app.component.html', 'ngswitch-template')(format=".")
a#prefer-asterisk
:marked
## Prefer the asterisk (\*) syntax.
The asterisk (\*) syntax is more clear than the other "de-sugared" forms.
Use [&lt;ng-container&gt;](#ng-container) when there's no single element
to host the directive.
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](#unless).
a#template
.l-main-section
:marked
## The *&lt;template&gt;*
The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template" target="_blank" title="MDN: Template Tag">HTML 5 &lt;template&gt;</a>
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, if you merely wrap some elements in a `<template>` and do nothing with it,
those elements disappear.
That's the fate of the middle "hip" in the phrase "Hip! Hip! Hooray!".
+makeExample('structural-directives/ts/src/app/app.component.html', 'template-tag')(format=".")
:marked
Angular erases the middle "hip", leaving the cheer a bit less enthusiastic.
figure.image-display
img(src='/resources/images/devguide/structural-directives/template-rendering.png' width="350" alt="template tag rendering")
:marked
A structural directive puts a `<template>` to work
as you'll see when you write your own structural directive.
a#unless
.l-main-section
:marked
## Write a structural directive
In this section, you write a `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***.
+makeExample('structural-directives/ts/src/app/app.component.html', 'myUnless-1')(format=".")
:marked
block unless-intro
:marked
Creating a directive is similar to creating a component.
* import the `Directive` decorator.
* add a CSS **attribute selector** (in brackets) that identifies our directive.
* Import the `Directive` decorator (instead of the `Component` decorator).
* specify the name of the public `input` property for binding
(typically the name of the directive itself).
* Import the `Input`, `TemplateRef`, and `ViewContainerRef` symbols; you'll them need for _any_ structural directive.
* apply the decorator to our implementation class.
* Apply the decorator to to the directive class.
Here is how we begin:
* Set the CSS *attribute selector* that identifies the directive when applied to an element in a template.
+makeExample('structural-directives/ts/src/app/unless.directive.ts', 'unless-declaration', 'unless.directive.ts (excerpt)')(format=".")
Here's how you might begin:
+makeExample('structural-directives/ts/src/app/unless.directive.ts', 'skeleton', 'unless.directive.ts (skeleton)')(format=".")
:marked
The directive's _selector_ is typically the directive's **attribute name** in square brackets.`[myUnless]`.
The brackets define a CSS
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors" target="_blank" title="MDN: Attribute selectors">attribute selector</a>.
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`.
:marked
The directive _class_ name ends in `Directive` per the [style guide](style-guide.html#02-03 "Angular Style Guide").
Angular's own directives do not.
### _TemplateRef_ and _ViewContainerRef_
A simple structural directive like this one creates an
[_embedded view_](../api/core/index/EmbeddedViewRef-class.html "API: EmbeddedViewRef")
from the Angular-generated `<template>` and inserts that view in a
[_view container_](../api/core/index/ViewContainerRef-class.html "API: ViewContainerRef")
adjacent to the directive's original `<p>` host element.
You'll acquire the `<template>` contents with a
[`TemplateRef`](../api/core/index/TemplateRef-class.html "API: TemplateRef")
and access the _view container_ through a
[`ViewContainerRef`](../api/core/index/ViewContainerRef-class.html "API: ViewContainerRef").
You inject both in the directive constructor as private variables of the class.
+makeExample('structural-directives/ts/src/app/unless.directive.ts', 'ctor')(format=".")
:marked
### 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`
.l-sub-section
:marked
### Selector brackets [&nbsp;]
The CSS syntax for selecting an attribute is a name in square brackets.
We surround our directive name in square brackets. See *Directive configuration* on the
[cheatsheet](cheatsheet.html).
Read about `@Input` in the [_Template Syntax_](template-syntax.html#inputs-outputs) guide.
### Selector name prefixes
We recommend picking a selector name with a prefix to ensure
that it cannot conflict with any standard HTML attribute, now or in the future.
We do **not** prefix our `unless` directive name with **`ng`**.
That prefix belongs to Angular and
we don't want to confuse our directives with their directives.
Our prefix is `my`.
+makeExample('structural-directives/ts/src/app/unless.directive.ts', 'set')(format=".")
:marked
We'll need access to the template *and* something that can render its contents.
We access the template with a `TemplateRef`. The renderer is a `ViewContainerRef`.
We inject both into our constructor as private variables.
Angular sets the `myUnless` property whenever the value of the condition changes.
Because the `myUnless` property does work, it needs a setter.
+makeExample('structural-directives/ts/src/app/unless.directive.ts', 'unless-constructor')(format=".")
* If the condition is falsy and the view hasn't been created previously,
tell the _view container_ to create the _embedded view_ from the template.
* If the condition is truthy 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:
+makeExample('structural-directives/ts/src/app/unless.directive.ts', 'no-docs', 'unless.directive.ts')
:marked
The consumer of our directive will bind a boolean value to our directive's `myUnless` input property.
The directive adds or removes the template based on that value.
Add this directive to the `!{_declsVsDirectives}` !{_array} of the !{_AppModuleVsAppComp}.
Let's add the `myUnless` property now as a setter-only property.
+makeExample('structural-directives/ts/src/app/unless.directive.ts', 'unless-set')(format=".")
.l-sub-section
:marked
The `@Input()` annotation marks this property as an input for the directive.
Then create some HTML to try it.
+makeExample('structural-directives/ts/src/app/app.component.html', 'myUnless')(format=".")
:marked
Nothing fancy here: if the condition is false,
we render the template, otherwise we clear the element content.
The end result should look like this:
+makeExample('structural-directives/ts/src/app/unless.directive.ts', null, 'unless.directive.ts')
:marked
Now we add it to the `!{_declsVsDirectives}` !{_array} of the !{_AppModuleVsAppComp} and try it.
First we add some test HTML to the template:
+makeExample('structural-directives/ts/src/app/structural-directives.component.html', 'myUnless')(format=".")
:marked
We run it and it behaves as expected, doing the opposite of `ngIf`.
When `condition` is `true`, the top paragraph is removed (replaced by `<script>` tags) and the bottom paragraph appears.
When the `condition` is falsy, the top (A) paragraph appears and the bottom (B) paragraph disappears.
When the `condition` is truthy, the top (A) paragraph is removed and the bottom (B) paragraph appears.
figure.image-display
img(src='/resources/images/devguide/structural-directives/myUnless-is-true.png' alt="myUnless is true" )
:marked
Our `myUnless` directive is dead simple. Surely we left something out.
Surely `ngIf` is more complex?
[Look at the source code](https://github.com/angular/angular/blob/master/modules/%40angular/common/src/directives/ng_if.ts).
It's well documented and we shouldn't be shy
about consulting the source when we want to know how something works.
`ngIf` isn't much different! There are a few
additional checks to improve performance (don't clear or recreate the
view unless necessary) but otherwise it's much the same.
img(src='/resources/images/devguide/structural-directives/unless-anim.gif' alt="UnlessDirective in action" )
a#summary
.l-main-section
:marked
## Wrap up
Here is the pertinent source for this chapter.
## Summary
You can both try and download the source code for this guide in the <live-example></live-example>.
Here is the source from the `app/` folder.
+makeTabs(`
structural-directives/ts/src/app/unless.directive.ts,
structural-directives/ts/src/app/heavy-loader.component.ts,
structural-directives/ts/src/app/structural-directives.component.ts,
structural-directives/ts/src/app/structural-directives.component.html
structural-directives/ts/src/app/app.component.ts,
structural-directives/ts/src/app/app.component.html,
structural-directives/ts/src/app/app.component.css,
structural-directives/ts/src/app/app.module.ts,
structural-directives/ts/src/app/hero.ts,
structural-directives/ts/src/app/hero-switch.components.ts,
structural-directives/ts/src/app/unless.directive.ts
`,
null,
`unless.directive.ts,
heavy-loader.component.ts,
structural-directives.component.ts,
structural-directives.component.html
`)
`app.component.ts,
app.component.html,
app.component.css,
app.module.ts,
hero.ts,
hero-switch.components.ts,
unless.directive.ts
`)
:marked
We learned that we can manipulate our HTML layout with
structural directives like `ngFor` and `ngIf` and we
wrote our own structural directive, `myUnless`, to do something similar.
Angular offers more sophisticated techniques for managing layout
such as *structural components* that can take external content
and incorporate that content within their own templates.
Tab and tab pane controls are good examples.
We'll learn about structural components in a future chapter.
You learned
* that structural directives manipulate HTML layout.
* to use [`<ng-container>`](#ngcontainer) as a grouping element when there is no suitable host element.
* that the angular "de-sugars" [asterisk (\*) syntax](#asterisk) into a `<template>`.
* how that works for the `NgIf`, `NgFor` and `NgSwitch` built-in directives.
* about the [_microsyntax_](#microsyntax) that expands into a [`<template>`](#template).
* to write a [custom structural directive](#unless), `UnlessDirective`.

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB