From f41242f18e56492b0bc97af0cf501525b5f8aaf3 Mon Sep 17 00:00:00 2001 From: Kapunahele Wong Date: Mon, 3 Dec 2018 13:42:53 -0500 Subject: [PATCH] docs: rewrite inputs/outputs section of Template Syntax (#27685) PR Close #27685 --- .../inputs-outputs/e2e/src/app.e2e-spec.ts | 70 +++ .../inputs-outputs/example-config.json | 0 .../src/app/aliasing/aliasing.component.css | 0 .../src/app/aliasing/aliasing.component.html | 7 + .../app/aliasing/aliasing.component.spec.ts | 25 + .../src/app/aliasing/aliasing.component.ts | 46 ++ .../inputs-outputs/src/app/app.component.css | 0 .../inputs-outputs/src/app/app.component.html | 45 ++ .../src/app/app.component.spec.ts | 27 ++ .../inputs-outputs/src/app/app.component.ts | 55 +++ .../inputs-outputs/src/app/app.module.ts | 28 ++ .../in-the-metadata.component.css | 0 .../in-the-metadata.component.html | 3 + .../in-the-metadata.component.spec.ts | 25 + .../in-the-metadata.component.ts | 32 ++ .../input-output/input-output.component.css | 0 .../input-output/input-output.component.html | 2 + .../input-output.component.spec.ts | 25 + .../input-output/input-output.component.ts | 25 + .../app/item-detail/item-detail.component.css | 0 .../item-detail/item-detail.component.html | 8 + .../item-detail/item-detail.component.spec.ts | 25 + .../app/item-detail/item-detail.component.ts | 16 + .../app/item-output/item-output.component.css | 0 .../item-output/item-output.component.html | 6 + .../item-output/item-output.component.spec.ts | 25 + .../app/item-output/item-output.component.ts | 22 + .../examples/inputs-outputs/src/index.html | 14 + .../examples/inputs-outputs/src/main.ts | 12 + .../examples/inputs-outputs/stackblitz.json | 10 + aio/content/guide/template-syntax.md | 446 +++++++++++++----- .../input-diagram-target-source.svg | 1 + .../inputs-outputs/input-output-diagram.svg | 1 + .../images/guide/inputs-outputs/input.svg | 1 + .../images/guide/inputs-outputs/output.svg | 1 + 35 files changed, 875 insertions(+), 128 deletions(-) create mode 100644 aio/content/examples/inputs-outputs/e2e/src/app.e2e-spec.ts create mode 100644 aio/content/examples/inputs-outputs/example-config.json create mode 100644 aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.css create mode 100644 aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.html create mode 100644 aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.spec.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/app.component.css create mode 100644 aio/content/examples/inputs-outputs/src/app/app.component.html create mode 100644 aio/content/examples/inputs-outputs/src/app/app.component.spec.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/app.component.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/app.module.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.css create mode 100644 aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.html create mode 100644 aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.spec.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.css create mode 100644 aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.html create mode 100644 aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.spec.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.css create mode 100644 aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.html create mode 100644 aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.spec.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.css create mode 100644 aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.html create mode 100644 aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.spec.ts create mode 100644 aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.ts create mode 100644 aio/content/examples/inputs-outputs/src/index.html create mode 100644 aio/content/examples/inputs-outputs/src/main.ts create mode 100644 aio/content/examples/inputs-outputs/stackblitz.json create mode 100644 aio/content/images/guide/inputs-outputs/input-diagram-target-source.svg create mode 100644 aio/content/images/guide/inputs-outputs/input-output-diagram.svg create mode 100644 aio/content/images/guide/inputs-outputs/input.svg create mode 100644 aio/content/images/guide/inputs-outputs/output.svg diff --git a/aio/content/examples/inputs-outputs/e2e/src/app.e2e-spec.ts b/aio/content/examples/inputs-outputs/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000000..d62786fff5 --- /dev/null +++ b/aio/content/examples/inputs-outputs/e2e/src/app.e2e-spec.ts @@ -0,0 +1,70 @@ +'use strict'; + +import { browser, element, by } from 'protractor'; +import { logging } from 'selenium-webdriver'; + +describe('Inputs and Outputs', function () { + + + beforeEach(() => { + browser.get(''); + }); + + + // helper function used to test what's logged to the console + async function logChecker(button, contents) { + const logs = await browser + .manage() + .logs() + .get(logging.Type.BROWSER); + const message = logs.filter(({ message }) => + message.indexOf(contents) !== -1 ? true : false + ); + console.log(message); + expect(message.length).toBeGreaterThan(0); + } + + it('should have title Inputs and Outputs', function () { + let title = element.all(by.css('h1')).get(0); + expect(title.getText()).toEqual('Inputs and Outputs'); + }); + + it('should add 123 to the parent list', async () => { + let addToParentButton = element.all(by.css('button')).get(0); + let addToListInput = element.all(by.css('input')).get(0); + let addedItem = element.all(by.css('li')).get(4); + await addToListInput.sendKeys('123'); + await addToParentButton.click(); + expect(addedItem.getText()).toEqual('123'); + }); + + it('should delete item', async () => { + let deleteButton = element.all(by.css('button')).get(1); + const contents = 'Child'; + await deleteButton.click(); + await logChecker(deleteButton, contents); + }); + + it('should log buy the item', async () => { + let buyButton = element.all(by.css('button')).get(2); + const contents = 'Child'; + await buyButton.click(); + await logChecker(buyButton, contents); + }); + + it('should save item for later', async () => { + let saveButton = element.all(by.css('button')).get(3); + const contents = 'Child'; + await saveButton.click(); + await logChecker(saveButton, contents); + }); + + it('should add item to wishlist', async () => { + let addToParentButton = element.all(by.css('button')).get(4); + let addedItem = element.all(by.css('li')).get(6); + await addToParentButton.click(); + expect(addedItem.getText()).toEqual('Television'); + }); + +}); + diff --git a/aio/content/examples/inputs-outputs/example-config.json b/aio/content/examples/inputs-outputs/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.css b/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.html b/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.html new file mode 100644 index 0000000000..ae7da190a3 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.html @@ -0,0 +1,7 @@ +

Save for later item: {{input1}}

+ + + +

Item for wishlist: {{input2}}

+ + diff --git a/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.spec.ts b/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.spec.ts new file mode 100644 index 0000000000..8f93c989bc --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AliasingComponent } from './aliasing.component'; + +describe('AliasingComponent', () => { + let component: AliasingComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AliasingComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AliasingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.ts b/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.ts new file mode 100644 index 0000000000..8eed8bddbd --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/aliasing/aliasing.component.ts @@ -0,0 +1,46 @@ +/* tslint:disable:use-input-property-decorator */ +/* tslint:disable:use-output-property-decorator */ + +/* tslint:disable:no-input-rename */ + + +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-aliasing', + templateUrl: './aliasing.component.html', + styleUrls: ['./aliasing.component.css'], + // #docregion alias + // tslint:disable: no-inputs-metadata-property no-outputs-metadata-property + inputs: ['input1: saveForLaterItem'], // propertyName:alias + outputs: ['outputEvent1: saveForLaterEvent'] + // tslint:disable: no-inputs-metadata-property no-outputs-metadata-property + // #enddocregion alias + +}) +export class AliasingComponent { + + input1: string; + outputEvent1: EventEmitter = new EventEmitter(); + + // #docregion alias-input-output + @Input('wishListItem') input2: string; // @Input(alias) + @Output('wishEvent') outputEvent2 = new EventEmitter(); // @Output(alias) propertyName = ... + // #enddocregion alias-input-output + + + saveIt() { + console.warn('Child says: emiting outputEvent1 with', this.input1); + this.outputEvent1.emit(this.input1); + } + + wishForIt() { + console.warn('Child says: emiting outputEvent2', this.input2); + this.outputEvent2.emit(this.input2); + } + + +} +/* tslint:enable:use-input-property-decorator */ +/* tslint:enable:use-output-property-decorator */ + diff --git a/aio/content/examples/inputs-outputs/src/app/app.component.css b/aio/content/examples/inputs-outputs/src/app/app.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/inputs-outputs/src/app/app.component.html b/aio/content/examples/inputs-outputs/src/app/app.component.html new file mode 100644 index 0000000000..d4a67c3170 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/app.component.html @@ -0,0 +1,45 @@ +

Inputs and Outputs

+ + + + +
+ + + + +

Parent component receiving value via @Output()

+ +
    +
  • {{item}}
  • +
+ +
+ +

Input and Output together

+

Open the console to see the EventEmitter at work when you click Delete.

+ + + + + +
+ +

Input and Output in the component class metadata

+

Open the console to see the EventEmitter at work when you click Buy.

+ + + + +
+ +

Aliasing Inputs and Outputs

+

See aliasing.component.ts for aliases and the console for the EventEmitter console logs.

+ + + +

Wishlist:

+
    +
  • {{wish}}
  • +
+ diff --git a/aio/content/examples/inputs-outputs/src/app/app.component.spec.ts b/aio/content/examples/inputs-outputs/src/app/app.component.spec.ts new file mode 100644 index 0000000000..bcbdf36b3e --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/app.component.spec.ts @@ -0,0 +1,27 @@ +import { TestBed, async } from '@angular/core/testing'; +import { AppComponent } from './app.component'; +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + AppComponent + ], + }).compileComponents(); + })); + it('should create the app', async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + })); + it(`should have as title 'app'`, async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('app'); + })); + it('should render title in a h1 tag', async(() => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!'); + })); +}); diff --git a/aio/content/examples/inputs-outputs/src/app/app.component.ts b/aio/content/examples/inputs-outputs/src/app/app.component.ts new file mode 100644 index 0000000000..515cbbd595 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/app.component.ts @@ -0,0 +1,55 @@ + +// #docplaster + +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) + +// #docregion parent-property +// #docregion add-new-item +export class AppComponent { +// #enddocregion add-new-item + currentItem = 'Television'; + // #enddocregion parent-property + + lastChanceItem = 'Beanbag'; +// #docregion add-new-item + items = ['item1', 'item2', 'item3', 'item4']; +// #enddocregion add-new-item + wishlist = ['Drone', 'Computer']; + + // #docregion add-new-item + + addItem(newItem: string) { + this.items.push(newItem); + } + // #enddocregion add-new-item + + + crossOffItem(item: string) { + console.warn(`Parent says: crossing off ${item}.`); + } + + buyClearanceItem(item) { + console.warn(`Parent says: buying ${item}.`); + } + + saveForLater(item) { + console.warn(`Parent says: saving ${item} for later.`); + } + + addToWishList(wish: string) { + console.warn(`Parent says: adding ${this.currentItem} to your wishlist.`); + this.wishlist.push(wish); + console.warn(this.wishlist); + } +// #docregion add-new-item +// #docregion parent-property +} +// #enddocregion add-new-item +// #enddocregion parent-property + diff --git a/aio/content/examples/inputs-outputs/src/app/app.module.ts b/aio/content/examples/inputs-outputs/src/app/app.module.ts new file mode 100644 index 0000000000..b5cc72fdcd --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/app.module.ts @@ -0,0 +1,28 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + + +import { AppComponent } from './app.component'; +import { ItemDetailComponent } from './item-detail/item-detail.component'; +import { ItemOutputComponent } from './item-output/item-output.component'; +import { InputOutputComponent } from './input-output/input-output.component'; +import { InTheMetadataComponent } from './in-the-metadata/in-the-metadata.component'; +import { AliasingComponent } from './aliasing/aliasing.component'; + + +@NgModule({ + declarations: [ + AppComponent, + ItemDetailComponent, + ItemOutputComponent, + InputOutputComponent, + InTheMetadataComponent, + AliasingComponent + ], + imports: [ + BrowserModule + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.css b/aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.html b/aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.html new file mode 100644 index 0000000000..960e4f16e5 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.html @@ -0,0 +1,3 @@ +

Latest clearance item: {{clearanceItem}}

+ + diff --git a/aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.spec.ts b/aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.spec.ts new file mode 100644 index 0000000000..eaa33528a9 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InTheMetadataComponent } from './in-the-metadata.component'; + +describe('InTheMetadataComponent', () => { + let component: InTheMetadataComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ InTheMetadataComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(InTheMetadataComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.ts b/aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.ts new file mode 100644 index 0000000000..465add0eca --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.ts @@ -0,0 +1,32 @@ +/* tslint:disable:use-input-property-decorator */ +/* tslint:disable:use-output-property-decorator */ + +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-in-the-metadata', + templateUrl: './in-the-metadata.component.html', + styleUrls: ['./in-the-metadata.component.css'], + // #docregion metadata + // tslint:disable: no-inputs-metadata-property no-outputs-metadata-property + inputs: ['clearanceItem'], + outputs: ['buyEvent'] + // tslint:enable: no-inputs-metadata-property no-outputs-metadata-property + // #enddocregion metadata + +}) +export class InTheMetadataComponent { + + + buyEvent = new EventEmitter(); + clearanceItem: string; + + buyIt() { + console.warn('Child says: emiting buyEvent with', this.clearanceItem); + this.buyEvent.emit(this.clearanceItem); + } + +} + +/* tslint:enable:use-input-property-decorator */ +/* tslint:enable:use-output-property-decorator */ diff --git a/aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.css b/aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.html b/aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.html new file mode 100644 index 0000000000..c6a1e1dc1e --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.html @@ -0,0 +1,2 @@ +Item: {{item}} + diff --git a/aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.spec.ts b/aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.spec.ts new file mode 100644 index 0000000000..7e49af8ac4 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InputOutputComponent } from './input-output.component'; + +describe('InputOutputComponent', () => { + let component: InputOutputComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ InputOutputComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(InputOutputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.ts b/aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.ts new file mode 100644 index 0000000000..f328ab8614 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/input-output/input-output.component.ts @@ -0,0 +1,25 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-input-output', + templateUrl: './input-output.component.html', + styleUrls: ['./input-output.component.css'] +}) +export class InputOutputComponent { +// #docregion input-output + @Input() item: string; +// #docregion output + @Output() deleteRequest = new EventEmitter(); +// #enddocregion output +// #enddocregion input-output + + lineThrough = ''; + + // #docregion delete-method + delete() { + console.warn('Child says: emiting item deleteRequest with', this.item); + this.deleteRequest.emit(this.item); + this.lineThrough = this.lineThrough ? '' : 'line-through'; + } + // #enddocregion delete-method +} diff --git a/aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.css b/aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.html b/aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.html new file mode 100644 index 0000000000..cd38989ce0 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.html @@ -0,0 +1,8 @@ +

Child component with @Input()

+ + +

+ Today's item: {{item}} +

+ + diff --git a/aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.spec.ts b/aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.spec.ts new file mode 100644 index 0000000000..7559cb65f6 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ItemDetailComponent } from './item-detail.component'; + +describe('ItemDetailComponent', () => { + let component: ItemDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ItemDetailComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.ts b/aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.ts new file mode 100644 index 0000000000..32f1b5fb59 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/item-detail/item-detail.component.ts @@ -0,0 +1,16 @@ +// #docplaster +// #docregion use-input +import { Component, Input } from '@angular/core'; // First, import Input +// #enddocregion use-input + +@Component({ + selector: 'app-item-detail', + templateUrl: './item-detail.component.html', + styleUrls: ['./item-detail.component.css'] +}) + +// #docregion use-input +export class ItemDetailComponent { + @Input() item: string; // decorate the property with @Input() +} +// #enddocregion use-input diff --git a/aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.css b/aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.html b/aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.html new file mode 100644 index 0000000000..c6baef5bfb --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.html @@ -0,0 +1,6 @@ +

Child component with @Output()

+ + + + + diff --git a/aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.spec.ts b/aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.spec.ts new file mode 100644 index 0000000000..c307491352 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ItemOutputComponent } from './item-output.component'; + +describe('ItemOutputComponent', () => { + let component: ItemOutputComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ItemOutputComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemOutputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.ts b/aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.ts new file mode 100644 index 0000000000..68bbc6e584 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/app/item-output/item-output.component.ts @@ -0,0 +1,22 @@ +// #docregion imports +import { Component, Output, EventEmitter } from '@angular/core'; +// #enddocregion imports + +@Component({ + selector: 'app-item-output', + templateUrl: './item-output.component.html', + styleUrls: ['./item-output.component.css'] +}) + +// #docregion item-output-class +export class ItemOutputComponent { +// #docregion item-output + + @Output() newItemEvent = new EventEmitter(); + +// #enddocregion item-output + addNewItem(value: string) { + this.newItemEvent.emit(value); + } +} +// #enddocregion item-output-class diff --git a/aio/content/examples/inputs-outputs/src/index.html b/aio/content/examples/inputs-outputs/src/index.html new file mode 100644 index 0000000000..6565652186 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/index.html @@ -0,0 +1,14 @@ + + + + + Inputs and Outputs + + + + + + + + + diff --git a/aio/content/examples/inputs-outputs/src/main.ts b/aio/content/examples/inputs-outputs/src/main.ts new file mode 100644 index 0000000000..91ec6da5f0 --- /dev/null +++ b/aio/content/examples/inputs-outputs/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.log(err)); diff --git a/aio/content/examples/inputs-outputs/stackblitz.json b/aio/content/examples/inputs-outputs/stackblitz.json new file mode 100644 index 0000000000..f52293403f --- /dev/null +++ b/aio/content/examples/inputs-outputs/stackblitz.json @@ -0,0 +1,10 @@ +{ + "description": "Inputs and Outputs", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1,2].*" + ], + "file": "src/app/app.component.ts", + "tags": ["Inputs and Outputs"] +} diff --git a/aio/content/guide/template-syntax.md b/aio/content/guide/template-syntax.md index b4c30e6ed5..487912e4ec 100644 --- a/aio/content/guide/template-syntax.md +++ b/aio/content/guide/template-syntax.md @@ -1760,170 +1760,360 @@ This example declares the `fax` variable as `ref-fax` instead of `#fax`. {@a inputs-outputs} -## Input and Output properties +## `@Input()` and `@Output()` properties -An _Input_ property is a _settable_ property annotated with an `@Input` decorator. -Values flow _into_ the property when it is data bound with a [property binding](#property-binding) +`@Input()` and `@Output()` allow Angular to share data between the parent context and child directives. An `@Input()` property is writable while an `@Output()` property is observable. -An _Output_ property is an _observable_ property annotated with an `@Output` decorator. -The property almost always returns an Angular [`EventEmitter`](api/core/EventEmitter). -Values flow _out_ of the component as events bound with an [event binding](#event-binding). +Consider this example of a child/parent relationship: -You can only bind to _another_ component or directive through its _Input_ and _Output_ properties. +```html + + + -
+``` -Remember that all **components** are **directives**. +Here, the `` selector, or child directive, is embedded +within a ``, which serves as the child's context. + +`@Input()` and `@Output()` act as +the API, or application programming interface, of the child +component in that they allow the child to +communicate with the parent. Think of `@Input()` and `@Output()` like ports +or doorways—`@Input()` is the doorway into the component allowing data +to flow in while `@Output()` is the doorway out of the component, allowing the +child component to send data out. + +This section about `@Input()` and `@Output()` has its own . The following subsections highlight +key points in the sample app. + +
+ +#### `@Input()` and `@Output()` are independent + +Though `@Input()` and `@Output()` often appear together in apps, you can use +them separately. If the nested +component is such that it only needs to send data to its parent, you wouldn't +need an `@Input()`, only an `@Output()`. The reverse is also true in that if the +child only needs to receive data from the parent, you'd only neeed `@Input()`. -The following discussion refers to _components_ for brevity and -because this topic is mostly a concern for component authors.
-

Discussion

+{@a input} -You are usually binding a template to its _own component class_. -In such binding expressions, the component's property or method is to the _right_ of the (`=`). +## How to use `@Input()` - +Use the `@Input()` decorator in a child component or directive to let Angular know +that a property in that component can receive its value from its parent component. +It helps to remember that the data flow is from the perspective of the +child component. So an `@Input()` allows data to be input _into_ the +child component from the parent component. + + +
+ Input data flow diagram +
+ +To illustrate the use of `@Input()`, edit these parts of your app: + +* The child component class and template +* The parent component class and template + + +### In the child + +To use the `@Input()` decorator in a child component class, first import +`Input` and then decorate the property with `@Input()`: + + -The `iconUrl` and `onSave` are members of the `AppComponent` class. -They are _not_ decorated with `@Input()` or `@Output`. -Angular does not object. -**You can always bind to a public property of a component in its own template.** -It doesn't have to be an _Input_ or _Output_ property +In this case, `@Input()` decorates the property item, which has +a type of `string`, however, `@Input()` properties can have any type, such as +`number`, `string`, `boolean`, or `object`. The value for `item` will come from the parent component, which the next section covers. -A component's class and template are closely coupled. -They are both parts of the same thing. -Together they _are_ the component. -Exchanges between a component class and its template are internal implementation details. +Next, in the child component template, add the following: -### Binding to a different component - -You can also bind to a property of a _different_ component. -In such bindings, the _other_ component's property is to the _left_ of the (`=`). - -In the following example, the `AppComponent` template binds `AppComponent` class members to properties of the `HeroDetailComponent` whose selector is `'app-hero-detail'`. - - + -The Angular compiler _may_ reject these bindings with errors like this one: + + +### In the parent + +The next step is to bind the property in the parent component's template. +In this example, the parent component template is `app.component.html`. + +First, use the child's selector, here ``, as a directive within the +parent component template. Then, use [property binding](guide/template-syntax#property-binding) +to bind the property in the child to the property of the parent. + + + + +Next, in the parent component class, `app.component.ts`, designate a value for `currentItem`: + + + + +With `@Input()`, Angular passes the value for `currentItem` to the child so that `item` renders as `Television`. + +The following diagram shows this structure: + +
+ Property binding diagram +
+ +The target in the square brackets, `[]`, is the property you decorate +with `@Input()` in the child component. The binding source, the part +to the right of the equal sign, is the data that the parent +component passes to the nested component. + +The key takeaway is that when binding to a child component's property in a parent component—that is, what's +in square brackets—you must +decorate the property with `@Input()` in the child component. + +
+ +#### `OnChanges` and `@Input()` + +To watch for changes on an `@Input()` property, use +`OnChanges`, one of Angular's [lifecycle hooks](guide/lifecycle-hooks#onchanges). +`OnChanges` is specifically designed to work with properties that have the +`@Input()` decorator. See the [`OnChanges`](guide/lifecycle-hooks#onchanges) section of the [Lifecycle Hooks](guide/lifecycle-hooks) guide for more details and examples. + +
+ +{@a output} + +## How to use `@Output()` + +Use the `@Output()` decorator in the child component or directive to allow data to flow from +the child _out_ to the parent. + +An `@Output()` property should normally be initialized to an Angular [`EventEmitter`](api/core/EventEmitter) with values flowing out of the component as [events](#event-binding). + + +
+ Output diagram +
+ +Just like with `@Input()`, you can use `@Output()` +on a property of the child component but its type should be +`EventEmitter`. + +`@Output()` marks a property in a child component as a doorway +through which data can travel from the child to the parent. +The child component then has to raise an event so the +parent knows something has changed. To raise an event, +`@Output()` works hand in hand with `EventEmitter`, +which is a class in `@angular/core` that you +use to emit custom events. + +When you use `@Output()`, edit these parts of your app: + +* The child component class and template +* The parent component class and template + + +The following example shows how to set up an `@Output()` in a child +component that pushes data you enter in an HTML `` to an array in the +parent component. + +
+ +The HTML element `` and the Angular decorator `@Input()` +are different. This documentation is about component communication in Angular as it pertains to `@Input()` and `@Output()`. For more information on the HTML element ``, see the [W3C Recommendation](https://www.w3.org/TR/html5/sec-forms.html#the-input-element). + +
+ +### In the child + +This example features an `` where a user can enter a value and click a `