docs(aio): doc guide tweaks (GS thru Components)
Working through obvious non-image content defects, from top of menu (Getting Started) through Fundamentals/Components.
This commit is contained in:

committed by
Pete Bacon Darwin

parent
215611aa9e
commit
1af42aedfa
1
aio/content/examples/component-interaction/.gitignore
vendored
Normal file
1
aio/content/examples/component-interaction/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
**/*.js
|
232
aio/content/examples/component-interaction/e2e-spec.ts
Normal file
232
aio/content/examples/component-interaction/e2e-spec.ts
Normal file
@ -0,0 +1,232 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Component Communication Cookbook Tests', function () {
|
||||
|
||||
// Note: '?e2e' which app can read to know it is running in protractor
|
||||
// e.g. `if (!/e2e/.test(location.search)) { ...`
|
||||
beforeAll(function () {
|
||||
browser.get('?e2e');
|
||||
});
|
||||
|
||||
describe('Parent-to-child communication', function() {
|
||||
// #docregion parent-to-child
|
||||
// ...
|
||||
let _heroNames = ['Mr. IQ', 'Magneta', 'Bombasto'];
|
||||
let _masterName = 'Master';
|
||||
|
||||
it('should pass properties to children properly', function () {
|
||||
let parent = element.all(by.tagName('hero-parent')).get(0);
|
||||
let heroes = parent.all(by.tagName('hero-child'));
|
||||
|
||||
for (let i = 0; i < _heroNames.length; i++) {
|
||||
let childTitle = heroes.get(i).element(by.tagName('h3')).getText();
|
||||
let childDetail = heroes.get(i).element(by.tagName('p')).getText();
|
||||
expect(childTitle).toEqual(_heroNames[i] + ' says:');
|
||||
expect(childDetail).toContain(_masterName);
|
||||
}
|
||||
});
|
||||
// ...
|
||||
// #enddocregion parent-to-child
|
||||
});
|
||||
|
||||
describe('Parent-to-child communication with setter', function() {
|
||||
// #docregion parent-to-child-setter
|
||||
// ...
|
||||
it('should display trimmed, non-empty names', function () {
|
||||
let _nonEmptyNameIndex = 0;
|
||||
let _nonEmptyName = '"Mr. IQ"';
|
||||
let parent = element.all(by.tagName('name-parent')).get(0);
|
||||
let hero = parent.all(by.tagName('name-child')).get(_nonEmptyNameIndex);
|
||||
|
||||
let displayName = hero.element(by.tagName('h3')).getText();
|
||||
expect(displayName).toEqual(_nonEmptyName);
|
||||
});
|
||||
|
||||
it('should replace empty name with default name', function () {
|
||||
let _emptyNameIndex = 1;
|
||||
let _defaultName = '"<no name set>"';
|
||||
let parent = element.all(by.tagName('name-parent')).get(0);
|
||||
let hero = parent.all(by.tagName('name-child')).get(_emptyNameIndex);
|
||||
|
||||
let displayName = hero.element(by.tagName('h3')).getText();
|
||||
expect(displayName).toEqual(_defaultName);
|
||||
});
|
||||
// ...
|
||||
// #enddocregion parent-to-child-setter
|
||||
});
|
||||
|
||||
describe('Parent-to-child communication with ngOnChanges', function() {
|
||||
// #docregion parent-to-child-onchanges
|
||||
// ...
|
||||
// Test must all execute in this exact order
|
||||
it('should set expected initial values', function () {
|
||||
let actual = getActual();
|
||||
|
||||
let initialLabel = 'Version 1.23';
|
||||
let initialLog = 'Initial value of major set to 1, Initial value of minor set to 23';
|
||||
|
||||
expect(actual.label).toBe(initialLabel);
|
||||
expect(actual.count).toBe(1);
|
||||
expect(actual.logs.get(0).getText()).toBe(initialLog);
|
||||
});
|
||||
|
||||
it('should set expected values after clicking \'Minor\' twice', function () {
|
||||
let repoTag = element(by.tagName('version-parent'));
|
||||
let newMinorButton = repoTag.all(by.tagName('button')).get(0);
|
||||
|
||||
newMinorButton.click().then(function() {
|
||||
newMinorButton.click().then(function() {
|
||||
let actual = getActual();
|
||||
|
||||
let labelAfter2Minor = 'Version 1.25';
|
||||
let logAfter2Minor = 'minor changed from 24 to 25';
|
||||
|
||||
expect(actual.label).toBe(labelAfter2Minor);
|
||||
expect(actual.count).toBe(3);
|
||||
expect(actual.logs.get(2).getText()).toBe(logAfter2Minor);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should set expected values after clicking \'Major\' once', function () {
|
||||
let repoTag = element(by.tagName('version-parent'));
|
||||
let newMajorButton = repoTag.all(by.tagName('button')).get(1);
|
||||
|
||||
newMajorButton.click().then(function() {
|
||||
let actual = getActual();
|
||||
|
||||
let labelAfterMajor = 'Version 2.0';
|
||||
let logAfterMajor = 'major changed from 1 to 2, minor changed from 25 to 0';
|
||||
|
||||
expect(actual.label).toBe(labelAfterMajor);
|
||||
expect(actual.count).toBe(4);
|
||||
expect(actual.logs.get(3).getText()).toBe(logAfterMajor);
|
||||
});
|
||||
});
|
||||
|
||||
function getActual() {
|
||||
let versionTag = element(by.tagName('version-child'));
|
||||
let label = versionTag.element(by.tagName('h3')).getText();
|
||||
let ul = versionTag.element((by.tagName('ul')));
|
||||
let logs = ul.all(by.tagName('li'));
|
||||
|
||||
return {
|
||||
label: label,
|
||||
logs: logs,
|
||||
count: logs.count()
|
||||
};
|
||||
}
|
||||
// ...
|
||||
// #enddocregion parent-to-child-onchanges
|
||||
|
||||
});
|
||||
|
||||
describe('Child-to-parent communication', function() {
|
||||
// #docregion child-to-parent
|
||||
// ...
|
||||
it('should not emit the event initially', function () {
|
||||
let voteLabel = element(by.tagName('vote-taker'))
|
||||
.element(by.tagName('h3')).getText();
|
||||
expect(voteLabel).toBe('Agree: 0, Disagree: 0');
|
||||
});
|
||||
|
||||
it('should process Agree vote', function () {
|
||||
let agreeButton1 = element.all(by.tagName('my-voter')).get(0)
|
||||
.all(by.tagName('button')).get(0);
|
||||
agreeButton1.click().then(function() {
|
||||
let voteLabel = element(by.tagName('vote-taker'))
|
||||
.element(by.tagName('h3')).getText();
|
||||
expect(voteLabel).toBe('Agree: 1, Disagree: 0');
|
||||
});
|
||||
});
|
||||
|
||||
it('should process Disagree vote', function () {
|
||||
let agreeButton1 = element.all(by.tagName('my-voter')).get(1)
|
||||
.all(by.tagName('button')).get(1);
|
||||
agreeButton1.click().then(function() {
|
||||
let voteLabel = element(by.tagName('vote-taker'))
|
||||
.element(by.tagName('h3')).getText();
|
||||
expect(voteLabel).toBe('Agree: 1, Disagree: 1');
|
||||
});
|
||||
});
|
||||
// ...
|
||||
// #enddocregion child-to-parent
|
||||
});
|
||||
|
||||
// Can't run timer tests in protractor because
|
||||
// interaction w/ zones causes all tests to freeze & timeout.
|
||||
xdescribe('Parent calls child via local var', function() {
|
||||
countDownTimerTests('countdown-parent-lv');
|
||||
});
|
||||
|
||||
xdescribe('Parent calls ViewChild', function() {
|
||||
countDownTimerTests('countdown-parent-vc');
|
||||
});
|
||||
|
||||
function countDownTimerTests(parentTag: string) {
|
||||
// #docregion countdown-timer-tests
|
||||
// ...
|
||||
it('timer and parent seconds should match', function () {
|
||||
let parent = element(by.tagName(parentTag));
|
||||
let message = parent.element(by.tagName('countdown-timer')).getText();
|
||||
browser.sleep(10); // give `seconds` a chance to catchup with `message`
|
||||
let seconds = parent.element(by.className('seconds')).getText();
|
||||
expect(message).toContain(seconds);
|
||||
});
|
||||
|
||||
it('should stop the countdown', function () {
|
||||
let parent = element(by.tagName(parentTag));
|
||||
let stopButton = parent.all(by.tagName('button')).get(1);
|
||||
|
||||
stopButton.click().then(function() {
|
||||
let message = parent.element(by.tagName('countdown-timer')).getText();
|
||||
expect(message).toContain('Holding');
|
||||
});
|
||||
});
|
||||
// ...
|
||||
// #enddocregion countdown-timer-tests
|
||||
}
|
||||
|
||||
|
||||
describe('Parent and children communicate via a service', function() {
|
||||
// #docregion bidirectional-service
|
||||
// ...
|
||||
it('should announce a mission', function () {
|
||||
let missionControl = element(by.tagName('mission-control'));
|
||||
let announceButton = missionControl.all(by.tagName('button')).get(0);
|
||||
announceButton.click().then(function () {
|
||||
let history = missionControl.all(by.tagName('li'));
|
||||
expect(history.count()).toBe(1);
|
||||
expect(history.get(0).getText()).toMatch(/Mission.* announced/);
|
||||
});
|
||||
});
|
||||
|
||||
it('should confirm the mission by Lovell', function () {
|
||||
testConfirmMission(1, 2, 'Lovell');
|
||||
});
|
||||
|
||||
it('should confirm the mission by Haise', function () {
|
||||
testConfirmMission(3, 3, 'Haise');
|
||||
});
|
||||
|
||||
it('should confirm the mission by Swigert', function () {
|
||||
testConfirmMission(2, 4, 'Swigert');
|
||||
});
|
||||
|
||||
function testConfirmMission(buttonIndex: number, expectedLogCount: number, astronaut: string) {
|
||||
let _confirmedLog = ' confirmed the mission';
|
||||
let missionControl = element(by.tagName('mission-control'));
|
||||
let confirmButton = missionControl.all(by.tagName('button')).get(buttonIndex);
|
||||
confirmButton.click().then(function () {
|
||||
let history = missionControl.all(by.tagName('li'));
|
||||
expect(history.count()).toBe(expectedLogCount);
|
||||
expect(history.get(expectedLogCount - 1).getText()).toBe(astronaut + _confirmedLog);
|
||||
});
|
||||
}
|
||||
// ...
|
||||
// #enddocregion bidirectional-service
|
||||
});
|
||||
|
||||
});
|
9
aio/content/examples/component-interaction/plnkr.json
Normal file
9
aio/content/examples/component-interaction/plnkr.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"description": "Component Communication Cookbook samples",
|
||||
"basePath": "src/",
|
||||
"files":[
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js"
|
||||
],
|
||||
"tags":["cookbook", "component"]
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<h1 id="top">Component Communication Cookbook</h1>
|
||||
|
||||
<a href="#parent-to-child">Pass data from parent to child with input binding ("Heroes")</a><br/>
|
||||
<a href="#parent-to-child-setter">Intercept input property changes with a setter ("Master")</a><br/>
|
||||
<a href="#parent-to-child-on-changes">Intercept input property changes with <i>ngOnChanges</i> ("Source code version")</a><br/>
|
||||
<a href="#child-to-parent">Parent listens for child event ("Colonize Universe")</a><br/>
|
||||
<a href="#parent-to-child-local-var">Parent to child via <i>local variable</i>("Countdown to Liftoff")</a><br/>
|
||||
<a href="#parent-to-view-child">Parent calls <i>ViewChild</i>("Countdown to Liftoff")</a><br/>
|
||||
<a href="#bidirectional-service">Parent and children communicate via a service ("Mission Control")</a><br/>
|
||||
|
||||
<div id="parent-to-child">
|
||||
<hero-parent></hero-parent>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
|
||||
<hr>
|
||||
<div id="parent-to-child-setter">
|
||||
<name-parent></name-parent>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
|
||||
<hr>
|
||||
<div id="parent-to-child-on-changes">
|
||||
<version-parent></version-parent>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
|
||||
<hr>
|
||||
<div id="child-to-parent">
|
||||
<vote-taker></vote-taker>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
||||
<div id="parent-to-child-local-var">
|
||||
<countdown-parent-lv></countdown-parent-lv>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
||||
<div id="parent-to-view-child">
|
||||
<countdown-parent-vc></countdown-parent-vc>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
||||
<div id="bidirectional-service">
|
||||
<mission-control></mission-control>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
@ -0,0 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html'
|
||||
})
|
||||
export class AppComponent { }
|
@ -0,0 +1,54 @@
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AstronautComponent } from './astronaut.component';
|
||||
import { CountdownLocalVarParentComponent, CountdownViewChildParentComponent } from './countdown-parent.component';
|
||||
import { CountdownTimerComponent } from './countdown-timer.component';
|
||||
import { HeroChildComponent } from './hero-child.component';
|
||||
import { HeroParentComponent } from './hero-parent.component';
|
||||
import { MissionControlComponent } from './missioncontrol.component';
|
||||
import { NameChildComponent } from './name-child.component';
|
||||
import { NameParentComponent } from './name-parent.component';
|
||||
import { VersionChildComponent } from './version-child.component';
|
||||
import { VersionParentComponent } from './version-parent.component';
|
||||
import { VoterComponent } from './voter.component';
|
||||
import { VoteTakerComponent } from './votetaker.component';
|
||||
|
||||
let directives: any[] = [
|
||||
AppComponent,
|
||||
AstronautComponent,
|
||||
CountdownTimerComponent,
|
||||
HeroChildComponent,
|
||||
HeroParentComponent,
|
||||
MissionControlComponent,
|
||||
NameChildComponent,
|
||||
NameParentComponent,
|
||||
VersionChildComponent,
|
||||
VersionParentComponent,
|
||||
VoterComponent,
|
||||
VoteTakerComponent
|
||||
];
|
||||
|
||||
let schemas: any[] = [];
|
||||
|
||||
// Include Countdown examples
|
||||
// unless in e2e tests which they break.
|
||||
if (!/e2e/.test(location.search)) {
|
||||
console.log('adding countdown timer examples');
|
||||
directives.push(CountdownLocalVarParentComponent);
|
||||
directives.push(CountdownViewChildParentComponent);
|
||||
} else {
|
||||
// In e2e test use CUSTOM_ELEMENTS_SCHEMA to supress unknown element errors
|
||||
schemas.push(CUSTOM_ELEMENTS_SCHEMA);
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
declarations: directives,
|
||||
bootstrap: [ AppComponent ],
|
||||
schemas: schemas
|
||||
})
|
||||
export class AppModule { }
|
@ -0,0 +1,46 @@
|
||||
// #docregion
|
||||
import { Component, Input, OnDestroy } from '@angular/core';
|
||||
|
||||
import { MissionService } from './mission.service';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
@Component({
|
||||
selector: 'my-astronaut',
|
||||
template: `
|
||||
<p>
|
||||
{{astronaut}}: <strong>{{mission}}</strong>
|
||||
<button
|
||||
(click)="confirm()"
|
||||
[disabled]="!announced || confirmed">
|
||||
Confirm
|
||||
</button>
|
||||
</p>
|
||||
`
|
||||
})
|
||||
export class AstronautComponent implements OnDestroy {
|
||||
@Input() astronaut: string;
|
||||
mission = '<no mission announced>';
|
||||
confirmed = false;
|
||||
announced = false;
|
||||
subscription: Subscription;
|
||||
|
||||
constructor(private missionService: MissionService) {
|
||||
this.subscription = missionService.missionAnnounced$.subscribe(
|
||||
mission => {
|
||||
this.mission = mission;
|
||||
this.announced = true;
|
||||
this.confirmed = false;
|
||||
});
|
||||
}
|
||||
|
||||
confirm() {
|
||||
this.confirmed = true;
|
||||
this.missionService.confirmMission(this.astronaut);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
// prevent memory leak when component destroyed
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,57 @@
|
||||
// #docplaster
|
||||
// #docregion vc
|
||||
import { AfterViewInit, ViewChild } from '@angular/core';
|
||||
// #docregion lv
|
||||
import { Component } from '@angular/core';
|
||||
import { CountdownTimerComponent } from './countdown-timer.component';
|
||||
|
||||
// #enddocregion lv
|
||||
// #enddocregion vc
|
||||
|
||||
//// Local variable, #timer, version
|
||||
// #docregion lv
|
||||
@Component({
|
||||
selector: 'countdown-parent-lv',
|
||||
template: `
|
||||
<h3>Countdown to Liftoff (via local variable)</h3>
|
||||
<button (click)="timer.start()">Start</button>
|
||||
<button (click)="timer.stop()">Stop</button>
|
||||
<div class="seconds">{{timer.seconds}}</div>
|
||||
<countdown-timer #timer></countdown-timer>
|
||||
`,
|
||||
styleUrls: ['demo.css']
|
||||
})
|
||||
export class CountdownLocalVarParentComponent { }
|
||||
// #enddocregion lv
|
||||
|
||||
//// View Child version
|
||||
// #docregion vc
|
||||
@Component({
|
||||
selector: 'countdown-parent-vc',
|
||||
template: `
|
||||
<h3>Countdown to Liftoff (via ViewChild)</h3>
|
||||
<button (click)="start()">Start</button>
|
||||
<button (click)="stop()">Stop</button>
|
||||
<div class="seconds">{{ seconds() }}</div>
|
||||
<countdown-timer></countdown-timer>
|
||||
`,
|
||||
styleUrls: ['demo.css']
|
||||
})
|
||||
export class CountdownViewChildParentComponent implements AfterViewInit {
|
||||
|
||||
@ViewChild(CountdownTimerComponent)
|
||||
private timerComponent: CountdownTimerComponent;
|
||||
|
||||
seconds() { return 0; }
|
||||
|
||||
ngAfterViewInit() {
|
||||
// Redefine `seconds()` to get from the `CountdownTimerComponent.seconds` ...
|
||||
// but wait a tick first to avoid one-time devMode
|
||||
// unidirectional-data-flow-violation error
|
||||
setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
|
||||
}
|
||||
|
||||
start() { this.timerComponent.start(); }
|
||||
stop() { this.timerComponent.stop(); }
|
||||
}
|
||||
// #enddocregion vc
|
@ -0,0 +1,37 @@
|
||||
// #docregion
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'countdown-timer',
|
||||
template: '<p>{{message}}</p>'
|
||||
})
|
||||
export class CountdownTimerComponent implements OnInit, OnDestroy {
|
||||
|
||||
intervalId = 0;
|
||||
message = '';
|
||||
seconds = 11;
|
||||
|
||||
clearTimer() { clearInterval(this.intervalId); }
|
||||
|
||||
ngOnInit() { this.start(); }
|
||||
ngOnDestroy() { this.clearTimer(); }
|
||||
|
||||
start() { this.countDown(); }
|
||||
stop() {
|
||||
this.clearTimer();
|
||||
this.message = `Holding at T-${this.seconds} seconds`;
|
||||
}
|
||||
|
||||
private countDown() {
|
||||
this.clearTimer();
|
||||
this.intervalId = window.setInterval(() => {
|
||||
this.seconds -= 1;
|
||||
if (this.seconds === 0) {
|
||||
this.message = 'Blast off!';
|
||||
} else {
|
||||
if (this.seconds < 0) { this.seconds = 10; } // reset
|
||||
this.message = `T-${this.seconds} seconds and counting`;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// #docregion
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { Hero } from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-child',
|
||||
template: `
|
||||
<h3>{{hero.name}} says:</h3>
|
||||
<p>I, {{hero.name}}, am at your service, {{masterName}}.</p>
|
||||
`
|
||||
})
|
||||
export class HeroChildComponent {
|
||||
@Input() hero: Hero;
|
||||
@Input('master') masterName: string;
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,20 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { HEROES } from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-parent',
|
||||
template: `
|
||||
<h2>{{master}} controls {{heroes.length}} heroes</h2>
|
||||
<hero-child *ngFor="let hero of heroes"
|
||||
[hero]="hero"
|
||||
[master]="master">
|
||||
</hero-child>
|
||||
`
|
||||
})
|
||||
export class HeroParentComponent {
|
||||
heroes = HEROES;
|
||||
master = 'Master';
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,9 @@
|
||||
export class Hero {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const HEROES = [
|
||||
{name: 'Mr. IQ'},
|
||||
{name: 'Magneta'},
|
||||
{name: 'Bombasto'}
|
||||
];
|
@ -0,0 +1,25 @@
|
||||
// #docregion
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
@Injectable()
|
||||
export class MissionService {
|
||||
|
||||
// Observable string sources
|
||||
private missionAnnouncedSource = new Subject<string>();
|
||||
private missionConfirmedSource = new Subject<string>();
|
||||
|
||||
// Observable string streams
|
||||
missionAnnounced$ = this.missionAnnouncedSource.asObservable();
|
||||
missionConfirmed$ = this.missionConfirmedSource.asObservable();
|
||||
|
||||
// Service message commands
|
||||
announceMission(mission: string) {
|
||||
this.missionAnnouncedSource.next(mission);
|
||||
}
|
||||
|
||||
confirmMission(astronaut: string) {
|
||||
this.missionConfirmedSource.next(astronaut);
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,43 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { MissionService } from './mission.service';
|
||||
|
||||
@Component({
|
||||
selector: 'mission-control',
|
||||
template: `
|
||||
<h2>Mission Control</h2>
|
||||
<button (click)="announce()">Announce mission</button>
|
||||
<my-astronaut *ngFor="let astronaut of astronauts"
|
||||
[astronaut]="astronaut">
|
||||
</my-astronaut>
|
||||
<h3>History</h3>
|
||||
<ul>
|
||||
<li *ngFor="let event of history">{{event}}</li>
|
||||
</ul>
|
||||
`,
|
||||
providers: [MissionService]
|
||||
})
|
||||
export class MissionControlComponent {
|
||||
astronauts = ['Lovell', 'Swigert', 'Haise'];
|
||||
history: string[] = [];
|
||||
missions = ['Fly to the moon!',
|
||||
'Fly to mars!',
|
||||
'Fly to Vegas!'];
|
||||
nextMission = 0;
|
||||
|
||||
constructor(private missionService: MissionService) {
|
||||
missionService.missionConfirmed$.subscribe(
|
||||
astronaut => {
|
||||
this.history.push(`${astronaut} confirmed the mission`);
|
||||
});
|
||||
}
|
||||
|
||||
announce() {
|
||||
let mission = this.missions[this.nextMission++];
|
||||
this.missionService.announceMission(mission);
|
||||
this.history.push(`Mission "${mission}" announced`);
|
||||
if (this.nextMission >= this.missions.length) { this.nextMission = 0; }
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,18 @@
|
||||
// #docregion
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'name-child',
|
||||
template: '<h3>"{{name}}"</h3>'
|
||||
})
|
||||
export class NameChildComponent {
|
||||
private _name = '';
|
||||
|
||||
@Input()
|
||||
set name(name: string) {
|
||||
this._name = (name && name.trim()) || '<no name set>';
|
||||
}
|
||||
|
||||
get name(): string { return this._name; }
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,15 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'name-parent',
|
||||
template: `
|
||||
<h2>Master controls {{names.length}} names</h2>
|
||||
<name-child *ngFor="let name of names" [name]="name"></name-child>
|
||||
`
|
||||
})
|
||||
export class NameParentComponent {
|
||||
// Displays 'Mr. IQ', '<no name set>', 'Bombasto'
|
||||
names = ['Mr. IQ', ' ', ' Bombasto '];
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,35 @@
|
||||
/* tslint:disable:forin */
|
||||
// #docregion
|
||||
import { Component, Input, OnChanges, SimpleChange } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'version-child',
|
||||
template: `
|
||||
<h3>Version {{major}}.{{minor}}</h3>
|
||||
<h4>Change log:</h4>
|
||||
<ul>
|
||||
<li *ngFor="let change of changeLog">{{change}}</li>
|
||||
</ul>
|
||||
`
|
||||
})
|
||||
export class VersionChildComponent implements OnChanges {
|
||||
@Input() major: number;
|
||||
@Input() minor: number;
|
||||
changeLog: string[] = [];
|
||||
|
||||
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
|
||||
let log: string[] = [];
|
||||
for (let propName in changes) {
|
||||
let changedProp = changes[propName];
|
||||
let to = JSON.stringify(changedProp.currentValue);
|
||||
if (changedProp.isFirstChange()) {
|
||||
log.push(`Initial value of ${propName} set to ${to}`);
|
||||
} else {
|
||||
let from = JSON.stringify(changedProp.previousValue);
|
||||
log.push(`${propName} changed from ${from} to ${to}`);
|
||||
}
|
||||
}
|
||||
this.changeLog.push(log.join(', '));
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,26 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'version-parent',
|
||||
template: `
|
||||
<h2>Source code version</h2>
|
||||
<button (click)="newMinor()">New minor version</button>
|
||||
<button (click)="newMajor()">New major version</button>
|
||||
<version-child [major]="major" [minor]="minor"></version-child>
|
||||
`
|
||||
})
|
||||
export class VersionParentComponent {
|
||||
major = 1;
|
||||
minor = 23;
|
||||
|
||||
newMinor() {
|
||||
this.minor++;
|
||||
}
|
||||
|
||||
newMajor() {
|
||||
this.major++;
|
||||
this.minor = 0;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,22 @@
|
||||
// #docregion
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-voter',
|
||||
template: `
|
||||
<h4>{{name}}</h4>
|
||||
<button (click)="vote(true)" [disabled]="voted">Agree</button>
|
||||
<button (click)="vote(false)" [disabled]="voted">Disagree</button>
|
||||
`
|
||||
})
|
||||
export class VoterComponent {
|
||||
@Input() name: string;
|
||||
@Output() onVoted = new EventEmitter<boolean>();
|
||||
voted = false;
|
||||
|
||||
vote(agreed: boolean) {
|
||||
this.onVoted.emit(agreed);
|
||||
this.voted = true;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
@ -0,0 +1,24 @@
|
||||
// #docregion
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'vote-taker',
|
||||
template: `
|
||||
<h2>Should mankind colonize the Universe?</h2>
|
||||
<h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
|
||||
<my-voter *ngFor="let voter of voters"
|
||||
[name]="voter"
|
||||
(onVoted)="onVoted($event)">
|
||||
</my-voter>
|
||||
`
|
||||
})
|
||||
export class VoteTakerComponent {
|
||||
agreed = 0;
|
||||
disagreed = 0;
|
||||
voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto'];
|
||||
|
||||
onVoted(agreed: boolean) {
|
||||
agreed ? this.agreed++ : this.disagreed++;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
9
aio/content/examples/component-interaction/src/demo.css
Normal file
9
aio/content/examples/component-interaction/src/demo.css
Normal file
@ -0,0 +1,9 @@
|
||||
/* Component Communication cookbook specific styles */
|
||||
.seconds {
|
||||
background-color: black;
|
||||
color: red;
|
||||
font-size: 3em;
|
||||
margin: 0.3em 0;
|
||||
text-align: center;
|
||||
width: 1.5em;
|
||||
}
|
29
aio/content/examples/component-interaction/src/index.html
Normal file
29
aio/content/examples/component-interaction/src/index.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passing information from parent to child</title>
|
||||
<base href="/">
|
||||
<style>
|
||||
.to-top {margin-top: 8px; display: block;}
|
||||
</style>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="demo.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-app>
|
||||
</body>
|
||||
|
||||
</html>
|
5
aio/content/examples/component-interaction/src/main.ts
Normal file
5
aio/content/examples/component-interaction/src/main.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
Reference in New Issue
Block a user