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

@ -4,65 +4,55 @@ import { browser, element, by } from 'protractor';
describe('Structural Directives', function () { describe('Structural Directives', function () {
// tests interact - so we need beforeEach instead of beforeAll beforeAll(function () {
beforeEach(function () {
browser.get(''); browser.get('');
}); });
it('should be able to use ngFor, ngIf and ngWhen together', function () { it('first div should show hero name with *ngIf', function () {
let allDivEles = element.all(by.css('structural-directives > div')); const allDivs = element.all(by.tagName('div'));
expect(allDivEles.get(0).getText()).toEqual('Mr. Nice'); expect(allDivs.get(0).getText()).toEqual('Mr. Nice');
expect(allDivEles.get(1).getText()).toEqual('Mr. Nice');
expect(allDivEles.get(4).getText()).toEqual('Ready');
}); });
it('should be able to toggle ngIf with a button', function () { it('first li should show hero name with *ngFor', function () {
let setConditionButtonEle = element.all(by.css('button')).get(0); const allLis = element.all(by.tagName('li'));
let conditionTrueEles = element.all(by.cssContainingText('p', 'condition is true')); expect(allLis.get(0).getText()).toEqual('Mr. Nice');
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'); it('ngSwitch have three <happy-hero> instances', function () {
setConditionButtonEle.click().then(function() { const happyHeroEls = element.all(by.tagName('happy-hero'));
expect(conditionTrueEles.count()).toBe(0, 'should be no condition true elements'); expect(happyHeroEls.count()).toEqual(3);
expect(conditionFalseEles.count()).toBe(2, 'should be two condition false elements'); });
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 () { it('should have only one "Hip!" (the other is erased)', function () {
let setConditionButtonEle = element.all(by.css('button')).get(0); const paragraph = element.all(by.cssContainingText('p', 'Hip!'));
let ngIfButtonEle = element(by.cssContainingText('button', 'if | !if')); expect(paragraph.count()).toEqual(1);
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 be able to use *ngIf ', function () { it('myUnless should show 3 paragraph (A)s and (B)s at the start', function () {
let setConditionButtonEle = element.all(by.css('button')).get(0); const paragraph = element.all(by.css('p.unless'));
let displayEles = element.all(by.cssContainingText('p', 'Our heroes are true!')); expect(paragraph.count()).toEqual(3);
expect(displayEles.count()).toBe(2, 'should be displaying two ngIf elements'); for (let i = 0; i < 3; i++) {
setConditionButtonEle.click().then(function() { expect(paragraph.get(i).getText()).toContain('(A)');
expect(displayEles.count()).toBe(0, 'should nog longer be displaying ngIf elements'); }
});
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", "description": "Structural directives",
"basePath": "src/", "basePath": "src/",
"files": ["!**/*.d.ts", "!**/*.js"], "files": [
"!**/*.d.ts",
"!**/*.js",
"!app/scrap.txt"
],
"tags": [ "tags": [
"structural", "directives", "template", "ngIf", "structural", "directives", "template", "ngIf",
"ngSwitch", "ngFor" "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 // #docregion
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { StructuralDirectivesComponent } from './structural-directives.component'; import { AppComponent } from './app.component';
import { heroSwitchComponents } from './hero-switch.components';
import { UnlessDirective } from './unless.directive'; import { UnlessDirective } from './unless.directive';
import { HeavyLoaderComponent } from './heavy-loader.component';
@NgModule({ @NgModule({
imports: [ BrowserModule ], imports: [ BrowserModule, FormsModule ],
declarations: [ declarations: [
StructuralDirectivesComponent, AppComponent,
UnlessDirective, heroSwitchComponents,
HeavyLoaderComponent UnlessDirective
], ],
bootstrap: [ StructuralDirectivesComponent ] bootstrap: [ AppComponent ]
}) })
export class AppModule { } 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 // #docplaster
// #docregion // #docregion
// #docregion unless-declaration // #docregion no-docs
import { Directive, Input } from '@angular/core'; // #docregion skeleton
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
// #enddocregion unless-declaration // #enddocregion skeleton
import { TemplateRef, ViewContainerRef } from '@angular/core'; /**
* Add the template content to the DOM unless the condition is true.
// #docregion unless-declaration // #enddocregion no-docs
@Directive({ selector: '[myUnless]' }) *
* 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 { export class UnlessDirective {
// #enddocregion unless-declaration // #enddocregion skeleton
private hasView = false;
// #docregion unless-constructor // #docregion ctor
constructor( constructor(
private templateRef: TemplateRef<any>, private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef private viewContainer: ViewContainerRef) { }
) { } // #enddocregion ctor
// #enddocregion unless-constructor
// #docregion unless-set // #docregion set
@Input() set myUnless(condition: boolean) { @Input() set myUnless(condition: boolean) {
if (!condition) { if (!condition && !this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef); this.viewContainer.createEmbeddedView(this.templateRef);
} else { this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear(); this.viewContainer.clear();
this.hasView = false;
} }
} }
// #enddocregion unless-set // #enddocregion set
// #docregion unless-declaration // #docregion skeleton
} }
// #enddocregion unless-declaration // #enddocregion skeleton
// #enddocregion no-docs
// #enddocregion // #enddocregion

View File

@ -21,7 +21,7 @@
</head> </head>
<body> <body>
<structural-directives>Loading...</structural-directives> <my-app>Loading...</my-apps>
</body> </body>
</html> </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> <a id="toc"></a>
<h1>Template Syntax</h1> <h1>Template Syntax</h1>
<a href="#interpolation">Interpolation</a><br> <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="#mental-model">Mental Model</a><br>
<a href="#buttons">Buttons</a><br> <a href="#buttons">Buttons</a><br>
<a href="#prop-vs-attrib">Properties vs. Attributes</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="#ngClass">NgClass Binding</a><br>
<a href="#ngStyle">NgStyle Binding</a><br> <a href="#ngStyle">NgStyle Binding</a><br>
<a href="#ngIf">NgIf</a><br> <a href="#ngIf">NgIf</a><br>
<a href="#ngSwitch">NgSwitch</a><br>
<a href="#ngFor">NgFor</a><br> <a href="#ngFor">NgFor</a><br>
<div style="margin-left:8px"> <div style="margin-left:8px">
<a href="#ngFor-index">NgFor with index</a><br> <a href="#ngFor-index">NgFor with index</a><br>
<a href="#ngFor-trackBy">NgFor with trackBy</a><br> <a href="#ngFor-trackBy">NgFor with trackBy</a><br>
</div> </div>
<a href="#ngSwitch">NgSwitch</a><br>
</div> </div>
<br> <br>
<a href="#star-prefix">* prefix and &lt;template&gt;</a><br>
<a href="#ref-vars">Template reference variables</a><br> <a href="#ref-vars">Template reference variables</a><br>
<a href="#inputs-and-outputs">Inputs and outputs</a><br> <a href="#inputs-and-outputs">Inputs and outputs</a><br>
<a href="#pipes">Pipes</a><br> <a href="#pipes">Pipes</a><br>
@ -41,7 +42,7 @@
<hr><h2 id="interpolation">Interpolation</h2> <hr><h2 id="interpolation">Interpolation</h2>
<!-- #docregion first-interpolation --> <!-- #docregion first-interpolation -->
<p>My current hero is {{currentHero.firstName}}</p> <p>My current hero is {{currentHero.name}}</p>
<!-- #enddocregion first-interpolation --> <!-- #enddocregion first-interpolation -->
<!-- #docregion title+image --> <!-- #docregion title+image -->
@ -63,6 +64,68 @@
<a class="to-toc" href="#toc">top</a> <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 --> <!-- New Mental Model -->
<hr><h2 id="mental-model">New Mental Model</h2> <hr><h2 id="mental-model">New Mental Model</h2>
@ -105,16 +168,17 @@
<!-- #docregion event-binding-syntax-1 --> <!-- #docregion event-binding-syntax-1 -->
<button (click) = "onSave()">Save</button> <button (click) = "onSave()">Save</button>
<hero-detail (deleteRequest)="deleteHero()"></hero-detail> <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 --> <!-- #enddocregion event-binding-syntax-1 -->
{{clicked}} {{clicked}}
<br><br> <br><br>
<div> <div>
Hero Name:
<!-- #docregion 2-way-binding-syntax-1 --> <!-- #docregion 2-way-binding-syntax-1 -->
<input [(ngModel)]="heroName"> <input [(ngModel)]="heroName">
<!-- #enddocregion 2-way-binding-syntax-1 --> <!-- #enddocregion 2-way-binding-syntax-1 -->
Hero Name: {{heroName}} {{heroName}}
</div> </div>
<br><br> <br><br>
@ -205,6 +269,10 @@ button</button>
<!-- #enddocregion property-binding-vs-interpolation --> <!-- #enddocregion property-binding-vs-interpolation -->
<!-- #docregion property-binding-vs-interpolation-sanitization --> <!-- #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>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p> <p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
<!-- #enddocregion property-binding-vs-interpolation-sanitization --> <!-- #enddocregion property-binding-vs-interpolation-sanitization -->
@ -307,7 +375,7 @@ button</button>
<!-- #docregion event-binding-3 --> <!-- #docregion event-binding-3 -->
<!-- `myClick` is an event on the custom `ClickDirective` --> <!-- `myClick` is an event on the custom `ClickDirective` -->
<!-- #docregion myClick --> <!-- #docregion myClick -->
<div (myClick)="clickMessage=$event">click with myClick</div> <div (myClick)="clickMessage=$event" clickable>click with myClick</div>
<!-- #enddocregion myClick --> <!-- #enddocregion myClick -->
<!-- #enddocregion event-binding-3 --> <!-- #enddocregion event-binding-3 -->
{{clickMessage}} {{clickMessage}}
@ -326,26 +394,25 @@ button</button>
</big-hero-detail> </big-hero-detail>
<!-- #docregion event-binding-bubbling --> <!-- #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 class="child-div">Click me too!</div>
</div> </div>
<!-- #enddocregion event-binding-bubbling --> <!-- #enddocregion event-binding-bubbling -->
<br><br>
<!-- #docregion event-binding-no-propagation --> <!-- #docregion event-binding-no-propagation -->
<!-- Will save only once --> <!-- Will save only once -->
<div (click)="onSave()"> <div (click)="onSave()" clickable>
<button (click)="onSave()">Save, no propagation</button> <button (click)="onSave()">Save, no propagation</button>
</div> </div>
<!-- #enddocregion event-binding-no-propagation --> <!-- #enddocregion event-binding-no-propagation -->
<br><br>
<!-- #docregion event-binding-propagation --> <!-- #docregion event-binding-propagation -->
<!-- Will save twice --> <!-- Will save twice -->
<div (click)="onSave()"> <div (click)="onSave()" clickable>
<button (click)="onSave() || true">Save w/ propagation</button> <button (click)="onSave() || true">Save w/ propagation</button>
</div> </div>
<!-- #enddocregion event-binding-propagation --> <!-- #enddocregion event-binding-propagation -->
<br><br>
<a class="to-toc" href="#toc">top</a> <a class="to-toc" href="#toc">top</a>
<hr><h2 id="two-way">Two-way Binding</h2> <hr><h2 id="two-way">Two-way Binding</h2>
@ -363,7 +430,6 @@ button</button>
<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer> <my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>
<!-- #enddocregion two-way-2 --> <!-- #enddocregion two-way-2 -->
</div> </div>
<br><br>
<a class="to-toc" href="#toc">top</a> <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` --> passing the changed display value to the event handler via `$event` -->
<hr><h2 id="ngModel">NgModel (two-way) Binding</h2> <hr><h2 id="ngModel">NgModel (two-way) Binding</h2>
<h3>Result: {{currentHero.firstName}}</h3> <h3>Result: {{currentHero.name}}</h3>
<!-- #docregion without-NgModel --> <!-- #docregion without-NgModel -->
<input [value]="currentHero.firstName" <input [value]="currentHero.name"
(input)="currentHero.firstName=$event.target.value" > (input)="currentHero.name=$event.target.value" >
<!-- #enddocregion without-NgModel --> <!-- #enddocregion without-NgModel -->
without NgModel without NgModel
<br> <br>
<!-- #docregion NgModel-1 --> <!-- #docregion NgModel-1 -->
<input [(ngModel)]="currentHero.firstName"> <input [(ngModel)]="currentHero.name">
<!-- #enddocregion NgModel-1 --> <!-- #enddocregion NgModel-1 -->
[(ngModel)] [(ngModel)]
<br> <br>
<!-- #docregion NgModel-2 --> <!-- #docregion NgModel-2 -->
<input bindon-ngModel="currentHero.firstName"> <input bindon-ngModel="currentHero.name">
<!-- #enddocregion NgModel-2 --> <!-- #enddocregion NgModel-2 -->
bindon-ngModel bindon-ngModel
<br> <br>
<!-- #docregion NgModel-3 --> <!-- #docregion NgModel-3 -->
<input <input
[ngModel]="currentHero.firstName" [ngModel]="currentHero.name"
(ngModelChange)="currentHero.firstName=$event"> (ngModelChange)="currentHero.name=$event">
<!-- #enddocregion NgModel-3 --> <!-- #enddocregion NgModel-3 -->
(ngModelChange) = "...firstName=$event" (ngModelChange) = "...name=$event"
<br> <br>
<!-- #docregion NgModel-4 --> <!-- #docregion NgModel-4 -->
<input <input
[ngModel]="currentHero.firstName" [ngModel]="currentHero.name"
(ngModelChange)="setUpperCaseFirstName($event)"> (ngModelChange)="setUppercaseName($event)">
<!-- #enddocregion NgModel-4 --> <!-- #enddocregion NgModel-4 -->
(ngModelChange) = "setUpperCaseFirstName($event)" (ngModelChange) = "setUppercaseName($event)"
<br>
<a class="to-toc" href="#toc">top</a> <a class="to-toc" href="#toc">top</a>
@ -462,7 +527,6 @@ bindon-ngModel
This div should be {{ canSave ? "italic": "plain"}}, This div should be {{ canSave ? "italic": "plain"}},
{{ isUnchanged ? "normal weight" : "bold" }} and, {{ isUnchanged ? "normal weight" : "bold" }} and,
{{ isSpecial ? "extra large": "normal size"}} after clicking "refresh".</div> {{ isSpecial ? "extra large": "normal size"}} after clicking "refresh".</div>
<br>
<a class="to-toc" href="#toc">top</a> <a class="to-toc" href="#toc">top</a>
@ -470,21 +534,17 @@ bindon-ngModel
<hr><h2 id="ngIf">NgIf Binding</h2> <hr><h2 id="ngIf">NgIf Binding</h2>
<!-- #docregion NgIf-1 --> <!-- #docregion NgIf-1 -->
<div *ngIf="currentHero">Hello, {{currentHero.firstName}}</div> <hero-detail *ngIf="isActive"></hero-detail>
<!-- #enddocregion NgIf-1 --> <!-- #enddocregion NgIf-1 -->
<!-- #docregion NgIf-2 --> <!-- #docregion NgIf-2 -->
<!-- because of the ngIf guard <div *ngIf="currentHero">Hello, {{currentHero.name}}</div>
`nullHero.firstName` never has a chance to fail --> <div *ngIf="nullHero">Hello, {{nullHero.name}}</div>
<div *ngIf="nullHero">Hello, {{nullHero.firstName}}</div>
<!-- Hero Detail is not in the DOM because isActive is false-->
<hero-detail *ngIf="isActive"></hero-detail>
<!-- #enddocregion NgIf-2 --> <!-- #enddocregion NgIf-2 -->
<!-- NgIf binding with template (no *) --> <!-- 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! --> <!-- Does not show because isActive is false! -->
<div>Hero Detail removed from DOM (via template) because isActive is false</div> <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> <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 --> <!-- NgFor binding -->
<hr><h2 id="ngFor">NgFor Binding</h2> <hr><h2 id="ngFor">NgFor Binding</h2>
<div class="box"> <div class="box">
<!-- #docregion NgFor-1 --> <!-- #docregion NgFor-1, NgFor-1-2 -->
<div *ngFor="let hero of heroes">{{hero.fullName}}</div> <div *ngFor="let hero of heroes">{{hero.name}}</div>
<!-- #enddocregion NgFor-1 --> <!-- #enddocregion NgFor-1, NgFor-1-2 -->
</div> </div>
<br> <br>
<div class="box"> <div class="box">
<!-- *ngFor w/ hero-detail Component --> <!-- *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> <hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>
<!-- #enddocregion NgFor-2 --> <!-- #enddocregion NgFor-2, NgFor-1-2 -->
</div> </div>
<a class="to-toc" href="#toc">top</a> <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> <p>with <i>semi-colon</i> separator</p>
<div class="box"> <div class="box">
<!-- #docregion NgFor-3 --> <!-- #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 --> <!-- #enddocregion NgFor-3 -->
</div> </div>
<p>with <i>comma</i> separator</p> <p>with <i>comma</i> separator</p>
<div class="box"> <div class="box">
<!-- Ex: "1 - Hercules Son of Zeus"" --> <!-- Ex: "1 - Hercules" -->
<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>
</div> </div>
<a class="to-toc" href="#toc">top</a> <a class="to-toc" href="#toc">top</a>
<h4 id="ngFor-trackBy">NgForTrackBy</h4> <h4 id="ngFor-trackBy">*ngFor trackBy</h4>
<button (click)="refreshHeroes()">Refresh heroes</button> <button (click)="resetHeroes()">Reset heroes</button>
<p>First hero: <input [(ngModel)]="heroes[0].firstName"></p> <button (click)="changeIds()">Change ids</button>
<button (click)="clearTrackByCounts()">Clear counts</button>
<p><i>without</i> trackBy</p> <p><i>without</i> trackBy</p>
<div #noTrackBy class="box"> <div class="box">
<!-- #docregion NgForTrackBy-1 --> <div #noTrackBy *ngFor="let hero of heroes">({{hero.id}}) {{hero.name}}</div>
<div *ngFor="let hero of heroes">({{hero.id}}) {{hero.fullName}}</div>
<!-- #enddocregion NgForTrackBy-1 --> <div id="noTrackByCnt" *ngIf="heroesNoTrackByCount" >
</div> Hero DOM elements change #{{heroesNoTrackByCount}} without trackBy
<div id="noTrackByCnt" *ngIf="heroesNoTrackByChangeCount" style="background-color:bisque"> </div>
Hero DOM elements change #<span style="background-color:gold">{{heroesNoTrackByChangeCount}}</span> without trackBy
</div> </div>
<p>with trackBy and <i>semi-colon</i> separator</p> <p>with trackBy</p>
<div #withTrackBy class="box"> <div class="box">
<!-- #docregion NgForTrackBy-2 --> <div #withTrackBy *ngFor="let hero of heroes; trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
<div *ngFor="let hero of heroes; trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
<!-- #enddocregion NgForTrackBy-2 --> <div id="withTrackByCnt" *ngIf="heroesWithTrackByCount">
Hero DOM elements change #{{heroesWithTrackByCount}} with trackBy
</div>
</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> </div>
<p>with trackBy and <i>comma</i> separator</p> <p>with trackBy and <i>comma</i> separator</p>
<div class="box"> <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> </div>
<p>with trackBy and <i>space</i> separator</p> <p>with trackBy and <i>space</i> separator</p>
<div class="box"> <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> </div>
<p>with <i>generic</i> trackById function</p> <p>with <i>generic</i> trackById function</p>
<div class="box"> <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> </div>
<a class="to-toc" href="#toc">top</a> <a class="to-toc" href="#toc">top</a>
<!-- * and template --> <!-- NgSwitch binding -->
<hr><h2 id="star-prefix">* prefix and &lt;template&gt;</h2> <hr><h2 id="ngSwitch">NgSwitch Binding</h2>
<h3>*ngIf expansion</h3> <div>Pick your favorite hero</div>
<p><i>*ngIf</i></p> <p>
<!-- #docregion Template-1 --> <ng-container *ngFor="let h of heroes">
<hero-detail *ngIf="currentHero" [hero]="currentHero"></hero-detail> <label>
<!-- #enddocregion Template-1 --> <input type="radio" name="heroes" [(ngModel)]="currentHero" [value]="h">{{h.name}}
</label>
</ng-container>
</p>
<p><i>expand to template = "..."</i></p> <!-- #docregion NgSwitch -->
<!-- #docregion Template-2a --> <div [ngSwitch]="currentHero.emotion">
<hero-detail template="ngIf:currentHero" [hero]="currentHero"></hero-detail> <happy-hero *ngSwitchCase="'happy'" [hero]="currentHero"></happy-hero>
<!-- #enddocregion Template-2a --> <sad-hero *ngSwitchCase="'sad'" [hero]="currentHero"></sad-hero>
<confused-hero *ngSwitchCase="'confused'" [hero]="currentHero"></confused-hero>
<p><i>expand to &lt;template&gt;</i></p> <!-- #enddocregion NgSwitch -->
<!-- #docregion Template-2 --> <!-- #docregion NgSwitch-div -->
<template [ngIf]="currentHero"> <div *ngSwitchCase="'confused'">Are you as confused as {{currentHero.name}}?</div>
<hero-detail [hero]="currentHero"></hero-detail> <!-- #enddocregion NgSwitch-div -->
</template> <!-- #docregion NgSwitch -->
<!-- #enddocregion Template-2 --> <unknown-hero *ngSwitchDefault [hero]="currentHero"></unknown-hero>
<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 -->
</div> </div>
<!-- #enddocregion NgSwitch -->
<a class="to-toc" href="#toc">top</a> <a class="to-toc" href="#toc">top</a>
@ -677,34 +684,27 @@ bindon-ngModel
<hr><h2 id="ref-vars">Template reference variables</h2> <hr><h2 id="ref-vars">Template reference variables</h2>
<!-- #docregion ref-phone --> <!-- #docregion ref-phone -->
<!-- phone refers to the input element; pass its `value` to an event handler --> <!-- #docregion ref-var -->
<input #phone placeholder="phone number"> <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 --> <!-- lots of other elements -->
<input ref-fax placeholder="fax number">
<button (click)="callFax(fax.value)">Fax</button> <!-- phone refers to the input element; pass its `value` to an event handler -->
<button (click)="callPhone(phone.value)">Call</button>
<!-- #enddocregion ref-phone --> <!-- #enddocregion ref-phone -->
<h4>Example Form</h4> <!-- #docregion ref-fax -->
<!-- #docregion ref-form --> <input ref-fax placeholder="fax number">
<!-- #docregion ref-form-a --> <button (click)="callFax(fax.value)">Fax</button>
<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm"> <!-- #enddocregion ref-fax -->
<!-- #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>
<!-- btn refers to the button element; show its disabled state --> <!-- btn refers to the button element; show its disabled state -->
<button #btn disabled [innerHTML]="'disabled by attribute: '+btn.disabled"></button> <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> <a class="to-toc" href="#toc">top</a>
<!-- inputs and output --> <!-- inputs and output -->
@ -720,7 +720,7 @@ bindon-ngModel
</hero-detail> </hero-detail>
<!-- #enddocregion io-2 --> <!-- #enddocregion io-2 -->
<div (myClick)="clickMessage2=$event">myClick2</div> <div (myClick)="clickMessage2=$event" clickable>myClick2</div>
{{clickMessage2}} {{clickMessage2}}
<a class="to-toc" href="#toc">top</a> <a class="to-toc" href="#toc">top</a>
@ -769,43 +769,42 @@ bindon-ngModel
<div> <div>
<!-- #docregion safe-2 --> <!-- #docregion safe-2 -->
The current hero's name is {{currentHero?.firstName}} The current hero's name is {{currentHero?.name}}
<!-- #enddocregion safe-2 --> <!-- #enddocregion safe-2 -->
</div> </div>
<div> <div>
<!-- #docregion safe-3 --> <!-- #docregion safe-3 -->
The current hero's name is {{currentHero.firstName}} The current hero's name is {{currentHero.name}}
<!-- #enddocregion safe-3 --> <!-- #enddocregion safe-3 -->
</div> </div>
<!-- <!--
The null hero's name is {{nullHero.firstName}} The null hero's name is {{nullHero.name}}
See console log: See console log:
TypeError: Cannot read property 'firstName' of null in [null] TypeError: Cannot read property 'name' of null in [null]
--> -->
<!-- #docregion safe-4 --> <!-- #docregion safe-4 -->
<!--No hero, div not displayed, no error --> <!--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 --> <!-- #enddocregion safe-4 -->
<div> <div>
<!-- #docregion safe-5 --> <!-- #docregion safe-5 -->
The null hero's name is {{nullHero && nullHero.firstName}} The null hero's name is {{nullHero && nullHero.name}}
<!-- #enddocregion safe-5 --> <!-- #enddocregion safe-5 -->
</div> </div>
<div> <div>
<!-- #docregion safe-6 --> <!-- #docregion safe-6 -->
<!-- No hero, no problem! --> <!-- No hero, no problem! -->
The null hero's name is {{nullHero?.firstName}} The null hero's name is {{nullHero?.name}}
<!-- #enddocregion safe-6 --> <!-- #enddocregion safe-6 -->
</div> </div>
<a class="to-toc" href="#toc">top</a> <a class="to-toc" href="#toc">top</a>
<!-- TODO: discuss this in the Style binding section --> <!-- TODO: discuss this in the Style binding section -->

View File

@ -2,7 +2,6 @@
// #docplaster // #docplaster
import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren } from '@angular/core'; import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Hero } from './hero'; import { Hero } from './hero';
@ -19,20 +18,26 @@ export enum Color {Red, Green, Blue};
@Component({ @Component({
moduleId: module.id, moduleId: module.id,
selector: 'my-app', selector: 'my-app',
templateUrl: './app.component.html' templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
}) })
export class AppComponent implements AfterViewInit, OnInit { export class AppComponent implements AfterViewInit, OnInit {
ngOnInit() { ngOnInit() {
this.refreshHeroes(); this.resetHeroes();
this.setCurrentClasses(); this.setCurrentClasses();
this.setCurrentStyles(); this.setCurrentStyles();
} }
ngAfterViewInit() { 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'; actionName = 'Go for it';
alert = alerter; alert = alerter;
badCurly = 'bad curly'; badCurly = 'bad curly';
@ -42,20 +47,40 @@ export class AppComponent implements AfterViewInit, OnInit {
callPhone(value: string) {this.alert(`Calling ${value} ...`); } callPhone(value: string) {this.alert(`Calling ${value} ...`); }
canSave = true; 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;
color = Color.Red; color = Color.Red;
colorToggle() {this.color = (this.color === Color.Red) ? Color.Blue : Color.Red; } colorToggle() {this.color = (this.color === Color.Red) ? Color.Blue : Color.Red; }
currentHero = Hero.MockHeroes[0]; currentHero: Hero;
deleteHero(hero: Hero) { deleteHero(hero: Hero) {
this.alert('Deleted hero: ' + (hero && hero.firstName)); this.alert(`Delete ${hero ? hero.name : 'the hero'}.`);
} }
// #docregion evil-title // #docregion evil-title
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax'; evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
// #enddocregion evil-title // #enddocregion evil-title
fontSizePx = 16;
title = 'Template Syntax'; title = 'Template Syntax';
getStyles(el: Element) { getStyles(el: Element) {
@ -69,22 +94,26 @@ export class AppComponent implements AfterViewInit, OnInit {
getVal() { return this.val; } getVal() { return this.val; }
hero: Hero; // defined to demonstrate template context precedence
heroes: Hero[]; 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'; // heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png';
// Public Domain terms of use: http://www.wpclipart.com/terms.html // Public Domain terms of use: http://www.wpclipart.com/terms.html
heroImageUrl = 'images/hero.png'; heroImageUrl = 'images/hero.png';
// iconUrl = 'https://angular.io/resources/images/logos/standard/shield-large.png';
clicked = '';
clickMessage = '';
clickMessage2 = '';
iconUrl = 'images/ng-logo.png'; iconUrl = 'images/ng-logo.png';
isActive = false; isActive = false;
isSpecial = true; isSpecial = true;
isUnchanged = true; isUnchanged = true;
nullHero: Hero = null; // or undefined nullHero: Hero = null;
onCancel(event: KeyboardEvent) { onCancel(event: KeyboardEvent) {
let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).innerHTML : ''; let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).innerHTML : '';
@ -101,26 +130,20 @@ export class AppComponent implements AfterViewInit, OnInit {
this.alert('Saved.' + evtMsg); this.alert('Saved.' + evtMsg);
} }
onSubmit(form: NgForm) { onSubmit() { /* referenced but not used */}
let evtMsg = form.valid ?
' Form value is ' + JSON.stringify(form.value) :
' Form is invalid';
this.alert('Form submitted.' + evtMsg);
}
product = { product = {
name: 'frimfram', name: 'frimfram',
price: 42 price: 42
}; };
// #docregion refresh-heroes // updates with fresh set of cloned heroes
// update this.heroes with fresh set of cloned heroes resetHeroes() {
refreshHeroes() { this.heroes = Hero.heroes.map(hero => hero.clone());
this.heroes = Hero.MockHeroes.map(hero => Hero.clone(hero)); this.currentHero = this.heroes[0];
this.heroesWithTrackByCountReset = 0;
} }
// #enddocregion refresh-heroes
// #docregion same-as-it-ever-was
private samenessCount = 5; private samenessCount = 5;
moreOfTheSame() { this.samenessCount++; }; moreOfTheSame() { this.samenessCount++; };
get sameAsItEverWas() { get sameAsItEverWas() {
@ -131,11 +154,9 @@ export class AppComponent implements AfterViewInit, OnInit {
// return {id:id, text: 'same as it ever was ...'}; // return {id:id, text: 'same as it ever was ...'};
// }); // });
} }
// #enddocregion same-as-it-ever-was
setUpperCaseFirstName(firstName: string) { setUppercaseName(name: string) {
// console.log(firstName); this.currentHero.name = name.toUpperCase();
this.currentHero.firstName = firstName.toUpperCase();
} }
// #docregion setClasses // #docregion setClasses
@ -162,69 +183,31 @@ export class AppComponent implements AfterViewInit, OnInit {
} }
// #enddocregion setStyles // #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 // #docregion trackByHeroes
trackByHeroes(index: number, hero: Hero) { return hero.id; } trackByHeroes(index: number, hero: Hero): number { return hero.id; }
// #enddocregion trackByHeroes // #enddocregion trackByHeroes
// #docregion trackById // #docregion trackById
trackById(index: number, item: any): string { return item['id']; } trackById(index: number, item: any): number { return item['id']; }
// #enddocregion trackById // #enddocregion trackById
val = 2; val = 2;
// villainImageUrl = 'http://www.clker.com/cliparts/u/s/y/L/x/9/villain-man-hi.png' // 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 // Public Domain terms of use http://www.clker.com/disclaimer.html
villainImageUrl = 'images/villain.png'; 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 // helper to track changes to viewChildren
function toArray(viewChildren: QueryList<ElementRef>) { function trackChanges(views: QueryList<ElementRef>, changed: () => void) {
let result: HTMLElement[] = []; let oldRefs = views.toArray();
let children = viewChildren.toArray()[0].nativeElement.children; views.changes.subscribe((changes: QueryList<ElementRef>) => {
for (let i = 0; i < children.length; i++) { result.push(children[i]); } const changedRefs = changes.toArray();
return result; // 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 // #docregion
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; 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({ @NgModule({
imports: [ imports: [
BrowserModule, BrowserModule,
FormsModule FormsModule // <--- import into the NgModule
], ],
declarations: [ /* Other module metadata */
AppComponent
],
bootstrap: [ AppComponent ]
}) })
export class AppModule { } export class AppModule { }

View File

@ -5,6 +5,8 @@ import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { BigHeroDetailComponent, HeroDetailComponent } from './hero-detail.component'; import { BigHeroDetailComponent, HeroDetailComponent } from './hero-detail.component';
import { ClickDirective, ClickDirective2 } from './click.directive'; import { ClickDirective, ClickDirective2 } from './click.directive';
import { HeroFormComponent } from './hero-form.component';
import { heroSwitchComponents } from './hero-switch.components';
import { SizerComponent } from './sizer.component'; import { SizerComponent } from './sizer.component';
@NgModule({ @NgModule({
@ -16,6 +18,8 @@ import { SizerComponent } from './sizer.component';
AppComponent, AppComponent,
BigHeroDetailComponent, BigHeroDetailComponent,
HeroDetailComponent, HeroDetailComponent,
HeroFormComponent,
heroSwitchComponents,
ClickDirective, ClickDirective,
ClickDirective2, ClickDirective2,
SizerComponent SizerComponent

View File

@ -18,7 +18,7 @@ import { Hero } from './hero';
<div> <div>
<img src="{{heroImageUrl}}"> <img src="{{heroImageUrl}}">
<span [style.text-decoration]="lineThrough"> <span [style.text-decoration]="lineThrough">
{{prefix}} {{hero?.fullName}} {{prefix}} {{hero?.name}}
</span> </span>
<button (click)="delete()">Delete</button> <button (click)="delete()">Delete</button>
</div>` </div>`
@ -27,7 +27,7 @@ import { Hero } from './hero';
}) })
// #enddocregion input-output-2 // #enddocregion input-output-2
export class HeroDetailComponent { 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'; // heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png';
// Public Domain terms of use: http://www.wpclipart.com/terms.html // Public Domain terms of use: http://www.wpclipart.com/terms.html
heroImageUrl = 'images/hero.png'; heroImageUrl = 'images/hero.png';
@ -50,18 +50,22 @@ export class HeroDetailComponent {
@Component({ @Component({
selector: 'big-hero-detail', selector: 'big-hero-detail',
template: ` template: `
<div style="border: 1px solid black; padding:3px"> <div class="detail">
<img src="{{heroImageUrl}}" style="float:left; margin-right:8px;"> <img src="{{heroImageUrl}}">
<div><b>{{hero?.fullName}}</b></div> <div><b>{{hero?.name}}</b></div>
<div>First: {{hero?.firstName}}</div> <div>Name: {{hero?.name}}</div>
<div>Last: {{hero?.lastName}}</div> <div>Emotion: {{hero?.emotion}}</div>
<div>Birthdate: {{hero?.birthdate | date:'longDate'}}</div> <div>Birthdate: {{hero?.birthdate | date:'longDate'}}</div>
<div>Web: <a href="{{hero?.url}}" target="_blank">{{hero?.url}}</a></div> <div>Web: <a href="{{hero?.url}}" target="_blank">{{hero?.url}}</a></div>
<div>Rate/hr: {{hero?.rate | currency:'EUR'}}</div> <div>Rate/hr: {{hero?.rate | currency:'EUR'}}</div>
<br clear="all"> <br clear="all">
<button (click)="delete()">Delete</button> <button (click)="delete()">Delete</button>
</div> </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 { 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 { export class Hero {
static nextId = 1; static nextId = 1;
static MockHeroes = [ static heroes: Hero[] = [
new Hero( new Hero(
325,
'Hercules', 'Hercules',
'Son of Zeus', 'happy',
new Date(1970, 1, 25), new Date(1970, 1, 25),
'http://www.imdb.com/title/tt0065832/', 'http://www.imdb.com/title/tt0065832/'
325), ),
new Hero(1, 'Mr. Nice', 'happy'),
new Hero('eenie', 'toe'), new Hero(2, 'Narco', 'sad' ),
new Hero('Meanie', 'Toe'), new Hero(3, 'Windstorm', 'confused' ),
new Hero('Miny', 'Toe'), new Hero(4, 'Magneta')
new Hero('Moe', 'Toe')
]; ];
public id: number;
static clone({firstName, lastName, birthdate, url, rate, id}: Hero) {
return new Hero(firstName, lastName, birthdate, url, rate, id);
}
constructor( constructor(
public firstName: string, public id?: number,
public lastName?: string, public name?: string,
public emotion?: string,
public birthdate?: Date, public birthdate?: Date,
public url?: string, public url?: string,
public rate = 100, public rate = 100,
id?: number) { ) {
this.id = id ? id : Hero.nextId++;
this.id = id != null ? 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 charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="template-syntax.css">
<!-- Polyfills --> <!-- Polyfills -->
<script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/core-js/client/shim.min.js"></script>

View File

@ -191,7 +191,7 @@ function heroModuleSetup() {
})); }));
// #docregion title-case-pipe // #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 inputName = 'quick BROWN fox';
const titleCaseName = 'Quick Brown Fox'; const titleCaseName = 'Quick Brown Fox';
@ -205,7 +205,7 @@ function heroModuleSetup() {
fixture.detectChanges(); fixture.detectChanges();
expect(page.nameDisplay.textContent).toBe(titleCaseName); expect(page.nameDisplay.textContent).toBe(titleCaseName);
})); });
// #enddocregion title-case-pipe // #enddocregion title-case-pipe
// #enddocregion selected-tests // #enddocregion selected-tests
// #docregion route-good-id // #docregion route-good-id

View File

@ -54,8 +54,10 @@ a#aot
:marked :marked
In practice, a synonym for [Decoration](#decorator). In practice, a synonym for [Decoration](#decorator).
a#attribute-directive
a#attribute-directives
:marked :marked
## Attribute directive ## Attribute directives
.l-sub-section .l-sub-section
:marked :marked
A category of [directive](#directive) that can listen to and modify the behavior of 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. 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 .l-main-section#B
+ifDocsFor('ts|js') +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). 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*. When you see "camelCase" in this documentation it always means *lower camel case*.
a#component
:marked :marked
## Component ## Component
.l-sub-section .l-sub-section
@ -245,7 +250,7 @@ a#aot
that "B" is a dependency of "A". that "B" is a dependency of "A".
You can ask a "dependency injection system" to create "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 If "A" needs "B" and "B" needs "C", the system resolves that chain of dependencies
and returns a fully prepared instance of "A". and returns a fully prepared instance of "A".
@ -276,9 +281,12 @@ a#aot
Registering providers is a critical preparatory step. Registering providers is a critical preparatory step.
Angular registers some of its own providers with every injector. 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. Read more in the [Dependency Injection](!{docsLatest}/guide/dependency-injection.html) page.
a#directive
a#directives
:marked :marked
## Directive ## Directive
.l-sub-section .l-sub-section
@ -286,8 +294,7 @@ a#aot
An Angular class responsible for creating, reshaping, and interacting with HTML elements An Angular class responsible for creating, reshaping, and interacting with HTML elements
in the browser DOM. Directives are Angular's most fundamental feature. in the browser DOM. Directives are Angular's most fundamental feature.
A Directive is almost always associated with an HTML element or attribute. 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.
When Angular finds a directive in an HTML template, When Angular finds a directive in an HTML template,
it creates the matching directive class instance it creates the matching directive class instance
and gives the instance control over that portion of the browser DOM. 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 as if you were writing native HTML. In this way, directives become extensions of
HTML itself. 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 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 They are the building blocks of an Angular application and the
developer can expect to write a lot of them. developer can expect to write a lot of them.
1. [Attribute directives](#attribute-directive) that can listen to and modify the behavior of 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. as HTML attributes, hence the name.
1. [Structural directives](#structural-directive), a directive responsible for 1. [Structural directives](#structural-directive) that
shaping or reshaping HTML layout, typically by adding, removing, or manipulating shape or reshape HTML layout, typically by adding and removing elements in the DOM.
elements and their children.
.l-main-section#E .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. Applications often require services such as a hero data service or a logging service.
A service is a class with a focused purpose. 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, independent from any specific view,
provide shared data or logic across components, or encapsulate external interactions. 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. 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 :marked
## Structural directive ## Structural directives
.l-sub-section .l-sub-section
:marked :marked
A category of [directive](#directive) that can A category of [directive](#directive) that can
shape or reshape HTML layout, typically by adding, removing, or manipulating shape or reshape HTML layout, typically by adding and removing elements in the DOM.
elements and their children; for example, the `ngIf` "conditional element" directive and the `ngFor` "repeater" directive. 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 .l-main-section#T
:marked :marked
@ -699,8 +707,8 @@ a#snake-case
A version of JavaScript that supports most [ECMAScript 2015](#es2015) A version of JavaScript that supports most [ECMAScript 2015](#es2015)
language features such as [decorators](#decorator). language features such as [decorators](#decorator).
TypeScript is also noteable for its optional typing system, which gives TypeScript is also noteable for its optional typing system, which enables
us compile-time type checking and strong tooling support (for example, "intellisense", compile-time type checking and strong tooling support (for example, "intellisense",
code completion, refactoring, and intelligent search). Many code editors code completion, refactoring, and intelligent search). Many code editors
and IDEs support TypeScript either natively or with plugins. and IDEs support TypeScript either natively or with plugins.

View File

@ -24,17 +24,20 @@ a#directive-overview
## Directives overview ## Directives overview
There are three kinds of directives in Angular: 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. Components &mdash; directives with a template.
1. Attribute directives&mdash;change the appearance or behavior of an element. 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. *Components* are the most common of the three directives.
You saw a component for the first time in the [QuickStart](../quickstart.html) example. 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) *Structural Directives* change the structure of the view.
in the [Template Syntax](template-syntax.html) page. 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. can change several element styles at the same time.
.l-main-section .l-main-section

View File

@ -5,6 +5,12 @@ block includes
The Angular documentation is a living document with continuous improvements. The Angular documentation is a living document with continuous improvements.
This log calls attention to recent significant changes. 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) ## 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. 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. 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: refreshed (2017-01-13)
[Hierarchical Dependency Injection](hierarchical-dependency-injection.html) guide significantly revised. [Hierarchical Dependency Injection](hierarchical-dependency-injection.html) guide significantly revised.
Closes issue #3086 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) ## Miscellaneous (2017-01-05)
* [Setup](setup.html) guide: * [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 block includes
include ../_util-fns include ../_util-fns
:marked // The docs standard h4 style uppercases, making code terms unreadable. Override it.
One of the defining features of a single page application is its manipulation style.
of the DOM tree. Instead of serving a whole new page every time a user h4 {font-size: 17px !important; text-transform: none !important;}
navigates, whole sections of the DOM appear and disappear according .syntax { font-family: Consolas, 'Lucida Sans', Courier, sans-serif; color: black; font-size: 85%; }
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.
In this chapter we will :marked
- [learn what structural directives are](#definition) This guide looks at how Angular manipulates the DOM with **structural directives** and
- [study *ngIf*](#ngIf) how you can write your own structural directives to do the same thing.
- [discover the &lt;template> element](#template)
- [understand the asterisk (\*) in **ngFor*](#asterisk) ### Table of contents
- [write our own structural directive](#unless)
- [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>. Try the <live-example></live-example>.
<a id="definition"></a> a#definition
.l-main-section .l-main-section
:marked :marked
## What are structural directives? ## What are structural directives?
There are three kinds of Angular directives: Structural directives are responsible for HTML layout.
1. Components They shape or reshape the DOM's _structure_, typically by adding, removing, or manipulating
1. Attribute directives elements.
1. Structural directives
The *Component* is really a directive with a template. As with other directives, you apply a structural directive to a _host element_.
It's the most common of the three directives and we write lots of them as we build our application. 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. Structural directives are easy to recognize.
The built-in [NgStyle](template-syntax.html#ngStyle) directive, for example, An asterisk (\*) precedes the directive attribute name as in this example.
can change several element styles at the same time. +makeExample('structural-directives/ts/src/app/app.component.html', 'ngif')(format=".")
We can use it to render text bold, italic, and lime green by binding to a :marked
component property that requests such a sickening result. No brackets. No parentheses. Just `*ngIf` set to a string.
A *Structural* directive changes the DOM layout by adding and removing DOM elements. You'll learn in this guide that the [asterisk (\*) is a convenience notation](#asterisk)
We've seen three of the built-in structural directives in other chapters: [ngIf](template-syntax.html#ngIf), and the string is a [_microsyntax_](#microsyntax) rather than the usual [template expression](template-syntax.html#template-expressions).
[ngSwitch](template-syntax.html#ngSwitch) and [ngFor](template-syntax.html#ngFor). 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 .l-main-section
:marked :marked
## NgIf Case Study ## NgIf Case Study
Let's focus on `ngIf`. It's a great example of a structural `NgIf` is the simplest structural directive and the easiest to understand.
directive: it takes a boolean and makes an entire chunk of DOM appear It takes a boolean value and makes an entire chunk of the DOM appear or disappear.
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 :marked
The `ngIf` directive does not hide the element. The `ngIf` directive doesn't hide elements with CSS. It adds and removes them physically from the DOM.
Using browser developer tools we can see that, when the condition is true, the top Confirm that fact using browser developer tools to inspect the DOM.
paragraph is in the DOM and the bottom disused paragraph is completely
absent from the DOM! In its place are empty `<script>` tags.
figure.image-display 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 :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*? ### 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, A directive could hide the unwanted paragraph instead by setting its `display` style to `none`.
the component's behavior continues. +makeExample('structural-directives/ts/src/app/app.component.html', 'display-none')(format=".")
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.
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 :marked
Although there are pros and cons to each approach, While invisible, the element remains in the DOM.
in general it is best to use `ngIf` to remove unwanted components rather than
hide them. 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.** **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 Before applying a structural directive, you might want to pause for a moment
about the consequences of adding and removing elements and of creating and destroying components. 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* a#ngcontainer
our recommendation and consider a component called `heavy-loader` that a#ng-container
***pretends*** to load a ton of data when initialized. .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. There's often a _root_ element that can and should host the structural directive.
We toggle the second into and out of the DOM with `ngIf`. The list element (`<li>`) is a typical host element of an `NgFor` repeater.
+makeTabs( +makeExample('structural-directives/ts/src/app/app.component.html', 'ngfor-li')(format=".")
`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')
:marked :marked
We also log when a component is created or destroyed When there isn't a host element, you can usually wrap the content in a native HTML container element,
using the built-in `ngOnInit` and `ngOnDestroy` [lifecycle hooks](lifecycle-hooks.html). such as a `<div>`, and attach the directive to that wrapper.
Here it is in action:
+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 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 :marked
Both components are in the DOM at the start. The `p span` style, intended for use elsewhere, was inadvertently applied here.
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.
Then we toggle the second component with `ngIf`. Another problem: some HTML elements require all immediate children to be of a specific type.
We create a new instance every time and the log shows that we're paying For example, the `<select>` tag requires `<option>` children.
a heavy price to create and destroy it. You can't wrap the _options_ in a conditional `<div>` or a `<span>`.
If we really expected to "wink" the component like this, toggling visibility would be the better choice. When you try this,
In most UIs, when we "close" a component we're unlikely to see it again for a long time, if ever. +makeExample('structural-directives/ts/src/app/app.component.html', 'select-span')(format=".")
The `ngIf` would be preferred in that case.
<a id="template"></a>
.l-main-section
:marked :marked
## The *&lt;template>* tag the drop down is empty.
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.
figure.image-display 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 :marked
Evidently Angular replaces the `<template>` tag and its contents with empty `<script>` tags. The `<ng-container>` is a syntax element recognized by the Angular parser.
That's just its default behavior. It's not a directive, component, class, or interface.
It can do something different as we saw when applying a variety of `ngSwitch` directives to `<template>` tags: 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 :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#asterisk
<a id="asterisk"></a>
.l-main-section .l-main-section
:marked :marked
## The asterisk (\*) effect ## The asterisk (\*) prefix
Here are those directives again. See the difference?
+makeExample('structural-directives/ts/src/app/structural-directives.component.html', 'asterisk')(format=".") 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/app.component.html', 'asterisk')(format=".")
:marked :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. // We should discourage writing <template> syntax in favor of <ng-container>
Under the hood, Angular replaces the asterisk version with a more verbose `<template>` form. block remember-the-brackets
.alert.is-critical
The next two `ngIf` examples are effectively the same and we may write in either style: :marked
Write `[ngIf]="hero"`, not `ngIf="hero"`!
+makeExample('structural-directives/ts/src/app/structural-directives.component.html', 'ngIf-template')(format=".") 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 :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.
It's worth knowing that Angular expands style (A) into style (B). None of these forms are actually rendered.
It moves the paragraph and its contents inside a `<template>` tag. Only the finished product ends up in the DOM.
It moves the directive up to the `<template>` tag where it becomes a property binding, figure.image-display
surrounded in square brackets. The boolean value of the host component's `condition` property img(src='/resources/images/devguide/structural-directives/hero-div-in-dom.png' alt="hero div in DOM")
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 :marked
The basic pattern is the same:&nbsp; create a `<template>`, relocate the content, Angular consumed the `<template>` content during its actual rendering and
and move the directive onto the `<template>`. replaced the `<template>` with a diagnostic comment.
There are extra nuances stemming from The [`NgFor`](#ngfor) and [`NgSwitch...`](#ngswitch) directives follow the same pattern.
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> a#ngfor
.l-main-section .l-main-section
:marked :marked
## Make a structural directive ## Inside _*ngFor_
Let's write our own structural directive, an `Unless` directive, the not-so-evil twin of `ngIf`.
Unlike `ngIf` which displays the template content when `true`, Angular transforms the `*ngFor` in similar fashion from asterisk (\*) syntax through
our directive displays the content when the condition is ***false***. 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 block unless-intro
:marked :marked
Creating a directive is similar to creating a component. 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 * Import the `Input`, `TemplateRef`, and `ViewContainerRef` symbols; you'll them need for _any_ structural directive.
(typically the name of the directive itself).
* 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 .l-sub-section
:marked :marked
### Selector brackets [&nbsp;] Read about `@Input` in the [_Template Syntax_](template-syntax.html#inputs-outputs) guide.
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).
### Selector name prefixes +makeExample('structural-directives/ts/src/app/unless.directive.ts', 'set')(format=".")
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`.
:marked :marked
We'll need access to the template *and* something that can render its contents. Angular sets the `myUnless` property whenever the value of the condition changes.
We access the template with a `TemplateRef`. The renderer is a `ViewContainerRef`. Because the `myUnless` property does work, it needs a setter.
We inject both into our constructor as private variables.
+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 :marked
The consumer of our directive will bind a boolean value to our directive's `myUnless` input property. Add this directive to the `!{_declsVsDirectives}` !{_array} of the !{_AppModuleVsAppComp}.
The directive adds or removes the template based on that value.
Let's add the `myUnless` property now as a setter-only property. Then create some HTML to try it.
+makeExample('structural-directives/ts/src/app/app.component.html', 'myUnless')(format=".")
+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.
:marked :marked
Nothing fancy here: if the condition is false, When the `condition` is falsy, the top (A) paragraph appears and the bottom (B) paragraph disappears.
we render the template, otherwise we clear the element content. When the `condition` is truthy, the top (A) paragraph is removed and the bottom (B) paragraph appears.
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.
figure.image-display figure.image-display
img(src='/resources/images/devguide/structural-directives/myUnless-is-true.png' alt="myUnless is true" ) img(src='/resources/images/devguide/structural-directives/unless-anim.gif' alt="UnlessDirective in action" )
: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.
a#summary
.l-main-section .l-main-section
:marked :marked
## Wrap up ## Summary
Here is the pertinent source for this chapter. 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(` +makeTabs(`
structural-directives/ts/src/app/unless.directive.ts, structural-directives/ts/src/app/app.component.ts,
structural-directives/ts/src/app/heavy-loader.component.ts, structural-directives/ts/src/app/app.component.html,
structural-directives/ts/src/app/structural-directives.component.ts, structural-directives/ts/src/app/app.component.css,
structural-directives/ts/src/app/structural-directives.component.html 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, null,
`unless.directive.ts, `app.component.ts,
heavy-loader.component.ts, app.component.html,
structural-directives.component.ts, app.component.css,
structural-directives.component.html app.module.ts,
hero.ts,
hero-switch.components.ts,
unless.directive.ts
`) `)
:marked :marked
We learned that we can manipulate our HTML layout with You learned
structural directives like `ngFor` and `ngIf` and we * that structural directives manipulate HTML layout.
wrote our own structural directive, `myUnless`, to do something similar. * 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>`.
Angular offers more sophisticated techniques for managing layout * how that works for the `NgIf`, `NgFor` and `NgSwitch` built-in directives.
such as *structural components* that can take external content * about the [_microsyntax_](#microsyntax) that expands into a [`<template>`](#template).
and incorporate that content within their own templates. * to write a [custom structural directive](#unless), `UnlessDirective`.
Tab and tab pane controls are good examples.
We'll learn about structural components in a future chapter.

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