docs(template-syntax/structural-directives): refresh both guides for style, accuracy, understanding (#3110)
Move details of structural directives from template-syntax to structural-directives guide Add <ng-container> to structural-directives Fix samples in both guides Touch up glossary Better conformance to google doc guidelines: we->you closes #2303, #2885
This commit is contained in:
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 ];
|
||||
@@ -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,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>
|
||||
@@ -0,0 +1,6 @@
|
||||
// #docregion
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
|
||||
@@ -1,68 +1,58 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Structural Directives', function () {
|
||||
|
||||
// tests interact - so we need beforeEach instead of beforeAll
|
||||
beforeEach(function () {
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should be able to use ngFor, ngIf and ngWhen together', function () {
|
||||
let allDivEles = element.all(by.css('structural-directives > div'));
|
||||
expect(allDivEles.get(0).getText()).toEqual('Mr. Nice');
|
||||
expect(allDivEles.get(1).getText()).toEqual('Mr. Nice');
|
||||
expect(allDivEles.get(4).getText()).toEqual('Ready');
|
||||
it('first div should show hero name with *ngIf', function () {
|
||||
const allDivs = element.all(by.tagName('div'));
|
||||
expect(allDivs.get(0).getText()).toEqual('Mr. Nice');
|
||||
});
|
||||
|
||||
it('should be able to toggle ngIf with a button', function () {
|
||||
let setConditionButtonEle = element.all(by.css('button')).get(0);
|
||||
let conditionTrueEles = element.all(by.cssContainingText('p', 'condition is true'));
|
||||
let conditionFalseEles = element.all(by.cssContainingText('p', 'condition is false'));
|
||||
expect(conditionTrueEles.count()).toBe(2, 'should be two condition true elements');
|
||||
expect(conditionFalseEles.count()).toBe(0, 'should be no condition false elements');
|
||||
setConditionButtonEle.click().then(function() {
|
||||
expect(conditionTrueEles.count()).toBe(0, 'should be no condition true elements');
|
||||
expect(conditionFalseEles.count()).toBe(2, 'should be two condition false elements');
|
||||
it('first li should show hero name with *ngFor', function () {
|
||||
const allLis = element.all(by.tagName('li'));
|
||||
expect(allLis.get(0).getText()).toEqual('Mr. Nice');
|
||||
});
|
||||
|
||||
it('ngSwitch have three <happy-hero> instances', function () {
|
||||
const happyHeroEls = element.all(by.tagName('happy-hero'));
|
||||
expect(happyHeroEls.count()).toEqual(3);
|
||||
});
|
||||
|
||||
it('should toggle *ngIf="hero" with a button', function () {
|
||||
const toggleHeroButton = element.all(by.cssContainingText('button', 'Toggle hero')).get(0);
|
||||
const paragraph = element.all(by.cssContainingText('p', 'I turned the corner'));
|
||||
expect(paragraph.get(0).getText()).toContain('I waved');
|
||||
toggleHeroButton.click().then(() => {
|
||||
expect(paragraph.get(0).getText()).not.toContain('I waved');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to compare use of ngIf with changing css visibility', function () {
|
||||
let setConditionButtonEle = element.all(by.css('button')).get(0);
|
||||
let ngIfButtonEle = element(by.cssContainingText('button', 'if | !if'));
|
||||
let ngIfParentEle = ngIfButtonEle.element(by.xpath('..'));
|
||||
let ngIfSiblingEle = ngIfParentEle.element(by.css('heavy-loader'));
|
||||
let cssButtonEle = element(by.cssContainingText('button', 'show | hide'));
|
||||
let cssSiblingEle = cssButtonEle.element(by.xpath('..')).element(by.css('heavy-loader'));
|
||||
let setConditionText: string;
|
||||
setConditionButtonEle.getText().then(function(text: string) {
|
||||
setConditionText = text;
|
||||
expect(ngIfButtonEle.isPresent()).toBe(true, 'should be able to find ngIfButton');
|
||||
expect(cssButtonEle.isPresent()).toBe(true, 'should be able to find cssButton');
|
||||
expect(ngIfParentEle.isPresent()).toBe(true, 'should be able to find ngIfButton parent');
|
||||
expect(ngIfSiblingEle.isPresent()).toBe(true, 'should be able to find ngIfButton sibling');
|
||||
expect(cssSiblingEle.isPresent()).toBe(true, 'should be able to find cssButton sibling');
|
||||
return ngIfButtonEle.click();
|
||||
}).then(function() {
|
||||
expect(ngIfSiblingEle.isPresent()).toBe(false, 'now should NOT be able to find ngIfButton sibling');
|
||||
expect(setConditionButtonEle.getText()).not.toEqual(setConditionText);
|
||||
return cssButtonEle.click();
|
||||
}).then(function() {
|
||||
expect(cssSiblingEle.isPresent()).toBe(true, 'now should still be able to find cssButton sibling');
|
||||
expect(cssSiblingEle.isDisplayed()).toBe(false, 'now cssButton sibling should NOT be visible');
|
||||
return ngIfButtonEle.click();
|
||||
}).then(function() {
|
||||
expect(setConditionButtonEle.getText()).toEqual(setConditionText);
|
||||
});
|
||||
it('should have only one "Hip!" (the other is erased)', function () {
|
||||
const paragraph = element.all(by.cssContainingText('p', 'Hip!'));
|
||||
expect(paragraph.count()).toEqual(1);
|
||||
});
|
||||
|
||||
it('should be able to use *ngIf ', function () {
|
||||
let setConditionButtonEle = element.all(by.css('button')).get(0);
|
||||
let displayEles = element.all(by.cssContainingText('p', 'Our heroes are true!'));
|
||||
expect(displayEles.count()).toBe(2, 'should be displaying two ngIf elements');
|
||||
setConditionButtonEle.click().then(function() {
|
||||
expect(displayEles.count()).toBe(0, 'should nog longer be displaying ngIf elements');
|
||||
it('myUnless should show 3 paragraph (A)s and (B)s at the start', function () {
|
||||
const paragraph = element.all(by.css('p.unless'));
|
||||
expect(paragraph.count()).toEqual(3);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
expect(paragraph.get(i).getText()).toContain('(A)');
|
||||
}
|
||||
});
|
||||
|
||||
it('myUnless should show 1 paragraph (B) after toggling condition', function () {
|
||||
const toggleConditionButton = element.all(by.cssContainingText('button', 'Toggle condition')).get(0);
|
||||
const paragraph = element.all(by.css('p.unless'));
|
||||
|
||||
toggleConditionButton.click().then(() => {
|
||||
expect(paragraph.count()).toEqual(1);
|
||||
expect(paragraph.get(0).getText()).toContain('(B)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"description": "Structural directives",
|
||||
"basePath": "src/",
|
||||
"files": ["!**/*.d.ts", "!**/*.js"],
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!app/scrap.txt"
|
||||
],
|
||||
"tags": [
|
||||
"structural", "directives", "template", "ngIf",
|
||||
"ngSwitch", "ngFor"
|
||||
|
||||
@@ -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
|
||||
import { NgModule } from '@angular/core';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { StructuralDirectivesComponent } from './structural-directives.component';
|
||||
import { UnlessDirective } from './unless.directive';
|
||||
import { HeavyLoaderComponent } from './heavy-loader.component';
|
||||
import { AppComponent } from './app.component';
|
||||
import { heroSwitchComponents } from './hero-switch.components';
|
||||
import { UnlessDirective } from './unless.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [ BrowserModule ],
|
||||
imports: [ BrowserModule, FormsModule ],
|
||||
declarations: [
|
||||
StructuralDirectivesComponent,
|
||||
UnlessDirective,
|
||||
HeavyLoaderComponent
|
||||
AppComponent,
|
||||
heroSwitchComponents,
|
||||
UnlessDirective
|
||||
],
|
||||
bootstrap: [ StructuralDirectivesComponent ]
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
@@ -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 -->
|
||||
-109
@@ -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 -->
|
||||
-19
@@ -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
|
||||
// #docregion
|
||||
// #docregion unless-declaration
|
||||
import { Directive, Input } from '@angular/core';
|
||||
// #docregion no-docs
|
||||
// #docregion skeleton
|
||||
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
|
||||
// #enddocregion unless-declaration
|
||||
import { TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
|
||||
// #docregion unless-declaration
|
||||
@Directive({ selector: '[myUnless]' })
|
||||
// #enddocregion skeleton
|
||||
/**
|
||||
* Add the template content to the DOM unless the condition is true.
|
||||
// #enddocregion no-docs
|
||||
*
|
||||
* If the expression assigned to `myUnless` evaluates to a truthy value
|
||||
* then the templated elements are removed removed from the DOM,
|
||||
* the templated elements are (re)inserted into the DOM.
|
||||
*
|
||||
* <div *ngUnless="errorCount" class="success">
|
||||
* Congrats! Everything is great!
|
||||
* </div>
|
||||
*
|
||||
* ### Syntax
|
||||
* *
|
||||
* - `<div *myUnless="condition">...</div>`
|
||||
* - `<div template="myUnless condition">...</div>`
|
||||
* - `<template [myUnless]="condition"><div>...</div></template>`
|
||||
*
|
||||
// #docregion no-docs
|
||||
*/
|
||||
// #docregion skeleton
|
||||
@Directive({ selector: '[myUnless]'})
|
||||
export class UnlessDirective {
|
||||
// #enddocregion unless-declaration
|
||||
// #enddocregion skeleton
|
||||
private hasView = false;
|
||||
|
||||
// #docregion unless-constructor
|
||||
// #docregion ctor
|
||||
constructor(
|
||||
private templateRef: TemplateRef<any>,
|
||||
private viewContainer: ViewContainerRef
|
||||
) { }
|
||||
// #enddocregion unless-constructor
|
||||
private viewContainer: ViewContainerRef) { }
|
||||
// #enddocregion ctor
|
||||
|
||||
// #docregion unless-set
|
||||
// #docregion set
|
||||
@Input() set myUnless(condition: boolean) {
|
||||
if (!condition) {
|
||||
if (!condition && !this.hasView) {
|
||||
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
} else {
|
||||
this.hasView = true;
|
||||
} else if (condition && this.hasView) {
|
||||
this.viewContainer.clear();
|
||||
this.hasView = false;
|
||||
}
|
||||
}
|
||||
// #enddocregion unless-set
|
||||
// #docregion unless-declaration
|
||||
// #enddocregion set
|
||||
// #docregion skeleton
|
||||
}
|
||||
// #enddocregion unless-declaration
|
||||
// #enddocregion skeleton
|
||||
// #enddocregion no-docs
|
||||
// #enddocregion
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<structural-directives>Loading...</structural-directives>
|
||||
<my-app>Loading...</my-apps>
|
||||
</body>
|
||||
|
||||
</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>
|
||||
<h1>Template Syntax</h1>
|
||||
<a href="#interpolation">Interpolation</a><br>
|
||||
<a href="#expression-context">Expression context</a><br>
|
||||
<a href="#statement-context">Statement context</a><br>
|
||||
<a href="#mental-model">Mental Model</a><br>
|
||||
<a href="#buttons">Buttons</a><br>
|
||||
<a href="#prop-vs-attrib">Properties vs. Attributes</a><br>
|
||||
@@ -22,15 +24,14 @@
|
||||
<a href="#ngClass">NgClass Binding</a><br>
|
||||
<a href="#ngStyle">NgStyle Binding</a><br>
|
||||
<a href="#ngIf">NgIf</a><br>
|
||||
<a href="#ngSwitch">NgSwitch</a><br>
|
||||
<a href="#ngFor">NgFor</a><br>
|
||||
<div style="margin-left:8px">
|
||||
<a href="#ngFor-index">NgFor with index</a><br>
|
||||
<a href="#ngFor-trackBy">NgFor with trackBy</a><br>
|
||||
</div>
|
||||
<a href="#ngSwitch">NgSwitch</a><br>
|
||||
</div>
|
||||
<br>
|
||||
<a href="#star-prefix">* prefix and <template></a><br>
|
||||
<a href="#ref-vars">Template reference variables</a><br>
|
||||
<a href="#inputs-and-outputs">Inputs and outputs</a><br>
|
||||
<a href="#pipes">Pipes</a><br>
|
||||
@@ -41,7 +42,7 @@
|
||||
<hr><h2 id="interpolation">Interpolation</h2>
|
||||
|
||||
<!-- #docregion first-interpolation -->
|
||||
<p>My current hero is {{currentHero.firstName}}</p>
|
||||
<p>My current hero is {{currentHero.name}}</p>
|
||||
<!-- #enddocregion first-interpolation -->
|
||||
|
||||
<!-- #docregion title+image -->
|
||||
@@ -63,6 +64,68 @@
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<hr><h2 id="expression-context">Expression context</h2>
|
||||
|
||||
<p>Component expression context ({{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 -->
|
||||
<hr><h2 id="mental-model">New Mental Model</h2>
|
||||
|
||||
@@ -105,16 +168,17 @@
|
||||
<!-- #docregion event-binding-syntax-1 -->
|
||||
<button (click) = "onSave()">Save</button>
|
||||
<hero-detail (deleteRequest)="deleteHero()"></hero-detail>
|
||||
<div (myClick)="clicked=$event">click me</div>
|
||||
<div (myClick)="clicked=$event" clickable>click me</div>
|
||||
<!-- #enddocregion event-binding-syntax-1 -->
|
||||
{{clicked}}
|
||||
<br><br>
|
||||
|
||||
<div>
|
||||
Hero Name:
|
||||
<!-- #docregion 2-way-binding-syntax-1 -->
|
||||
<input [(ngModel)]="heroName">
|
||||
<!-- #enddocregion 2-way-binding-syntax-1 -->
|
||||
Hero Name: {{heroName}}
|
||||
{{heroName}}
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
@@ -205,6 +269,10 @@ button</button>
|
||||
<!-- #enddocregion property-binding-vs-interpolation -->
|
||||
|
||||
<!-- #docregion property-binding-vs-interpolation-sanitization -->
|
||||
<!--
|
||||
Angular generates warnings for these two lines as it sanitizes them
|
||||
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
|
||||
-->
|
||||
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
|
||||
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
|
||||
<!-- #enddocregion property-binding-vs-interpolation-sanitization -->
|
||||
@@ -307,7 +375,7 @@ button</button>
|
||||
<!-- #docregion event-binding-3 -->
|
||||
<!-- `myClick` is an event on the custom `ClickDirective` -->
|
||||
<!-- #docregion myClick -->
|
||||
<div (myClick)="clickMessage=$event">click with myClick</div>
|
||||
<div (myClick)="clickMessage=$event" clickable>click with myClick</div>
|
||||
<!-- #enddocregion myClick -->
|
||||
<!-- #enddocregion event-binding-3 -->
|
||||
{{clickMessage}}
|
||||
@@ -326,26 +394,25 @@ button</button>
|
||||
</big-hero-detail>
|
||||
|
||||
<!-- #docregion event-binding-bubbling -->
|
||||
<div class="parent-div" (click)="onClickMe($event)">Click me
|
||||
<div class="parent-div" (click)="onClickMe($event)" clickable>Click me
|
||||
<div class="child-div">Click me too!</div>
|
||||
</div>
|
||||
<!-- #enddocregion event-binding-bubbling -->
|
||||
<br><br>
|
||||
|
||||
<!-- #docregion event-binding-no-propagation -->
|
||||
<!-- Will save only once -->
|
||||
<div (click)="onSave()">
|
||||
<div (click)="onSave()" clickable>
|
||||
<button (click)="onSave()">Save, no propagation</button>
|
||||
</div>
|
||||
<!-- #enddocregion event-binding-no-propagation -->
|
||||
<br><br>
|
||||
|
||||
<!-- #docregion event-binding-propagation -->
|
||||
<!-- Will save twice -->
|
||||
<div (click)="onSave()">
|
||||
<div (click)="onSave()" clickable>
|
||||
<button (click)="onSave() || true">Save w/ propagation</button>
|
||||
</div>
|
||||
<!-- #enddocregion event-binding-propagation -->
|
||||
<br><br>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<hr><h2 id="two-way">Two-way Binding</h2>
|
||||
@@ -363,7 +430,6 @@ button</button>
|
||||
<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>
|
||||
<!-- #enddocregion two-way-2 -->
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
@@ -371,38 +437,37 @@ button</button>
|
||||
passing the changed display value to the event handler via `$event` -->
|
||||
<hr><h2 id="ngModel">NgModel (two-way) Binding</h2>
|
||||
|
||||
<h3>Result: {{currentHero.firstName}}</h3>
|
||||
<h3>Result: {{currentHero.name}}</h3>
|
||||
|
||||
<!-- #docregion without-NgModel -->
|
||||
<input [value]="currentHero.firstName"
|
||||
(input)="currentHero.firstName=$event.target.value" >
|
||||
<input [value]="currentHero.name"
|
||||
(input)="currentHero.name=$event.target.value" >
|
||||
<!-- #enddocregion without-NgModel -->
|
||||
without NgModel
|
||||
<br>
|
||||
<!-- #docregion NgModel-1 -->
|
||||
<input [(ngModel)]="currentHero.firstName">
|
||||
<input [(ngModel)]="currentHero.name">
|
||||
<!-- #enddocregion NgModel-1 -->
|
||||
[(ngModel)]
|
||||
<br>
|
||||
<!-- #docregion NgModel-2 -->
|
||||
<input bindon-ngModel="currentHero.firstName">
|
||||
<input bindon-ngModel="currentHero.name">
|
||||
<!-- #enddocregion NgModel-2 -->
|
||||
bindon-ngModel
|
||||
<br>
|
||||
<!-- #docregion NgModel-3 -->
|
||||
<input
|
||||
[ngModel]="currentHero.firstName"
|
||||
(ngModelChange)="currentHero.firstName=$event">
|
||||
[ngModel]="currentHero.name"
|
||||
(ngModelChange)="currentHero.name=$event">
|
||||
<!-- #enddocregion NgModel-3 -->
|
||||
(ngModelChange) = "...firstName=$event"
|
||||
(ngModelChange) = "...name=$event"
|
||||
<br>
|
||||
<!-- #docregion NgModel-4 -->
|
||||
<input
|
||||
[ngModel]="currentHero.firstName"
|
||||
(ngModelChange)="setUpperCaseFirstName($event)">
|
||||
[ngModel]="currentHero.name"
|
||||
(ngModelChange)="setUppercaseName($event)">
|
||||
<!-- #enddocregion NgModel-4 -->
|
||||
(ngModelChange) = "setUpperCaseFirstName($event)"
|
||||
<br>
|
||||
(ngModelChange) = "setUppercaseName($event)"
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
@@ -417,7 +482,7 @@ bindon-ngModel
|
||||
<!-- not used in chapter -->
|
||||
<br>
|
||||
<label>saveable <input type="checkbox" [(ngModel)]="canSave"></label> |
|
||||
<label>modified: <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged"></label> |
|
||||
<label>modified: <input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged"></label> |
|
||||
<label>special: <input type="checkbox" [(ngModel)]="isSpecial"></label>
|
||||
<button (click)="setCurrentClasses()">Refresh currentClasses</button>
|
||||
<br><br>
|
||||
@@ -462,7 +527,6 @@ bindon-ngModel
|
||||
This div should be {{ canSave ? "italic": "plain"}},
|
||||
{{ isUnchanged ? "normal weight" : "bold" }} and,
|
||||
{{ isSpecial ? "extra large": "normal size"}} after clicking "refresh".</div>
|
||||
<br>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
@@ -470,21 +534,17 @@ bindon-ngModel
|
||||
<hr><h2 id="ngIf">NgIf Binding</h2>
|
||||
|
||||
<!-- #docregion NgIf-1 -->
|
||||
<div *ngIf="currentHero">Hello, {{currentHero.firstName}}</div>
|
||||
<hero-detail *ngIf="isActive"></hero-detail>
|
||||
<!-- #enddocregion NgIf-1 -->
|
||||
|
||||
<!-- #docregion NgIf-2 -->
|
||||
<!-- because of the ngIf guard
|
||||
`nullHero.firstName` never has a chance to fail -->
|
||||
<div *ngIf="nullHero">Hello, {{nullHero.firstName}}</div>
|
||||
|
||||
<!-- Hero Detail is not in the DOM because isActive is false-->
|
||||
<hero-detail *ngIf="isActive"></hero-detail>
|
||||
<div *ngIf="currentHero">Hello, {{currentHero.name}}</div>
|
||||
<div *ngIf="nullHero">Hello, {{nullHero.name}}</div>
|
||||
<!-- #enddocregion NgIf-2 -->
|
||||
|
||||
<!-- NgIf binding with template (no *) -->
|
||||
|
||||
<template [ngIf]="currentHero">Add {{currentHero.firstName}} with template</template>
|
||||
<template [ngIf]="currentHero">Add {{currentHero.name}} with template</template>
|
||||
|
||||
<!-- Does not show because isActive is false! -->
|
||||
<div>Hero Detail removed from DOM (via template) because isActive is false</div>
|
||||
@@ -506,170 +566,117 @@ bindon-ngModel
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- NgSwitch binding -->
|
||||
<hr><h2 id="ngSwitch">NgSwitch Binding</h2>
|
||||
|
||||
<fieldset #toePicker (click)="toeChooser(toePicker)" >
|
||||
<input type="radio" name="toes" value="Eenie">Eenie
|
||||
<input type="radio" name="toes" value="Meanie">Meanie
|
||||
<input type="radio" name="toes" value="Miney">Miney
|
||||
<input type="radio" name="toes" value="Moe">Moe
|
||||
<input type="radio" name="toes" value="???">???
|
||||
</fieldset>
|
||||
|
||||
<div class="toe">
|
||||
<div *ngIf="!toeChoice">Pick a toe</div>
|
||||
<div *ngIf="toeChoice">
|
||||
You picked ...
|
||||
<!-- #docregion NgSwitch, NgSwitch-expanded -->
|
||||
<span [ngSwitch]="toeChoice">
|
||||
<!-- #enddocregion NgSwitch -->
|
||||
|
||||
<!-- with *NgSwitch -->
|
||||
<!-- #docregion NgSwitch -->
|
||||
<span *ngSwitchCase="'Eenie'">Eenie</span>
|
||||
<span *ngSwitchCase="'Meanie'">Meanie</span>
|
||||
<span *ngSwitchCase="'Miney'">Miney</span>
|
||||
<span *ngSwitchCase="'Moe'">Moe</span>
|
||||
<span *ngSwitchDefault>other</span>
|
||||
<!-- #enddocregion NgSwitch -->
|
||||
|
||||
<!-- with <template> -->
|
||||
<template [ngSwitchCase]="'Eenie'"><span>Eenie</span></template>
|
||||
<template [ngSwitchCase]="'Meanie'"><span>Meanie</span></template>
|
||||
<template [ngSwitchCase]="'Miney'"><span>Miney</span></template>
|
||||
<template [ngSwitchCase]="'Moe'"><span>Moe</span></template>
|
||||
<template ngSwitchDefault><span>other</span></template>
|
||||
|
||||
<!-- #docregion NgSwitch -->
|
||||
</span>
|
||||
<!-- #enddocregion NgSwitch, NgSwitch-expanded -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- NgFor binding -->
|
||||
<hr><h2 id="ngFor">NgFor Binding</h2>
|
||||
|
||||
<div class="box">
|
||||
<!-- #docregion NgFor-1 -->
|
||||
<div *ngFor="let hero of heroes">{{hero.fullName}}</div>
|
||||
<!-- #enddocregion NgFor-1 -->
|
||||
<!-- #docregion NgFor-1, NgFor-1-2 -->
|
||||
<div *ngFor="let hero of heroes">{{hero.name}}</div>
|
||||
<!-- #enddocregion NgFor-1, NgFor-1-2 -->
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="box">
|
||||
<!-- *ngFor w/ hero-detail Component -->
|
||||
<!-- #docregion NgFor-2 -->
|
||||
<!-- #docregion NgFor-2, NgFor-1-2 -->
|
||||
<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>
|
||||
<!-- #enddocregion NgFor-2 -->
|
||||
<!-- #enddocregion NgFor-2, NgFor-1-2 -->
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<h4 id="ngFor-index">NgFor with index</h4>
|
||||
<h4 id="ngFor-index">*ngFor with index</h4>
|
||||
<p>with <i>semi-colon</i> separator</p>
|
||||
<div class="box">
|
||||
<!-- #docregion NgFor-3 -->
|
||||
<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.fullName}}</div>
|
||||
<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.name}}</div>
|
||||
<!-- #enddocregion NgFor-3 -->
|
||||
</div>
|
||||
|
||||
<p>with <i>comma</i> separator</p>
|
||||
<div class="box">
|
||||
<!-- Ex: "1 - Hercules Son of Zeus"" -->
|
||||
<div *ngFor="let hero of heroes, let i=index">{{i + 1}} - {{hero.fullName}}</div>
|
||||
<!-- Ex: "1 - Hercules" -->
|
||||
<div *ngFor="let hero of heroes, let i=index">{{i + 1}} - {{hero.name}}</div>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<h4 id="ngFor-trackBy">NgForTrackBy</h4>
|
||||
<button (click)="refreshHeroes()">Refresh heroes</button>
|
||||
<p>First hero: <input [(ngModel)]="heroes[0].firstName"></p>
|
||||
<h4 id="ngFor-trackBy">*ngFor trackBy</h4>
|
||||
<button (click)="resetHeroes()">Reset heroes</button>
|
||||
<button (click)="changeIds()">Change ids</button>
|
||||
<button (click)="clearTrackByCounts()">Clear counts</button>
|
||||
|
||||
<p><i>without</i> trackBy</p>
|
||||
<div #noTrackBy class="box">
|
||||
<!-- #docregion NgForTrackBy-1 -->
|
||||
<div *ngFor="let hero of heroes">({{hero.id}}) {{hero.fullName}}</div>
|
||||
<!-- #enddocregion NgForTrackBy-1 -->
|
||||
</div>
|
||||
<div id="noTrackByCnt" *ngIf="heroesNoTrackByChangeCount" style="background-color:bisque">
|
||||
Hero DOM elements change #<span style="background-color:gold">{{heroesNoTrackByChangeCount}}</span> without trackBy
|
||||
<div class="box">
|
||||
<div #noTrackBy *ngFor="let hero of heroes">({{hero.id}}) {{hero.name}}</div>
|
||||
|
||||
<div id="noTrackByCnt" *ngIf="heroesNoTrackByCount" >
|
||||
Hero DOM elements change #{{heroesNoTrackByCount}} without trackBy
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>with trackBy and <i>semi-colon</i> separator</p>
|
||||
<div #withTrackBy class="box">
|
||||
<!-- #docregion NgForTrackBy-2 -->
|
||||
<div *ngFor="let hero of heroes; trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
|
||||
<!-- #enddocregion NgForTrackBy-2 -->
|
||||
<p>with trackBy</p>
|
||||
<div class="box">
|
||||
<div #withTrackBy *ngFor="let hero of heroes; trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
|
||||
|
||||
<div id="withTrackByCnt" *ngIf="heroesWithTrackByCount">
|
||||
Hero DOM elements change #{{heroesWithTrackByCount}} with trackBy
|
||||
</div>
|
||||
</div>
|
||||
<div id="withTrackByCnt" *ngIf="heroesWithTrackByChangeCount" style="background-color:bisque">
|
||||
Hero DOM elements change #<span style="background-color:gold">{{heroesWithTrackByChangeCount}}</span> with trackBy
|
||||
|
||||
<br><br><br>
|
||||
|
||||
<p>with trackBy and <i>semi-colon</i> separator</p>
|
||||
<div class="box">
|
||||
<!-- #docregion trackBy -->
|
||||
<div *ngFor="let hero of heroes; trackBy: trackByHeroes">
|
||||
({{hero.id}}) {{hero.name}}
|
||||
</div>
|
||||
<!-- #enddocregion trackBy -->
|
||||
</div>
|
||||
|
||||
<p>with trackBy and <i>comma</i> separator</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let hero of heroes, trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
|
||||
<div *ngFor="let hero of heroes, trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
|
||||
</div>
|
||||
|
||||
<p>with trackBy and <i>space</i> separator</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let hero of heroes trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
|
||||
<div *ngFor="let hero of heroes trackBy: trackByHeroes">({{hero.id}}) {{hero.name}}</div>
|
||||
</div>
|
||||
|
||||
<p>with <i>generic</i> trackById function</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let hero of heroes, trackBy:trackById">({{hero.id}}) {{hero.fullName}}</div>
|
||||
<div *ngFor="let hero of heroes, trackBy: trackById">({{hero.id}}) {{hero.name}}</div>
|
||||
</div>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- * and template -->
|
||||
<hr><h2 id="star-prefix">* prefix and <template></h2>
|
||||
<!-- NgSwitch binding -->
|
||||
<hr><h2 id="ngSwitch">NgSwitch Binding</h2>
|
||||
|
||||
<h3>*ngIf expansion</h3>
|
||||
<p><i>*ngIf</i></p>
|
||||
<!-- #docregion Template-1 -->
|
||||
<hero-detail *ngIf="currentHero" [hero]="currentHero"></hero-detail>
|
||||
<!-- #enddocregion Template-1 -->
|
||||
<div>Pick your favorite hero</div>
|
||||
<p>
|
||||
<ng-container *ngFor="let h of heroes">
|
||||
<label>
|
||||
<input type="radio" name="heroes" [(ngModel)]="currentHero" [value]="h">{{h.name}}
|
||||
</label>
|
||||
</ng-container>
|
||||
</p>
|
||||
|
||||
<p><i>expand to template = "..."</i></p>
|
||||
<!-- #docregion Template-2a -->
|
||||
<hero-detail template="ngIf:currentHero" [hero]="currentHero"></hero-detail>
|
||||
<!-- #enddocregion Template-2a -->
|
||||
|
||||
<p><i>expand to <template></i></p>
|
||||
<!-- #docregion Template-2 -->
|
||||
<template [ngIf]="currentHero">
|
||||
<hero-detail [hero]="currentHero"></hero-detail>
|
||||
</template>
|
||||
<!-- #enddocregion Template-2 -->
|
||||
|
||||
<h3>*ngFor expansion</h3>
|
||||
<p><i>*ngFor</i></p>
|
||||
<!-- *ngFor w/ hero-detail Component -->
|
||||
<!-- #docregion Template-3a -->
|
||||
<hero-detail *ngFor="let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>
|
||||
<!-- #enddocregion Template-3a -->
|
||||
|
||||
<p><i>expand to template = "..."</i></p>
|
||||
<div class="box">
|
||||
<!-- ngFor w/ hero-detail Component and a template "attribute" directive -->
|
||||
<!-- #docregion Template-3 -->
|
||||
<hero-detail template="ngFor let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>
|
||||
<!-- #enddocregion Template-3 -->
|
||||
</div>
|
||||
|
||||
<p><i>expand to <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 -->
|
||||
<!-- #docregion NgSwitch -->
|
||||
<div [ngSwitch]="currentHero.emotion">
|
||||
<happy-hero *ngSwitchCase="'happy'" [hero]="currentHero"></happy-hero>
|
||||
<sad-hero *ngSwitchCase="'sad'" [hero]="currentHero"></sad-hero>
|
||||
<confused-hero *ngSwitchCase="'confused'" [hero]="currentHero"></confused-hero>
|
||||
<!-- #enddocregion NgSwitch -->
|
||||
<!-- #docregion NgSwitch-div -->
|
||||
<div *ngSwitchCase="'confused'">Are you as confused as {{currentHero.name}}?</div>
|
||||
<!-- #enddocregion NgSwitch-div -->
|
||||
<!-- #docregion NgSwitch -->
|
||||
<unknown-hero *ngSwitchDefault [hero]="currentHero"></unknown-hero>
|
||||
</div>
|
||||
<!-- #enddocregion NgSwitch -->
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
@@ -677,34 +684,27 @@ bindon-ngModel
|
||||
<hr><h2 id="ref-vars">Template reference variables</h2>
|
||||
|
||||
<!-- #docregion ref-phone -->
|
||||
<!-- phone refers to the input element; pass its `value` to an event handler -->
|
||||
<!-- #docregion ref-var -->
|
||||
<input #phone placeholder="phone number">
|
||||
<button (click)="callPhone(phone.value)">Call</button>
|
||||
<!-- #enddocregion ref-var -->
|
||||
|
||||
<!-- fax refers to the input element; pass its `value` to an event handler -->
|
||||
<input ref-fax placeholder="fax number">
|
||||
<button (click)="callFax(fax.value)">Fax</button>
|
||||
<!-- lots of other elements -->
|
||||
|
||||
<!-- phone refers to the input element; pass its `value` to an event handler -->
|
||||
<button (click)="callPhone(phone.value)">Call</button>
|
||||
<!-- #enddocregion ref-phone -->
|
||||
|
||||
<h4>Example Form</h4>
|
||||
<!-- #docregion ref-form -->
|
||||
<!-- #docregion ref-form-a -->
|
||||
<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm">
|
||||
<!-- #enddocregion ref-form-a -->
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input class="form-control" name="name" required [(ngModel)]="currentHero.firstName">
|
||||
</div>
|
||||
<!-- #docregion ref-form-a -->
|
||||
<button type="submit" [disabled]="!theForm.form.valid">Submit</button>
|
||||
</form>
|
||||
<!-- #enddocregion ref-form-a -->
|
||||
<!-- #enddocregion ref-form -->
|
||||
<br><br>
|
||||
<!-- #docregion ref-fax -->
|
||||
<input ref-fax placeholder="fax number">
|
||||
<button (click)="callFax(fax.value)">Fax</button>
|
||||
<!-- #enddocregion ref-fax -->
|
||||
|
||||
<!-- btn refers to the button element; show its disabled state -->
|
||||
<button #btn disabled [innerHTML]="'disabled by attribute: '+btn.disabled"></button>
|
||||
|
||||
<h4>Example Form</h4>
|
||||
<hero-form [hero]="currentHero"></hero-form>
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- inputs and output -->
|
||||
@@ -720,7 +720,7 @@ bindon-ngModel
|
||||
</hero-detail>
|
||||
<!-- #enddocregion io-2 -->
|
||||
|
||||
<div (myClick)="clickMessage2=$event">myClick2</div>
|
||||
<div (myClick)="clickMessage2=$event" clickable>myClick2</div>
|
||||
{{clickMessage2}}
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
@@ -769,43 +769,42 @@ bindon-ngModel
|
||||
|
||||
<div>
|
||||
<!-- #docregion safe-2 -->
|
||||
The current hero's name is {{currentHero?.firstName}}
|
||||
The current hero's name is {{currentHero?.name}}
|
||||
<!-- #enddocregion safe-2 -->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- #docregion safe-3 -->
|
||||
The current hero's name is {{currentHero.firstName}}
|
||||
The current hero's name is {{currentHero.name}}
|
||||
<!-- #enddocregion safe-3 -->
|
||||
</div>
|
||||
|
||||
|
||||
<!--
|
||||
The null hero's name is {{nullHero.firstName}}
|
||||
The null hero's name is {{nullHero.name}}
|
||||
|
||||
See console log:
|
||||
TypeError: Cannot read property 'firstName' of null in [null]
|
||||
TypeError: Cannot read property 'name' of null in [null]
|
||||
-->
|
||||
|
||||
<!-- #docregion safe-4 -->
|
||||
<!--No hero, div not displayed, no error -->
|
||||
<div *ngIf="nullHero">The null hero's name is {{nullHero.firstName}}</div>
|
||||
<div *ngIf="nullHero">The null hero's name is {{nullHero.name}}</div>
|
||||
<!-- #enddocregion safe-4 -->
|
||||
|
||||
<div>
|
||||
<!-- #docregion safe-5 -->
|
||||
The null hero's name is {{nullHero && nullHero.firstName}}
|
||||
The null hero's name is {{nullHero && nullHero.name}}
|
||||
<!-- #enddocregion safe-5 -->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- #docregion safe-6 -->
|
||||
<!-- No hero, no problem! -->
|
||||
The null hero's name is {{nullHero?.firstName}}
|
||||
The null hero's name is {{nullHero?.name}}
|
||||
<!-- #enddocregion safe-6 -->
|
||||
</div>
|
||||
|
||||
|
||||
<a class="to-toc" href="#toc">top</a>
|
||||
|
||||
<!-- TODO: discuss this in the Style binding section -->
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// #docplaster
|
||||
|
||||
import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
@@ -19,20 +18,26 @@ export enum Color {Red, Green, Blue};
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html'
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: [ './app.component.css' ]
|
||||
})
|
||||
export class AppComponent implements AfterViewInit, OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
this.refreshHeroes();
|
||||
this.resetHeroes();
|
||||
this.setCurrentClasses();
|
||||
this.setCurrentStyles();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.detectNgForTrackByEffects();
|
||||
// Detect effects of NgForTrackBy
|
||||
trackChanges(this.heroesNoTrackBy, () => this.heroesNoTrackByCount += 1);
|
||||
trackChanges(this.heroesWithTrackBy, () => this.heroesWithTrackByCount += 1);
|
||||
}
|
||||
|
||||
@ViewChildren('noTrackBy') heroesNoTrackBy: QueryList<ElementRef>;
|
||||
@ViewChildren('withTrackBy') heroesWithTrackBy: QueryList<ElementRef>;
|
||||
|
||||
actionName = 'Go for it';
|
||||
alert = alerter;
|
||||
badCurly = 'bad curly';
|
||||
@@ -42,20 +47,40 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||
callPhone(value: string) {this.alert(`Calling ${value} ...`); }
|
||||
canSave = true;
|
||||
|
||||
changeIds() {
|
||||
this.resetHeroes();
|
||||
this.heroes.forEach(h => h.id += 10 * this.heroIdIncrement++);
|
||||
this.heroesWithTrackByCountReset = -1;
|
||||
}
|
||||
|
||||
clearTrackByCounts() {
|
||||
const trackByCountReset = this.heroesWithTrackByCountReset;
|
||||
this.resetHeroes();
|
||||
this.heroesNoTrackByCount = -1;
|
||||
this.heroesWithTrackByCount = trackByCountReset;
|
||||
this.heroIdIncrement = 1;
|
||||
}
|
||||
|
||||
clicked = '';
|
||||
clickMessage = '';
|
||||
clickMessage2 = '';
|
||||
|
||||
Color = Color;
|
||||
color = Color.Red;
|
||||
colorToggle() {this.color = (this.color === Color.Red) ? Color.Blue : Color.Red; }
|
||||
|
||||
currentHero = Hero.MockHeroes[0];
|
||||
currentHero: Hero;
|
||||
|
||||
deleteHero(hero: Hero) {
|
||||
this.alert('Deleted hero: ' + (hero && hero.firstName));
|
||||
this.alert(`Delete ${hero ? hero.name : 'the hero'}.`);
|
||||
}
|
||||
|
||||
// #docregion evil-title
|
||||
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
|
||||
// #enddocregion evil-title
|
||||
|
||||
fontSizePx = 16;
|
||||
|
||||
title = 'Template Syntax';
|
||||
|
||||
getStyles(el: Element) {
|
||||
@@ -69,22 +94,26 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||
|
||||
getVal() { return this.val; }
|
||||
|
||||
hero: Hero; // defined to demonstrate template context precedence
|
||||
heroes: Hero[];
|
||||
|
||||
// trackBy change counting
|
||||
heroesNoTrackByCount = 0;
|
||||
heroesWithTrackByCount = 0;
|
||||
heroesWithTrackByCountReset = 0;
|
||||
|
||||
heroIdIncrement = 1;
|
||||
|
||||
// heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png';
|
||||
// Public Domain terms of use: http://www.wpclipart.com/terms.html
|
||||
heroImageUrl = 'images/hero.png';
|
||||
|
||||
// iconUrl = 'https://angular.io/resources/images/logos/standard/shield-large.png';
|
||||
clicked = '';
|
||||
clickMessage = '';
|
||||
clickMessage2 = '';
|
||||
iconUrl = 'images/ng-logo.png';
|
||||
isActive = false;
|
||||
isSpecial = true;
|
||||
isUnchanged = true;
|
||||
|
||||
nullHero: Hero = null; // or undefined
|
||||
nullHero: Hero = null;
|
||||
|
||||
onCancel(event: KeyboardEvent) {
|
||||
let evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).innerHTML : '';
|
||||
@@ -101,26 +130,20 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||
this.alert('Saved.' + evtMsg);
|
||||
}
|
||||
|
||||
onSubmit(form: NgForm) {
|
||||
let evtMsg = form.valid ?
|
||||
' Form value is ' + JSON.stringify(form.value) :
|
||||
' Form is invalid';
|
||||
this.alert('Form submitted.' + evtMsg);
|
||||
}
|
||||
onSubmit() { /* referenced but not used */}
|
||||
|
||||
product = {
|
||||
name: 'frimfram',
|
||||
price: 42
|
||||
};
|
||||
|
||||
// #docregion refresh-heroes
|
||||
// update this.heroes with fresh set of cloned heroes
|
||||
refreshHeroes() {
|
||||
this.heroes = Hero.MockHeroes.map(hero => Hero.clone(hero));
|
||||
// updates with fresh set of cloned heroes
|
||||
resetHeroes() {
|
||||
this.heroes = Hero.heroes.map(hero => hero.clone());
|
||||
this.currentHero = this.heroes[0];
|
||||
this.heroesWithTrackByCountReset = 0;
|
||||
}
|
||||
// #enddocregion refresh-heroes
|
||||
|
||||
// #docregion same-as-it-ever-was
|
||||
private samenessCount = 5;
|
||||
moreOfTheSame() { this.samenessCount++; };
|
||||
get sameAsItEverWas() {
|
||||
@@ -131,11 +154,9 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||
// return {id:id, text: 'same as it ever was ...'};
|
||||
// });
|
||||
}
|
||||
// #enddocregion same-as-it-ever-was
|
||||
|
||||
setUpperCaseFirstName(firstName: string) {
|
||||
// console.log(firstName);
|
||||
this.currentHero.firstName = firstName.toUpperCase();
|
||||
setUppercaseName(name: string) {
|
||||
this.currentHero.name = name.toUpperCase();
|
||||
}
|
||||
|
||||
// #docregion setClasses
|
||||
@@ -162,69 +183,31 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||
}
|
||||
// #enddocregion setStyles
|
||||
|
||||
toeChoice = '';
|
||||
toeChooser(picker: HTMLFieldSetElement) {
|
||||
let choices = picker.children;
|
||||
for (let i = 0; i < choices.length; i++) {
|
||||
let choice = <HTMLInputElement>choices[i];
|
||||
if (choice.checked) {return this.toeChoice = choice.value; }
|
||||
}
|
||||
}
|
||||
|
||||
// #docregion trackByHeroes
|
||||
trackByHeroes(index: number, hero: Hero) { return hero.id; }
|
||||
trackByHeroes(index: number, hero: Hero): number { return hero.id; }
|
||||
// #enddocregion trackByHeroes
|
||||
|
||||
// #docregion trackById
|
||||
trackById(index: number, item: any): string { return item['id']; }
|
||||
trackById(index: number, item: any): number { return item['id']; }
|
||||
// #enddocregion trackById
|
||||
|
||||
val = 2;
|
||||
// villainImageUrl = 'http://www.clker.com/cliparts/u/s/y/L/x/9/villain-man-hi.png'
|
||||
// Public Domain terms of use http://www.clker.com/disclaimer.html
|
||||
villainImageUrl = 'images/villain.png';
|
||||
|
||||
|
||||
//////// Detect effects of NgForTrackBy ///////////////
|
||||
@ViewChildren('noTrackBy') childrenNoTrackBy: QueryList<ElementRef>;
|
||||
@ViewChildren('withTrackBy') childrenWithTrackBy: QueryList<ElementRef>;
|
||||
|
||||
private _oldNoTrackBy: HTMLElement[];
|
||||
private _oldWithTrackBy: HTMLElement[];
|
||||
|
||||
heroesNoTrackByChangeCount = 0;
|
||||
heroesWithTrackByChangeCount = 0;
|
||||
|
||||
private detectNgForTrackByEffects() {
|
||||
this._oldNoTrackBy = toArray(this.childrenNoTrackBy);
|
||||
this._oldWithTrackBy = toArray(this.childrenWithTrackBy);
|
||||
|
||||
this.childrenNoTrackBy.changes.subscribe((changes: any) => {
|
||||
let newNoTrackBy = toArray(changes);
|
||||
let isSame = this._oldNoTrackBy.every((v: any, i: number) => v === newNoTrackBy[i]);
|
||||
if (!isSame) {
|
||||
this._oldNoTrackBy = newNoTrackBy;
|
||||
this.heroesNoTrackByChangeCount++;
|
||||
}
|
||||
});
|
||||
|
||||
this.childrenWithTrackBy.changes.subscribe((changes: any) => {
|
||||
let newWithTrackBy = toArray(changes);
|
||||
let isSame = this._oldWithTrackBy.every((v: any, i: number) => v === newWithTrackBy[i]);
|
||||
if (!isSame) {
|
||||
this._oldWithTrackBy = newWithTrackBy;
|
||||
this.heroesWithTrackByChangeCount++;
|
||||
}
|
||||
});
|
||||
}
|
||||
///////////////////
|
||||
|
||||
}
|
||||
|
||||
// helper to convert viewChildren to an array of HTMLElements
|
||||
function toArray(viewChildren: QueryList<ElementRef>) {
|
||||
let result: HTMLElement[] = [];
|
||||
let children = viewChildren.toArray()[0].nativeElement.children;
|
||||
for (let i = 0; i < children.length; i++) { result.push(children[i]); }
|
||||
return result;
|
||||
// helper to track changes to viewChildren
|
||||
function trackChanges(views: QueryList<ElementRef>, changed: () => void) {
|
||||
let oldRefs = views.toArray();
|
||||
views.changes.subscribe((changes: QueryList<ElementRef>) => {
|
||||
const changedRefs = changes.toArray();
|
||||
// Is every changed ElemRef the same as old and in the same position
|
||||
const isSame = oldRefs.every((v, i) => v === changedRefs[i]);
|
||||
if (!isSame) {
|
||||
oldRefs = changedRefs;
|
||||
// wait a tick because called after views are constructed
|
||||
setTimeout(changed, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
/* Other imports */
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule
|
||||
FormsModule // <--- import into the NgModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
bootstrap: [ AppComponent ]
|
||||
/* Other module metadata */
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { BigHeroDetailComponent, HeroDetailComponent } from './hero-detail.component';
|
||||
import { ClickDirective, ClickDirective2 } from './click.directive';
|
||||
import { SizerComponent } from './sizer.component';
|
||||
import { HeroFormComponent } from './hero-form.component';
|
||||
import { heroSwitchComponents } from './hero-switch.components';
|
||||
import { SizerComponent } from './sizer.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -16,6 +18,8 @@ import { SizerComponent } from './sizer.component';
|
||||
AppComponent,
|
||||
BigHeroDetailComponent,
|
||||
HeroDetailComponent,
|
||||
HeroFormComponent,
|
||||
heroSwitchComponents,
|
||||
ClickDirective,
|
||||
ClickDirective2,
|
||||
SizerComponent
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Hero } from './hero';
|
||||
<div>
|
||||
<img src="{{heroImageUrl}}">
|
||||
<span [style.text-decoration]="lineThrough">
|
||||
{{prefix}} {{hero?.fullName}}
|
||||
{{prefix}} {{hero?.name}}
|
||||
</span>
|
||||
<button (click)="delete()">Delete</button>
|
||||
</div>`
|
||||
@@ -27,7 +27,7 @@ import { Hero } from './hero';
|
||||
})
|
||||
// #enddocregion input-output-2
|
||||
export class HeroDetailComponent {
|
||||
hero: Hero = new Hero('', 'Zzzzzzzz'); // default sleeping hero
|
||||
hero: Hero = new Hero(-1, '', 'Zzzzzzzz'); // default sleeping hero
|
||||
// heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png';
|
||||
// Public Domain terms of use: http://www.wpclipart.com/terms.html
|
||||
heroImageUrl = 'images/hero.png';
|
||||
@@ -50,18 +50,22 @@ export class HeroDetailComponent {
|
||||
@Component({
|
||||
selector: 'big-hero-detail',
|
||||
template: `
|
||||
<div style="border: 1px solid black; padding:3px">
|
||||
<img src="{{heroImageUrl}}" style="float:left; margin-right:8px;">
|
||||
<div><b>{{hero?.fullName}}</b></div>
|
||||
<div>First: {{hero?.firstName}}</div>
|
||||
<div>Last: {{hero?.lastName}}</div>
|
||||
<div class="detail">
|
||||
<img src="{{heroImageUrl}}">
|
||||
<div><b>{{hero?.name}}</b></div>
|
||||
<div>Name: {{hero?.name}}</div>
|
||||
<div>Emotion: {{hero?.emotion}}</div>
|
||||
<div>Birthdate: {{hero?.birthdate | date:'longDate'}}</div>
|
||||
<div>Web: <a href="{{hero?.url}}" target="_blank">{{hero?.url}}</a></div>
|
||||
<div>Rate/hr: {{hero?.rate | currency:'EUR'}}</div>
|
||||
<br clear="all">
|
||||
<button (click)="delete()">Delete</button>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
styles: [`
|
||||
.detail { border: 1px solid black; padding: 4px; max-width: 450px; }
|
||||
img { float: left; margin-right: 8px; height: 100px; }
|
||||
`]
|
||||
})
|
||||
export class BigHeroDetailComponent extends HeroDetailComponent {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
static nextId = 1;
|
||||
|
||||
static MockHeroes = [
|
||||
static heroes: Hero[] = [
|
||||
new Hero(
|
||||
325,
|
||||
'Hercules',
|
||||
'Son of Zeus',
|
||||
'happy',
|
||||
new Date(1970, 1, 25),
|
||||
'http://www.imdb.com/title/tt0065832/',
|
||||
325),
|
||||
|
||||
new Hero('eenie', 'toe'),
|
||||
new Hero('Meanie', 'Toe'),
|
||||
new Hero('Miny', 'Toe'),
|
||||
new Hero('Moe', 'Toe')
|
||||
'http://www.imdb.com/title/tt0065832/'
|
||||
),
|
||||
new Hero(1, 'Mr. Nice', 'happy'),
|
||||
new Hero(2, 'Narco', 'sad' ),
|
||||
new Hero(3, 'Windstorm', 'confused' ),
|
||||
new Hero(4, 'Magneta')
|
||||
];
|
||||
|
||||
public id: number;
|
||||
|
||||
static clone({firstName, lastName, birthdate, url, rate, id}: Hero) {
|
||||
return new Hero(firstName, lastName, birthdate, url, rate, id);
|
||||
}
|
||||
|
||||
constructor(
|
||||
public firstName: string,
|
||||
public lastName?: string,
|
||||
public id?: number,
|
||||
public name?: string,
|
||||
public emotion?: string,
|
||||
public birthdate?: Date,
|
||||
public url?: string,
|
||||
public rate = 100,
|
||||
id?: number) {
|
||||
|
||||
this.id = id != null ? id : Hero.nextId++;
|
||||
) {
|
||||
this.id = id ? id : Hero.nextId++;
|
||||
}
|
||||
|
||||
get fullName() { return `${this.firstName} ${this.lastName}`; }
|
||||
clone(): Hero {
|
||||
return Object.assign(new Hero(), this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="template-syntax.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
@@ -191,7 +191,7 @@ function heroModuleSetup() {
|
||||
}));
|
||||
|
||||
// #docregion title-case-pipe
|
||||
it('should convert hero name to Title Case', fakeAsync(() => {
|
||||
it('should convert hero name to Title Case', () => {
|
||||
const inputName = 'quick BROWN fox';
|
||||
const titleCaseName = 'Quick Brown Fox';
|
||||
|
||||
@@ -205,7 +205,7 @@ function heroModuleSetup() {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(page.nameDisplay.textContent).toBe(titleCaseName);
|
||||
}));
|
||||
});
|
||||
// #enddocregion title-case-pipe
|
||||
// #enddocregion selected-tests
|
||||
// #docregion route-good-id
|
||||
|
||||
Reference in New Issue
Block a user