diff --git a/public/docs/_examples/ngcontainer/ts/example-config.json b/public/docs/_examples/ngcontainer/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/ngcontainer/ts/plnkr.json b/public/docs/_examples/ngcontainer/ts/plnkr.json new file mode 100644 index 0000000000..a6cdc4ba1a --- /dev/null +++ b/public/docs/_examples/ngcontainer/ts/plnkr.json @@ -0,0 +1,11 @@ +{ + "description": "", + "basePath": "src/", + "files": [ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": [ + "ngcontainer", "structural", "directives" + ] +} diff --git a/public/docs/_examples/ngcontainer/ts/src/app/app.component.css b/public/docs/_examples/ngcontainer/ts/src/app/app.component.css new file mode 100644 index 0000000000..953ed3c39d --- /dev/null +++ b/public/docs/_examples/ngcontainer/ts/src/app/app.component.css @@ -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 */ diff --git a/public/docs/_examples/ngcontainer/ts/src/app/app.component.html b/public/docs/_examples/ngcontainer/ts/src/app/app.component.html new file mode 100644 index 0000000000..afd0b00f36 --- /dev/null +++ b/public/docs/_examples/ngcontainer/ts/src/app/app.component.html @@ -0,0 +1,279 @@ + + +

<ng-container>

+ + +
{{hero.name}}
+ + +
+ +

<ng-container> and CSS

+

Examples demonstrating issues with rigid CSS styles.

+ + + +

#1 <ng-container> and <p>

+ +

+ I turned the corner + + and saw {{hero.name}}. I waved + + and continued on my way. +

+ + +

+ I turned the corner + + and saw {{hero.name}}. I waved + + and continued on my way. +

+ + +

#2 <ng-container> and <p>

+ +
+ +

+ {{hero.name}} is + + , + and + {{trait}} + . +

+ + + +

+ {{hero.name}} is + + , + and + {{trait}} + . +

+ + +
+ +

#3 <ng-container> and <p>

+ +

+ +

+ + +
+ The hero.id in the <span> + is caught by the p-span CSS: + +

+ + Id: ({{hero.id}}) + + Name: {{hero.name}} +

+ +
+ +
+ The hero.id in the <ng-container> + is unaffected by the p-span CSS: +

+ + Id: ({{hero.id}}) + + Name: {{hero.name}} +

+
+ +
+ The hero.id in the <template *ngIf> disappears: +

+ + Name: {{hero.name}} +

+
+ +
+ The hero.id in the <template [ngIf]> + is unaffected by the p-span CSS: +

+ + Name: {{hero.name}} +

+
+ +
+ +
+ +

<ng-container> and layout-sensitive elements

+

+ Examples demonstrating issues with layout-sensitive elements + such as <select> and <table>. +

+ +

#1 <ng-container> and <options>

+ +

<select> with <span>

+
+ Pick your favorite hero + () +
+ + + + +

<select> with <ng-container>

+
+ Pick your favorite hero + () +
+ + + + +



+ +

#2 <ng-container> and <options>

+

+ +

+ +

Options with <ng-container>

+ + + + +

Options with <span>

+ + + + +
+ +

<ng-container> and <table>

+

+ + + +

+ + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DirectiveTypeDescription
NgClassAAdd or remove multiple CSS classes.
xxxSdiv with *ngIf formats crazy.
yyySdiv with *ngIf formats crazy.
NgForSRepeat the template for each item in a list.
NgIfSAdd or remove DOM elements.
NgStyleAAdd or remove multiple style attributes.
NgSwitchSInclude in DOM if case matches the switch value.
+ +
+ +

Do not confuse <ng-container> with <ng-content>

+ +

<ng-container>Inside ng-container</ng-container>

+ +Inside ng-container + + +

<ng-content>this is an Angular parse error</ng-content>

+ + + +
Template parse errors:
+<ng-content> element cannot have content.
+ +

Demo of </ng-content>

+ + + Projected content + + diff --git a/public/docs/_examples/ngcontainer/ts/src/app/app.component.ts b/public/docs/_examples/ngcontainer/ts/src/app/app.component.ts new file mode 100644 index 0000000000..2d6d7f959c --- /dev/null +++ b/public/docs/_examples/ngcontainer/ts/src/app/app.component.ts @@ -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; +} diff --git a/public/docs/_examples/ngcontainer/ts/src/app/app.module.ts b/public/docs/_examples/ngcontainer/ts/src/app/app.module.ts new file mode 100644 index 0000000000..57ac92f518 --- /dev/null +++ b/public/docs/_examples/ngcontainer/ts/src/app/app.module.ts @@ -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 { } diff --git a/public/docs/_examples/ngcontainer/ts/src/app/content.component.ts b/public/docs/_examples/ngcontainer/ts/src/app/content.component.ts new file mode 100644 index 0000000000..63c5646f99 --- /dev/null +++ b/public/docs/_examples/ngcontainer/ts/src/app/content.component.ts @@ -0,0 +1,16 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'content-comp', + // #docregion template + template: + `
+ +
`, + // #enddocregion template + styles: [ ` + div { border: medium dashed green; padding: 1em; width: 150px; text-align: center} + `] +}) +export class ContentComponent { } diff --git a/public/docs/_examples/ngcontainer/ts/src/app/hero.components.ts b/public/docs/_examples/ngcontainer/ts/src/app/hero.components.ts new file mode 100644 index 0000000000..43e400ed8e --- /dev/null +++ b/public/docs/_examples/ngcontainer/ts/src/app/hero.components.ts @@ -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 ]; diff --git a/public/docs/_examples/ngcontainer/ts/src/app/hero.ts b/public/docs/_examples/ngcontainer/ts/src/app/hero.ts new file mode 100644 index 0000000000..a1de3b3b82 --- /dev/null +++ b/public/docs/_examples/ngcontainer/ts/src/app/hero.ts @@ -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'} +]; diff --git a/public/docs/_examples/ngcontainer/ts/src/index.html b/public/docs/_examples/ngcontainer/ts/src/index.html new file mode 100644 index 0000000000..fc5ff417c3 --- /dev/null +++ b/public/docs/_examples/ngcontainer/ts/src/index.html @@ -0,0 +1,26 @@ + + + + + Angular <ng-container> + + + + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/ngcontainer/ts/src/main.ts b/public/docs/_examples/ngcontainer/ts/src/main.ts new file mode 100644 index 0000000000..105b06712d --- /dev/null +++ b/public/docs/_examples/ngcontainer/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); + diff --git a/public/docs/_examples/structural-directives/e2e-spec.ts b/public/docs/_examples/structural-directives/e2e-spec.ts index 335915eb86..51f30bf0bb 100644 --- a/public/docs/_examples/structural-directives/e2e-spec.ts +++ b/public/docs/_examples/structural-directives/e2e-spec.ts @@ -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 instances', function () { + const happyHeroEls = element.all(by.tagName('happy-hero')); + expect(happyHeroEls.count()).toEqual(3); }); - 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 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 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('should have only one "Hip!" (the other is erased)', function () { + const paragraph = element.all(by.cssContainingText('p', 'Hip!')); + expect(paragraph.count()).toEqual(1); + }); + + 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)'); }); }); }); + diff --git a/public/docs/_examples/structural-directives/ts/plnkr.json b/public/docs/_examples/structural-directives/ts/plnkr.json index 9aa0a3ed8a..58a26d7b4f 100644 --- a/public/docs/_examples/structural-directives/ts/plnkr.json +++ b/public/docs/_examples/structural-directives/ts/plnkr.json @@ -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" diff --git a/public/docs/_examples/structural-directives/ts/src/app/app.component.css b/public/docs/_examples/structural-directives/ts/src/app/app.component.css new file mode 100644 index 0000000000..e28be894f8 --- /dev/null +++ b/public/docs/_examples/structural-directives/ts/src/app/app.component.css @@ -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%; +} diff --git a/public/docs/_examples/structural-directives/ts/src/app/app.component.html b/public/docs/_examples/structural-directives/ts/src/app/app.component.html new file mode 100644 index 0000000000..2758553e39 --- /dev/null +++ b/public/docs/_examples/structural-directives/ts/src/app/app.component.html @@ -0,0 +1,252 @@ + + +

Structural Directives

+ +

Conditional display of hero

+ +
+ +
{{hero.name}}
+ +
+ +

List of heroes

+ + +
    + +
  • {{hero.name}}
  • + +
+ + + +
+ +

NgIf

+ + +

+ Expression is true and ngIf is true. + This paragraph is in the DOM. +

+

+ Expression is false and ngIf is false. + This paragraph is not in the DOM. +

+ + + +

+ Expression sets display to "block"" . + This paragraph is visible. +

+

+ Expression sets display to "none" . + This paragraph is hidden but still in the DOM. +

+ + +

NgIf with template

+

<template> element

+ + + + +

template attribute

+ +
{{hero.name}}
+ + +
+ +

<ng-container>

+ +

*ngIf with a <ng-container>

+ + + + +

+ I turned the corner + + and saw {{hero.name}}. I waved + + and continued on my way. +

+ + +

+ I turned the corner + + and saw {{hero.name}}. I waved + + and continued on my way. +

+ + +

<select> with <span>

+ +
+ Pick your favorite hero + () +
+ + + +

<select> with <ng-container>

+ +
+ Pick your favorite hero + () +
+ + +

+ +
+ +

NgFor

+ +
+ +

<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">

+ +
+ ({{i}}) {{hero.name}} +
+ + +

<div template="ngFor let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">

+ +
+ ({{i}}) {{hero.name}} +
+ + +

<template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">

+ + + + +
+
+ +

NgSwitch

+ +
Pick your favorite hero
+

+ + + + +

+ +

NgSwitch

+ + +
+ + + + +
+ + +

NgSwitch with template attribute

+ +
+ + + + +
+ + +

NgSwitch with <template>

+ +
+ + + + +
+ + +
+ +

<template>

+ +

Hip!

+ +

Hooray!

+ + +
+ +

UnlessDirective

+

+ The condition is currently + {{condition}}. + +

+ +

+ (A) This paragraph is displayed because the condition is false. +

+ +

+ (B) Although the condition is true, + this paragraph is displayed because myUnless is set to false. +

+ + + +

UnlessDirective with template

+ + +

Show this sentence unless the condition is true.

+ + +

+ (A) <p template="myUnless condition" class="code unless"> +

+ + + diff --git a/public/docs/_examples/structural-directives/ts/src/app/app.component.ts b/public/docs/_examples/structural-directives/ts/src/app/app.component.ts new file mode 100644 index 0000000000..5fd9dc417f --- /dev/null +++ b/public/docs/_examples/structural-directives/ts/src/app/app.component.ts @@ -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 +} diff --git a/public/docs/_examples/structural-directives/ts/src/app/app.module.ts b/public/docs/_examples/structural-directives/ts/src/app/app.module.ts index 0712db6d4b..b6ffb456c9 100644 --- a/public/docs/_examples/structural-directives/ts/src/app/app.module.ts +++ b/public/docs/_examples/structural-directives/ts/src/app/app.module.ts @@ -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 { } diff --git a/public/docs/_examples/structural-directives/ts/src/app/heavy-loader.component.ts b/public/docs/_examples/structural-directives/ts/src/app/heavy-loader.component.ts deleted file mode 100644 index 6eaa9309e8..0000000000 --- a/public/docs/_examples/structural-directives/ts/src/app/heavy-loader.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -// #docregion -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; - -let nextId = 1; - -@Component({ - selector: 'heavy-loader', - template: 'heavy loader #{{id}} on duty!' -}) -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 diff --git a/public/docs/_examples/structural-directives/ts/src/app/hero-switch.components.ts b/public/docs/_examples/structural-directives/ts/src/app/hero-switch.components.ts new file mode 100644 index 0000000000..6608422d15 --- /dev/null +++ b/public/docs/_examples/structural-directives/ts/src/app/hero-switch.components.ts @@ -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 ]; diff --git a/public/docs/_examples/structural-directives/ts/src/app/hero.ts b/public/docs/_examples/structural-directives/ts/src/app/hero.ts new file mode 100644 index 0000000000..a1de3b3b82 --- /dev/null +++ b/public/docs/_examples/structural-directives/ts/src/app/hero.ts @@ -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'} +]; diff --git a/public/docs/_examples/structural-directives/ts/src/app/scrap.txt b/public/docs/_examples/structural-directives/ts/src/app/scrap.txt new file mode 100644 index 0000000000..96426d37f0 --- /dev/null +++ b/public/docs/_examples/structural-directives/ts/src/app/scrap.txt @@ -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]; } + } + } + + +

Switch with *ngFor repeated switchCases using <ng-container>

+ +
+ Your favorite hero is ... + + {{hero.name}} + + None of the above +
+ diff --git a/public/docs/_examples/structural-directives/ts/src/app/structural-directives.component.html b/public/docs/_examples/structural-directives/ts/src/app/structural-directives.component.html deleted file mode 100644 index cf19ebb137..0000000000 --- a/public/docs/_examples/structural-directives/ts/src/app/structural-directives.component.html +++ /dev/null @@ -1,109 +0,0 @@ - - -

Structural Directives

- - - -
{{hero}}
-
{{hero}}
- - -
- - - -
- - - -
- - - - -

- condition is true and ngIf is true. -

-

- condition is false and ngIf is false. -

- - -

- condition is false and myUnless is true. -

- -

- condition is true and myUnless is false. -

- - -
- - -
- - -
- -
- - -
- -

heavy-loader log:

-
{{message}}
- - -
- - -

- Hip! -

- -

- Hooray! -

- - -
- - - - -

- Our heroes are true! -

- - - - - -
- - - - - -
{{ hero }}
- - - - - diff --git a/public/docs/_examples/structural-directives/ts/src/app/structural-directives.component.ts b/public/docs/_examples/structural-directives/ts/src/app/structural-directives.component.ts deleted file mode 100644 index 1c073c74e1..0000000000 --- a/public/docs/_examples/structural-directives/ts/src/app/structural-directives.component.ts +++ /dev/null @@ -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 diff --git a/public/docs/_examples/structural-directives/ts/src/app/unless.directive.ts b/public/docs/_examples/structural-directives/ts/src/app/unless.directive.ts index 105fdde4ae..19d48b4453 100644 --- a/public/docs/_examples/structural-directives/ts/src/app/unless.directive.ts +++ b/public/docs/_examples/structural-directives/ts/src/app/unless.directive.ts @@ -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. + * + *
+ * Congrats! Everything is great! + *
+ * + * ### Syntax + * * + * - `
...
` + * - `
...
` + * - `` + * +// #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, - 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 diff --git a/public/docs/_examples/structural-directives/ts/src/index.html b/public/docs/_examples/structural-directives/ts/src/index.html index b77d172317..ce7a33266d 100644 --- a/public/docs/_examples/structural-directives/ts/src/index.html +++ b/public/docs/_examples/structural-directives/ts/src/index.html @@ -21,7 +21,7 @@ - Loading... + Loading... diff --git a/public/docs/_examples/template-syntax/ts/src/app/app.component.css b/public/docs/_examples/template-syntax/ts/src/app/app.component.css new file mode 100644 index 0000000000..23f9667623 --- /dev/null +++ b/public/docs/_examples/template-syntax/ts/src/app/app.component.css @@ -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} diff --git a/public/docs/_examples/template-syntax/ts/src/app/app.component.html b/public/docs/_examples/template-syntax/ts/src/app/app.component.html index 66d35f5df0..b3d6c29be5 100644 --- a/public/docs/_examples/template-syntax/ts/src/app/app.component.html +++ b/public/docs/_examples/template-syntax/ts/src/app/app.component.html @@ -2,6 +2,8 @@

Template Syntax

Interpolation
+Expression context
+Statement context
Mental Model
Buttons
Properties vs. Attributes
@@ -22,15 +24,14 @@

Template Syntax

NgClass Binding
NgStyle Binding
NgIf
- NgSwitch
NgFor
+ NgSwitch

-* prefix and <template>
Template reference variables
Inputs and outputs
Pipes
@@ -41,7 +42,7 @@

Template Syntax


Interpolation

-

My current hero is {{currentHero.firstName}}

+

My current hero is {{currentHero.name}}

@@ -63,6 +64,68 @@

top +

Expression context

+ +

Component expression context ({{title}}, [hidden]="isUnchanged")

+
+ + {{title}} + changed + +
+ + +

Template input variable expression context (let hero)

+ + + +

Template reference variable expression context (#heroInput)

+
+ Type something: + + {{heroInput.value}} + +
+ +top + +

Statement context

+ +

Component statement context ( (click)="onSave() ) +

+ + + +
+ +

Template $event statement context

+
+ + + +
+ +

Template input variable statement context (let hero)

+ +
+ + + +
+ +

Template reference variable statement context (#heroForm)

+
+ +
...
+ +
+ +top +

New Mental Model

@@ -105,16 +168,17 @@

-
click me
+
click me
{{clicked}}

+ Hero Name: - Hero Name: {{heroName}} + {{heroName}}


@@ -205,6 +269,10 @@

+

"{{evilTitle}}" is the interpolated evil title.

"" is the property bound evil title.

@@ -307,7 +375,7 @@

-
click with myClick
+
click with myClick
{{clickMessage}} @@ -326,26 +394,25 @@

-
Click me +
Click me
Click me too!
-

-
+
-

+ -
+
-

+ top

Two-way Binding

@@ -363,7 +430,6 @@

De-sugared two-way binding

-

top @@ -371,38 +437,37 @@

De-sugared two-way binding

passing the changed display value to the event handler via `$event` -->

NgModel (two-way) Binding

-

Result: {{currentHero.firstName}}

+

Result: {{currentHero.name}}

- + without NgModel
- + [(ngModel)]
- + bindon-ngModel
+ [ngModel]="currentHero.name" + (ngModelChange)="currentHero.name=$event"> -(ngModelChange) = "...firstName=$event" +(ngModelChange) = "...name=$event"
+ [ngModel]="currentHero.name" + (ngModelChange)="setUppercaseName($event)"> -(ngModelChange) = "setUpperCaseFirstName($event)" -
+(ngModelChange) = "setUppercaseName($event)" top @@ -417,7 +482,7 @@

Result: {{currentHero.firstName}}


| - | + |

@@ -462,7 +527,6 @@

[ngStyle] binding to `currentStyles` - CSS property names

This div should be {{ canSave ? "italic": "plain"}}, {{ isUnchanged ? "normal weight" : "bold" }} and, {{ isSpecial ? "extra large": "normal size"}} after clicking "refresh".
-
top @@ -470,21 +534,17 @@

[ngStyle] binding to `currentStyles` - CSS property names


NgIf Binding

-
Hello, {{currentHero.firstName}}
+ - -
Hello, {{nullHero.firstName}}
- - - +
Hello, {{currentHero.name}}
+
Hello, {{nullHero.name}}
- +
Hero Detail removed from DOM (via template) because isActive is false
@@ -506,170 +566,117 @@

[ngStyle] binding to `currentStyles` - CSS property names

top - -

NgSwitch Binding

- -
- Eenie - Meanie - Miney - Moe - ??? -
- -
-
Pick a toe
-
- You picked ... - - - - - - - Eenie - Meanie - Miney - Moe - other - - - - - - - - - - - - -
-
- -top -

NgFor Binding

- -
{{hero.fullName}}
- + +
{{hero.name}}
+

- + - +
top -

NgFor with index

+

*ngFor with index

with semi-colon separator

-
{{i + 1}} - {{hero.fullName}}
+
{{i + 1}} - {{hero.name}}

with comma separator

- -
{{i + 1}} - {{hero.fullName}}
+ +
{{i + 1}} - {{hero.name}}
top -

NgForTrackBy

- -

First hero:

+

*ngFor trackBy

+ + +

without trackBy

-
- -
({{hero.id}}) {{hero.fullName}}
- +
+
({{hero.id}}) {{hero.name}}
+ +
+ Hero DOM elements change #{{heroesNoTrackByCount}} without trackBy +
-
- Hero DOM elements change #{{heroesNoTrackByChangeCount}} without trackBy + +

with trackBy

+
+
({{hero.id}}) {{hero.name}}
+ +
+ Hero DOM elements change #{{heroesWithTrackByCount}} with trackBy +
+


+

with trackBy and semi-colon separator

-
- -
({{hero.id}}) {{hero.fullName}}
- -
-
- Hero DOM elements change #{{heroesWithTrackByChangeCount}} with trackBy +
+ +
+ ({{hero.id}}) {{hero.name}} +
+

with trackBy and comma separator

-
({{hero.id}}) {{hero.fullName}}
+
({{hero.id}}) {{hero.name}}

with trackBy and space separator

-
({{hero.id}}) {{hero.fullName}}
+
({{hero.id}}) {{hero.name}}

with generic trackById function

-
({{hero.id}}) {{hero.fullName}}
+
({{hero.id}}) {{hero.name}}
top - -

* prefix and <template>

- -

*ngIf expansion

-

*ngIf

- - - - -

expand to template = "..."

- - - - -

expand to <template>

- - - - -

*ngFor expansion

-

*ngFor

- - - - + +

NgSwitch Binding

-

expand to template = "..."

-
- - - - -
+
Pick your favorite hero
+

+ + + +

-

expand to <template>

-
- - - - + +
+ + + + + +
Are you as confused as {{currentHero.name}}?
+ + +
+ top @@ -677,34 +684,27 @@

*ngFor expansion


Template reference variables

- + + + + + + + - + - - -

Example Form

- - -
- -
- - -
- - -
- - -

+ +

Example Form

+ + top @@ -720,7 +720,7 @@

Example Form

-
myClick2
+
myClick2
{{clickMessage2}} top @@ -769,43 +769,42 @@

Example Form

- The current hero's name is {{currentHero?.firstName}} + The current hero's name is {{currentHero?.name}}
- The current hero's name is {{currentHero.firstName}} + The current hero's name is {{currentHero.name}}
-
The null hero's name is {{nullHero.firstName}}
+
The null hero's name is {{nullHero.name}}
-The null hero's name is {{nullHero && nullHero.firstName}} +The null hero's name is {{nullHero && nullHero.name}}
- The null hero's name is {{nullHero?.firstName}} + The null hero's name is {{nullHero?.name}}
- top diff --git a/public/docs/_examples/template-syntax/ts/src/app/app.component.ts b/public/docs/_examples/template-syntax/ts/src/app/app.component.ts index eb9b27ca52..166948362f 100644 --- a/public/docs/_examples/template-syntax/ts/src/app/app.component.ts +++ b/public/docs/_examples/template-syntax/ts/src/app/app.component.ts @@ -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; + @ViewChildren('withTrackBy') heroesWithTrackBy: QueryList; + 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 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 ' + (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 = 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; - @ViewChildren('withTrackBy') childrenWithTrackBy: QueryList; - - 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]); +// helper to track changes to viewChildren +function trackChanges(views: QueryList, changed: () => void) { + let oldRefs = views.toArray(); + views.changes.subscribe((changes: QueryList) => { + 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) { - this._oldWithTrackBy = newWithTrackBy; - this.heroesWithTrackByChangeCount++; + oldRefs = changedRefs; + // wait a tick because called after views are constructed + setTimeout(changed, 0); } - }); - } - /////////////////// - -} - -// helper to convert viewChildren to an array of HTMLElements -function toArray(viewChildren: QueryList) { - let result: HTMLElement[] = []; - let children = viewChildren.toArray()[0].nativeElement.children; - for (let i = 0; i < children.length; i++) { result.push(children[i]); } - return result; + }); } diff --git a/public/docs/_examples/template-syntax/ts/src/app/app.module.1.ts b/public/docs/_examples/template-syntax/ts/src/app/app.module.1.ts index 47007ecae4..8ea0d3d207 100644 --- a/public/docs/_examples/template-syntax/ts/src/app/app.module.1.ts +++ b/public/docs/_examples/template-syntax/ts/src/app/app.module.1.ts @@ -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 { } diff --git a/public/docs/_examples/template-syntax/ts/src/app/app.module.ts b/public/docs/_examples/template-syntax/ts/src/app/app.module.ts index 712b613daf..5c2fbed6f1 100644 --- a/public/docs/_examples/template-syntax/ts/src/app/app.module.ts +++ b/public/docs/_examples/template-syntax/ts/src/app/app.module.ts @@ -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 diff --git a/public/docs/_examples/template-syntax/ts/src/app/hero-detail.component.ts b/public/docs/_examples/template-syntax/ts/src/app/hero-detail.component.ts index a359a08d1c..486e6ac370 100644 --- a/public/docs/_examples/template-syntax/ts/src/app/hero-detail.component.ts +++ b/public/docs/_examples/template-syntax/ts/src/app/hero-detail.component.ts @@ -18,7 +18,7 @@ import { Hero } from './hero';
- {{prefix}} {{hero?.fullName}} + {{prefix}} {{hero?.name}}
` @@ -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: ` -
- -
{{hero?.fullName}}
-
First: {{hero?.firstName}}
-
Last: {{hero?.lastName}}
+
+ +
{{hero?.name}}
+
Name: {{hero?.name}}
+
Emotion: {{hero?.emotion}}
Birthdate: {{hero?.birthdate | date:'longDate'}}
Rate/hr: {{hero?.rate | currency:'EUR'}}

- ` + `, + styles: [` + .detail { border: 1px solid black; padding: 4px; max-width: 450px; } + img { float: left; margin-right: 8px; height: 100px; } + `] }) export class BigHeroDetailComponent extends HeroDetailComponent { diff --git a/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.html b/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.html new file mode 100644 index 0000000000..bbd417e304 --- /dev/null +++ b/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.html @@ -0,0 +1,16 @@ +
+ +
+
+ +
+ +
+
+ {{submitMessage}} +
+ +
+ diff --git a/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.ts b/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.ts new file mode 100644 index 0000000000..9a27902e1e --- /dev/null +++ b/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.ts @@ -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); + } +} diff --git a/public/docs/_examples/template-syntax/ts/src/app/hero-switch.components.ts b/public/docs/_examples/template-syntax/ts/src/app/hero-switch.components.ts new file mode 100644 index 0000000000..a21892e248 --- /dev/null +++ b/public/docs/_examples/template-syntax/ts/src/app/hero-switch.components.ts @@ -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 ]; diff --git a/public/docs/_examples/template-syntax/ts/src/app/hero.ts b/public/docs/_examples/template-syntax/ts/src/app/hero.ts index ecf4d58c43..6331b62a7b 100644 --- a/public/docs/_examples/template-syntax/ts/src/app/hero.ts +++ b/public/docs/_examples/template-syntax/ts/src/app/hero.ts @@ -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); + } } diff --git a/public/docs/_examples/template-syntax/ts/src/index.html b/public/docs/_examples/template-syntax/ts/src/index.html index 418daa58c0..3e711397cb 100644 --- a/public/docs/_examples/template-syntax/ts/src/index.html +++ b/public/docs/_examples/template-syntax/ts/src/index.html @@ -6,7 +6,6 @@ - diff --git a/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.spec.ts b/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.spec.ts index 297e5d7ef8..4e1ee034d7 100644 --- a/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.spec.ts +++ b/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.spec.ts @@ -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 diff --git a/public/docs/ts/latest/glossary.jade b/public/docs/ts/latest/glossary.jade index 4d78201c45..9491f2e35d 100644 --- a/public/docs/ts/latest/glossary.jade +++ b/public/docs/ts/latest/glossary.jade @@ -54,8 +54,10 @@ a#aot :marked In practice, a synonym for [Decoration](#decorator). +a#attribute-directive +a#attribute-directives :marked - ## Attribute directive + ## Attribute directives .l-sub-section :marked A category of [directive](#directive) that can listen to and modify the behavior of @@ -64,6 +66,8 @@ a#aot A good example of an attribute directive is the `ngClass` directive for adding and removing CSS class names. + Learn about them in the [_Attribute Directives_](!{docsLatest}/guide/attribute-directives.html) guide. + .l-main-section#B +ifDocsFor('ts|js') @@ -141,6 +145,7 @@ a#aot This form is also known as **lower camel case**, to distinguish it from **upper camel case**, which is [PascalCase](#pascalcase). When you see "camelCase" in this documentation it always means *lower camel case*. +a#component :marked ## Component .l-sub-section @@ -245,7 +250,7 @@ a#aot that "B" is a dependency of "A". You can ask a "dependency injection system" to create "A" - for us and handle all the dependencies. + and it will handle all of "A"s dependencies. If "A" needs "B" and "B" needs "C", the system resolves that chain of dependencies and returns a fully prepared instance of "A". @@ -276,9 +281,12 @@ a#aot Registering providers is a critical preparatory step. Angular registers some of its own providers with every injector. - We can register our own providers. + You can register your own providers. Read more in the [Dependency Injection](!{docsLatest}/guide/dependency-injection.html) page. + +a#directive +a#directives :marked ## Directive .l-sub-section @@ -286,8 +294,7 @@ a#aot An Angular class responsible for creating, reshaping, and interacting with HTML elements in the browser DOM. Directives are Angular's most fundamental feature. - A Directive is almost always associated with an HTML element or attribute. - We often refer to such an element or attribute as the directive itself. + A directive is almost always associated with an HTML element or attribute. When Angular finds a directive in an HTML template, it creates the matching directive class instance and gives the instance control over that portion of the browser DOM. @@ -297,20 +304,19 @@ a#aot as if you were writing native HTML. In this way, directives become extensions of HTML itself. - Directives fall into one of three categories: + Directives fall into three categories: 1. [Components](#component) that combine application logic with an HTML template to - render application [views]. Components are usually represented as HTML elements. + render application [views](#view). Components are usually represented as HTML elements. They are the building blocks of an Angular application and the developer can expect to write a lot of them. 1. [Attribute directives](#attribute-directive) that can listen to and modify the behavior of - other HTML elements, attributes, properties, and components. They are usually represented + HTML elements, components, and other directives. They are usually represented as HTML attributes, hence the name. - 1. [Structural directives](#structural-directive), a directive responsible for - shaping or reshaping HTML layout, typically by adding, removing, or manipulating - elements and their children. + 1. [Structural directives](#structural-directive) that + shape or reshape HTML layout, typically by adding and removing elements in the DOM. .l-main-section#E @@ -630,21 +636,23 @@ a#snake-case Applications often require services such as a hero data service or a logging service. A service is a class with a focused purpose. - We often create a service to implement features that are + You often create a service to implement features that are independent from any specific view, provide shared data or logic across components, or encapsulate external interactions. For more information, see the [Services](!{docsLatest}/tutorial/toh-pt4.html) page of the [Tour of Heroes](!{docsLatest}/tutorial/) tutorial. +a#structural-directive +a#structural-directives :marked - ## Structural directive + ## Structural directives .l-sub-section :marked A category of [directive](#directive) that can - shape or reshape HTML layout, typically by adding, removing, or manipulating - elements and their children; for example, the `ngIf` "conditional element" directive and the `ngFor` "repeater" directive. + shape or reshape HTML layout, typically by adding and removing elements in the DOM. + The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive are well-known examples. - Read more in the [Structural Directives](!{docsLatest}/guide/structural-directives.html) page. + Read more in the [_Structural Directives_](!{docsLatest}/guide/structural-directives.html) guide. .l-main-section#T :marked @@ -699,8 +707,8 @@ a#snake-case A version of JavaScript that supports most [ECMAScript 2015](#es2015) language features such as [decorators](#decorator). - TypeScript is also noteable for its optional typing system, which gives - us compile-time type checking and strong tooling support (for example, "intellisense", + TypeScript is also noteable for its optional typing system, which enables + compile-time type checking and strong tooling support (for example, "intellisense", code completion, refactoring, and intelligent search). Many code editors and IDEs support TypeScript either natively or with plugins. diff --git a/public/docs/ts/latest/guide/attribute-directives.jade b/public/docs/ts/latest/guide/attribute-directives.jade index 294eab77bb..07bb2f8f5a 100644 --- a/public/docs/ts/latest/guide/attribute-directives.jade +++ b/public/docs/ts/latest/guide/attribute-directives.jade @@ -24,17 +24,20 @@ a#directive-overview ## Directives overview 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. Attribute directives—change the appearance or behavior of an element. + + 1. Components — directives with a template. + 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. You saw a component for the first time in the [QuickStart](../quickstart.html) example. - *Structural Directives* change the structure of the view. Two examples are [NgFor](template-syntax.html#ngFor) and [NgIf](template-syntax.html#ngIf) - in the [Template Syntax](template-syntax.html) page. + *Structural Directives* change the structure of the view. + Two examples are [NgFor](template-syntax.html#ngFor) and [NgIf](template-syntax.html#ngIf). + Learn about them in the [Structural Directives](structural-directives.html) guide. - *Attribute directives* are used as attributes of elements. The built-in [NgStyle](template-syntax.html#ngStyle) directive in the [Template Syntax](template-syntax.html) page, for example, + *Attribute directives* are used as attributes of elements. + The built-in [NgStyle](template-syntax.html#ngStyle) directive in the [Template Syntax](template-syntax.html) guide, for example, can change several element styles at the same time. .l-main-section diff --git a/public/docs/ts/latest/guide/change-log.jade b/public/docs/ts/latest/guide/change-log.jade index 12ca1a1a12..4eb8cefa75 100644 --- a/public/docs/ts/latest/guide/change-log.jade +++ b/public/docs/ts/latest/guide/change-log.jade @@ -5,6 +5,12 @@ block includes The Angular documentation is a living document with continuous improvements. This log calls attention to recent significant changes. + ## Template Syntax/Structural Directives: refreshed (2017-02-06) + The [_Template-Syntax_](template-syntax.html) and [_Structural Directives_](structural-directives.html) + guides were significantly revised for clarity, accuracy, and current recommended practices. + Discusses ``. + Revised samples are more clear and cover all topics discussed. + ## NEW: Samples re-structured with `src/` folder (2017-02-02) All documentation samples have been realigned with the default folder structure of the angular-cli. That's a step along the road to basing our sample in the angular-cli. @@ -39,7 +45,7 @@ block includes ## Hierarchical Dependency Injection: refreshed (2017-01-13) [Hierarchical Dependency Injection](hierarchical-dependency-injection.html) guide significantly revised. Closes issue #3086 - Revised samples are more clear and cover all topics discussed + Revised samples are more clear and cover all topics discussed. ## Miscellaneous (2017-01-05) * [Setup](setup.html) guide: diff --git a/public/docs/ts/latest/guide/ngcontainer.jade b/public/docs/ts/latest/guide/ngcontainer.jade new file mode 100644 index 0000000000..549bdf7b98 --- /dev/null +++ b/public/docs/ts/latest/guide/ngcontainer.jade @@ -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 `` 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 `` 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 . + + 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 `` and apply the directive to the ``. + + The `` 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 `` satisfies a similar need in Angular templates. + + The `` _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 `` or `
`? + + 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 (``) 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 `
`, + 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 `` 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 `

`aragraph contains a ``. + +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 `` instead of `` 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 `` tags to trigger the unwanted style. + + Does Angular add an `` 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_ `` _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 ``. + 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 `