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
11
public/docs/_examples/ngcontainer/ts/plnkr.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"description": "<ng-container>",
|
||||||
|
"basePath": "src/",
|
||||||
|
"files": [
|
||||||
|
"!**/*.d.ts",
|
||||||
|
"!**/*.js"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ngcontainer", "structural", "directives"
|
||||||
|
]
|
||||||
|
}
|
@ -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 */
|
279
public/docs/_examples/ngcontainer/ts/src/app/app.component.html
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
<!-- #docplaster -->
|
||||||
|
<!-- #docregion -->
|
||||||
|
<h1><ng-container></h1>
|
||||||
|
|
||||||
|
<!-- #docregion ngif -->
|
||||||
|
<div *ngIf="hero">{{hero.name}}</div>
|
||||||
|
<!-- #enddocregion ngif -->
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h3><ng-container> and CSS</h3>
|
||||||
|
<p>Examples demonstrating issues with rigid CSS styles.</p>
|
||||||
|
|
||||||
|
<button (click)="hero = hero ? null : heroes[0]">Toggle hero</button>
|
||||||
|
|
||||||
|
<h4>#1 <ng-container> and <p></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 <ng-container> and <p></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 <ng-container> and <p></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 <span>
|
||||||
|
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 <ng-container>
|
||||||
|
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 <template *ngIf> disappears:
|
||||||
|
<p>
|
||||||
|
<template *ngIf="showId">
|
||||||
|
Id: ({{hero.id}})
|
||||||
|
</template>
|
||||||
|
Name: {{hero.name}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
The <code>hero.id</code> in the <template [ngIf]>
|
||||||
|
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><ng-container> and layout-sensitive elements</h3>
|
||||||
|
<p>
|
||||||
|
Examples demonstrating issues with layout-sensitive elements
|
||||||
|
such as <select> and <table>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>#1 <ng-container> and <options></h4>
|
||||||
|
|
||||||
|
<p><i><select> with <span></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><select> with <ng-container></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 <ng-container> and <options></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 <ng-container></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 <span></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><ng-container> and <table></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 <ng-container> with <ng-content></h3>
|
||||||
|
|
||||||
|
<p><ng-container>Inside ng-container</ng-container></p>
|
||||||
|
<!-- #docregion ngcontainer-bare -->
|
||||||
|
<ng-container>Inside ng-container</ng-container>
|
||||||
|
<!-- #enddocregion ngcontainer-bare -->
|
||||||
|
|
||||||
|
<p><ng-content>this is an Angular parse error</ng-content></p>
|
||||||
|
<!-- #docregion ngcontent-bad -->
|
||||||
|
<!-- <ng-content>this is an Angular parse error</ng-content> -->
|
||||||
|
<!-- #enddocregion ngcontent-bad -->
|
||||||
|
<div class="code">Template parse errors:<br>
|
||||||
|
<ng-content> element cannot have content.</div>
|
||||||
|
|
||||||
|
<p>Demo of </ng-content></p>
|
||||||
|
<!-- #docregion content-comp -->
|
||||||
|
<content-comp>
|
||||||
|
Projected content
|
||||||
|
</content-comp>
|
||||||
|
<!-- #enddocregion content-comp -->
|
@ -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;
|
||||||
|
}
|
19
public/docs/_examples/ngcontainer/ts/src/app/app.module.ts
Normal 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 { }
|
@ -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 { }
|
@ -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 ];
|
13
public/docs/_examples/ngcontainer/ts/src/app/hero.ts
Normal 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'}
|
||||||
|
];
|
26
public/docs/_examples/ngcontainer/ts/src/index.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- #docregion -->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Angular <ng-container></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>
|
6
public/docs/_examples/ngcontainer/ts/src/main.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// #docregion
|
||||||
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
import { AppModule } from './app/app.module';
|
||||||
|
|
||||||
|
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||||
|
|
@ -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)');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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%;
|
||||||
|
}
|
@ -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><template> 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"><ng-container></h2>
|
||||||
|
|
||||||
|
<h4>*ngIf with a <ng-container></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><select> with <span></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><select> with <ng-container></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"><div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"></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"><div template="ngFor let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd"></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"><template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById"></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 <template></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><template></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) <p template="myUnless condition" class="code unless">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<template [myUnless]="condition">
|
||||||
|
<p class="code unless">
|
||||||
|
(A) <template [myUnless]="condition">
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
|
@ -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
|
||||||
|
}
|
@ -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 { }
|
||||||
|
@ -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
|
|
@ -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 ];
|
@ -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'}
|
||||||
|
];
|
@ -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 <ng-container></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 -->
|
@ -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 -->
|
|
@ -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
|
|
@ -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
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<structural-directives>Loading...</structural-directives>
|
<my-app>Loading...</my-apps>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -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}
|
@ -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 <template></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 ({{title}}, [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 <template></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 <template></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 <template></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 -->
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -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 { }
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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 ];
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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—directives with a template.
|
|
||||||
1. Structural directives—change the DOM layout by adding and removing DOM elements.
|
1. Components — directives with a template.
|
||||||
1. Attribute directives—change the appearance or behavior of an element.
|
1. Structural directives — change the DOM layout by adding and removing DOM elements.
|
||||||
|
1. Attribute directives — 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
|
||||||
|
@ -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:
|
||||||
|
218
public/docs/ts/latest/guide/ngcontainer.jade
Normal 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)
|
||||||
|
- [<ng-container> is excluded from DOM](#excluded)
|
||||||
|
- [<ng-container> is not <ng-content>](#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
|
||||||
|
### <ng-container> 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").
|
||||||
|
<!--template bindings={
|
||||||
|
"ng-reflect-ng-if": "true"
|
||||||
|
}-->
|
||||||
|
<!--template bindings={} -->
|
||||||
|
|
||||||
|
: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
|
||||||
|
### <ng-container> 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
|
||||||
|
### <ng-container> is not <ng-content>
|
||||||
|
|
||||||
|
**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
|
||||||
|
`)
|
@ -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 <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 <ng-container>](#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 <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—[NgIf](template-syntax.html#ngIf),
|
||||||
|
[NgFor](template-syntax.html#ngFor), and [NgSwitch...](template-syntax.html#ngSwitch)—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) components and (2) attribute directives.
|
||||||
|
|
||||||
|
A *component* manages a region of HTML in the manner of a native HTML element.
|
||||||
|
Technically it's a directive with a template.
|
||||||
|
|
||||||
|
An [*attribute* directive](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 — and all of its descendant components —
|
|
||||||
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 — 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—and all of its descendant components—tie up resources.
|
||||||
|
The performance and memory burden can be substantial, responsiveness can degrade, and the user sees nothing.
|
||||||
|
|
||||||
|
On the positive side, showing the element again is quick.
|
||||||
|
The component's previous state is preserved and ready to display.
|
||||||
|
The component doesn't re-initialize—an operation that could be expensive.
|
||||||
|
So hiding and showing is sometimes the right thing to do.
|
||||||
|
|
||||||
|
But in the absence of a compelling reason to keep them around,
|
||||||
|
your preference should be to remove DOM elements that the user can't see
|
||||||
|
and recover the unused resources with a structural directive like `NgIf` .
|
||||||
|
|
||||||
**These same considerations apply to every structural directive, whether built-in or custom.**
|
**These same considerations apply to every structural directive, whether built-in or custom.**
|
||||||
We should ask ourselves — and the users of our directives — 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 <ng-container>
|
||||||
|
|
||||||
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—typically a `<span>` or `<div>`—to
|
||||||
|
group the elements under a single _root_ is usually harmless.
|
||||||
|
_Usually_ ... but not _always_.
|
||||||
|
|
||||||
|
The grouping element may break the template appearance because CSS styles
|
||||||
|
neither expect nor accommodate the new layout.
|
||||||
|
For example, suppose you have the following paragraph layout.
|
||||||
|
+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 *<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 — 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>`.
|
||||||
|
|
||||||
|
### <ng-container> 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 ..."`, 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: 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 [<ng-container>](#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 *<template>*
|
||||||
|
|
||||||
|
The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template" target="_blank" title="MDN: Template Tag">HTML 5 <template></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 [ ]
|
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.
|
|
||||||
|
BIN
public/resources/images/devguide/ngcontainer/hero-traits-bad.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 33 KiB |
BIN
public/resources/images/devguide/template-syntax/switch-anim.gif
Normal file
After Width: | Height: | Size: 75 KiB |