docs: rewrite inputs/outputs section of Template Syntax (#27685)

PR Close #27685
This commit is contained in:
Kapunahele Wong 2018-12-03 13:42:53 -05:00 committed by Andrew Kushnir
parent e2fd628618
commit f41242f18e
35 changed files with 875 additions and 128 deletions

View File

@ -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');
});
});

View File

@ -0,0 +1,7 @@
<p>Save for later item: {{input1}}</p>
<button (click)="saveIt()"> Save for later</button>
<p>Item for wishlist: {{input2}}</p>
<button (click)="wishForIt()"> Add to wishlist</button>

View File

@ -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<AliasingComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AliasingComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AliasingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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<string> = new EventEmitter<string>();
// #docregion alias-input-output
@Input('wishListItem') input2: string; // @Input(alias)
@Output('wishEvent') outputEvent2 = new EventEmitter<string>(); // @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 */

View File

@ -0,0 +1,45 @@
<h1>Inputs and Outputs</h1>
<!-- #docregion input-parent -->
<app-item-detail [item]="currentItem"></app-item-detail>
<!-- #enddocregion input-parent -->
<hr>
<!-- #docregion output-parent -->
<app-item-output (newItemEvent)="addItem($event)"></app-item-output>
<!-- #enddocregion output-parent -->
<h3>Parent component receiving value via @Output()</h3>
<ul>
<li *ngFor="let item of items">{{item}}</li>
</ul>
<hr>
<h2>Input and Output together</h2>
<p>Open the console to see the EventEmitter at work when you click Delete.</p>
<!-- #docregion together -->
<app-input-output [item]="currentItem" (deleteRequest)="crossOffItem($event)"></app-input-output>
<!-- #enddocregion together -->
<hr>
<h2>Input and Output in the component class metadata</h2>
<p>Open the console to see the EventEmitter at work when you click Buy.</p>
<app-in-the-metadata [clearanceItem]="lastChanceItem" (buyEvent)="buyClearanceItem($event)"></app-in-the-metadata>
<hr>
<h2>Aliasing Inputs and Outputs</h2>
<p>See aliasing.component.ts for aliases and the console for the EventEmitter console logs.</p>
<app-aliasing [saveForLaterItem]="currentItem" (saveForLaterEvent)="saveForLater($event)" [wishListItem]="currentItem" (wishEvent)="addToWishList($event)"></app-aliasing>
<h2>Wishlist:</h2>
<ul>
<li *ngFor="let wish of wishlist">{{wish}}</li>
</ul>

View File

@ -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!');
}));
});

View File

@ -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

View File

@ -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 { }

View File

@ -0,0 +1,3 @@
<p>Latest clearance item: {{clearanceItem}}</p>
<button (click)="buyIt()"> Buy it with an Output!</button>

View File

@ -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<InTheMetadataComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ InTheMetadataComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(InTheMetadataComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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<string>();
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 */

View File

@ -0,0 +1,2 @@
<span [style.text-decoration]="lineThrough">Item: {{item}}</span>
<button (click)="delete()">Delete it with an Output!</button>

View File

@ -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<InputOutputComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ InputOutputComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(InputOutputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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<string>();
// #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
}

View File

@ -0,0 +1,8 @@
<h2>Child component with @Input()</h2>
<!-- #docregion property-in-template -->
<p>
Today's item: {{item}}
</p>
<!-- #enddocregion property-in-template -->

View File

@ -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<ItemDetailComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ItemDetailComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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

View File

@ -0,0 +1,6 @@
<h2>Child component with @Output()</h2>
<!-- #docregion child-output -->
<label>Add an item: <input #newItem></label>
<button (click)="addNewItem(newItem.value)">Add to parent's list</button>
<!-- #enddocregion child-output -->

View File

@ -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<ItemOutputComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ItemOutputComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemOutputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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<string>();
// #enddocregion item-output
addNewItem(value: string) {
this.newItemEvent.emit(value);
}
}
// #enddocregion item-output-class

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Inputs and Outputs</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -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));

View File

@ -0,0 +1,10 @@
{
"description": "Inputs and Outputs",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["Inputs and Outputs"]
}

View File

@ -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
<parent-component>
<child-component></child-component>
</parent-component>
<div class="alert is-important">
```
Remember that all **components** are **directives**.
Here, the `<child-component>` selector, or child directive, is embedded
within a `<parent-component>`, 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&mdash;`@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 <live-example name="inputs-outputs"></live-example>. The following subsections highlight
key points in the sample app.
<div class="alert is-helpful">
#### `@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.
</div>
<h3 class="no-toc">Discussion</h3>
{@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()`
<code-example path="template-syntax/src/app/app.component.html" region="io-1" header="src/app/app.component.html" linenums="false">
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.
<figure>
<img src="generated/images/guide/inputs-outputs/input.svg" alt="Input data flow diagram">
</figure>
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()`:
<code-example path="inputs-outputs/src/app/item-detail/item-detail.component.ts" region="use-input" header="src/app/item-detail/item-detail.component.ts" linenums="false">
</code-example>
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 <code class="no-auto-link">item</code>, 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'`.
<code-example path="template-syntax/src/app/app.component.html" region="io-2" header="src/app/app.component.html" linenums="false">
<code-example path="inputs-outputs/src/app/item-detail/item-detail.component.html" region="property-in-template" header="src/app/item-detail/item-detail.component.html" linenums="false">
</code-example>
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 `<app-item-detail>`, 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.
<code-example path="inputs-outputs/src/app/app.component.html" region="input-parent" header="src/app/app.component.html" linenums="false">
</code-example>
Next, in the parent component class, `app.component.ts`, designate a value for `currentItem`:
<code-example path="inputs-outputs/src/app/app.component.ts" region="parent-property" header="src/app/app.component.ts" linenums="false">
</code-example>
With `@Input()`, Angular passes the value for `currentItem` to the child so that `item` renders as `Television`.
The following diagram shows this structure:
<figure>
<img src="generated/images/guide/inputs-outputs/input-diagram-target-source.svg" alt="Property binding diagram">
</figure>
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&mdash;that is, what's
in square brackets&mdash;you must
decorate the property with `@Input()` in the child component.
<div class="alert is-helpful">
#### `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.
</div>
{@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).
<figure>
<img src="generated/images/guide/inputs-outputs/output.svg" alt="Output diagram">
</figure>
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 `<input>` to an array in the
parent component.
<div class="alert is-helpful">
The HTML element `<input>` 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 `<input>`, see the [W3C Recommendation](https://www.w3.org/TR/html5/sec-forms.html#the-input-element).
</div>
### In the child
This example features an `<input>` where a user can enter a value and click a `<button>` that raises an event. The `EventEmitter` then relays the data to the parent component.
First, be sure to import `Output` and `EventEmitter`
in the child component class:
```js
import { Output, EventEmitter } from '@angular/core';
```
Next, still in the child, decorate a property with `@Output()` in the component class.
The following example `@Output()` is called `newItemEvent` and its type is
`EventEmitter`, which means it's an event.
<code-example path="inputs-outputs/src/app/item-output/item-output.component.ts" region="item-output" header="src/app/item-output/item-output.component.ts" linenums="false">
</code-example>
The different parts of the above declaration are as follows:
* `@Output()`&mdash;a decorator function marking the property as a way for data to go from the child to the parent
* `newItemEvent`&mdash;the name of the `@Output()`
* `EventEmitter<string>`&mdash;the `@Output()`'s type
* `new EventEmitter<string>()`&mdash;tells Angular to create a new event emitter and that the data it emits is of type string. The type could be any type, such as `number`, `boolean`, and so on. For more information on `EventEmitter`, see the [EventEmitter API documentation](api/core/EventEmitter).
Next, create an `addNewItem()` method in the same component class:
<code-example path="inputs-outputs/src/app/item-output/item-output.component.ts" region="item-output-class" header="src/app/item-output/item-output.component.ts" linenums="false">
</code-example>
The `addNewItem()` function uses the `@Output()`, `newItemEvent`,
to raise an event in which it emits the value the user
types into the `<input>`. In other words, when
the user clicks the add button in the UI, the child lets the parent know
about the event and gives that data to the parent.
#### In the child's template
The child's template has two controls. The first is an HTML `<input>` with a
[template reference variable](guide/template-syntax#ref-var) , `#newItem`,
where the user types in an item name. Whatever the user types
into the `<input>` gets stored in the `#newItem` variable.
<code-example path="inputs-outputs/src/app/item-output/item-output.component.html" region="child-output" header="src/app/item-output/item-output.component.html" linenums="false">
</code-example>
The second element is a `<button>`
with an [event binding](guide/template-syntax#event-binding). You know it's
an event binding because the part to the left of the equal
sign is in parentheses, `(click)`.
The `(click)` event is bound to the `addNewItem()` method in the child component class which
takes as its argument whatever the value of `#newItem` is.
Now the child component has an `@Output()`
for sending data to the parent and a method for raising an event.
The next step is in the parent.
### In the parent
In this example, the parent component is `AppComponent`, but you could use
any component in which you could nest the child.
The `AppComponent` in this example features a list of `items`
in an array and a method for adding more items to the array.
<code-example path="inputs-outputs/src/app/app.component.ts" region="add-new-item" header="src/app/app.component.ts" linenums="false">
</code-example>
The `addItem()` method takes an argument in the form of a string
and then pushes, or adds, that string to the `items` array.
#### In the parent's template
Next, in the parent's template, bind the parent's
method to the child's event. Put the child selector, here `<app-item-output>`,
within the parent component's
template, `app.component.html`.
<code-example path="inputs-outputs/src/app/app.component.html" region="output-parent" header="src/app/app.component.html" linenums="false">
</code-example>
The event binding, `(newItemEvent)='addItem($event)'`, tells
Angular to connect the event in the child, `newItemEvent`, to
the method in the parent, `addItem()`, and that the event that the child
is notifying the parent about is to be the argument of `addItem()`.
In other words, this is where the actual hand off of data takes place.
The `$event` contains the data that the user types into the `<input>`
in the child template UI.
Now, in order to see the `@Output()` working, add the following to the parent's template:
```
<ul>
<li *ngFor="let item of items">{{item}}</li>
</ul>
```
The `*ngFor` iterates over the items in the `items` array. When you enter a value in the child's `<input>` and click the button, the child emits the event and the parent's `addItem()` method pushes the value to the `items` array and it renders in the list.
## `@Input()` and `@Output()` together
You can use `@Input()` and `@Output()` on the same child component as in the following:
<code-example path="inputs-outputs/src/app/app.component.html" region="together" header="src/app/app.component.html" linenums="false">
</code-example>
The target, `item`, which is an `@Input()` property in the child component class, receives its value from the parent's property, `currentItem`. When you click delete, the child component raises an event, `deleteRequest`, which is the argument for the parent's `crossOffItem()` method.
The following diagram is of an `@Input()` and an `@Output()` on the same
child component and shows the different parts of each:
<figure>
<img src="generated/images/guide/inputs-outputs/input-output-diagram.svg" alt="Input/Output diagram">
</figure>
As the diagram shows, use inputs and outputs together in the same manner as using them separately. Here, the child selector is `<app-input-output>` with `item` and `deleteRequest` being `@Input()` and `@Output()`
properties in the child component class. The property `currentItem` and the method `crossOffItem()` are both in the parent component class.
To combine property and event bindings using the banana-in-a-box
syntax, `[()]`, see [Two-way Binding](guide/template-syntax#two-way).
For more detail on how these work, see the previous sections on [Input](guide/template-syntax#input) and [Output](guide/template-syntax#output). To see it in action, see the <live-example name="inputs-outputs">Inputs and Outputs Example</live-example>.
## `@Input()` and `@Output()` declarations
Instead of using the `@Input()` and `@Output()` decorators
to declare inputs and outputs, you can identify
members in the `inputs` and `outputs` arrays
of the directive metadata, as in this example:
<code-example path="inputs-outputs/src/app/in-the-metadata/in-the-metadata.component.ts" region="metadata" header="src/app/app.component.html" linenums="false">
</code-example>
While declaring `inputs` and `outputs` in the `@Directive` and `@Component`
metadata is possible, it is a better practice to use the `@Input()` and `@Output()`
class decorators instead, as follows:
<code-example path="inputs-outputs/src/app/input-output/input-output.component.ts" region="input-output" header="src/app/app.component.html" linenums="false">
</code-example>
See the [Decorate input and output properties](guide/styleguide#decorate-input-and-output-properties) section of the
[Style Guide](guide/styleguide) for details.
<div class="alert is-helpful">
If you get a template parse error when trying to use inputs or outputs, but you know that the
properties do indeed exist, double check
that your properties are annotated with `@Input()` / `@Output()` or that you've declared
them in an `inputs`/`outputs` array:
<code-example language="sh" class="code-shell">
Uncaught Error: Template parse errors:
Can't bind to 'hero' since it isn't a known property of 'app-hero-detail'
</code-example>
You know that `HeroDetailComponent` has `hero` and `deleteRequest` properties.
But the Angular compiler refuses to recognize them.
**The Angular compiler won't bind to properties of a different component
unless they are Input or Output properties**.
There's a good reason for this rule.
It's OK for a component to bind to its _own_ properties.
The component author is in complete control of those bindings.
But other components shouldn't have that kind of unrestricted access.
You'd have a hard time supporting your component if anyone could bind to any of its properties.
Outside components should only be able to bind to the component's public binding API.
Angular asks you to be _explicit_ about that API.
It's up to _you_ to decide which properties are available for binding by
external components.
#### TypeScript _public_ doesn't matter
You can't use the TypeScript _public_ and _private_ access modifiers to
shape the component's public binding API.
<div class="alert is-important">
All data bound properties must be TypeScript _public_ properties.
Angular never binds to a TypeScript _private_ property.
</div>
Angular requires some other way to identify properties that _outside_ components are allowed to bind to.
That _other way_ is the `@Input()` and `@Output()` decorators.
### Declaring Input and Output properties
In the sample for this guide, the bindings to `HeroDetailComponent` do not fail
because the data bound properties are annotated with `@Input()` and `@Output()` decorators.
<code-example path="template-syntax/src/app/hero-detail.component.ts" region="input-output-1" header="src/app/hero-detail.component.ts" linenums="false">
</code-example>
<div class="alert is-helpful">
Alternatively, you can identify members in the `inputs` and `outputs` arrays
of the directive metadata, as in this example:
<code-example path="template-syntax/src/app/hero-detail.component.ts" region="input-output-2" header="src/app/hero-detail.component.ts" linenums="false">
Can't bind to 'item' since it isn't a known property of 'app-item-detail'
</code-example>
</div>
### Input or output?
{@a aliasing-io}
*Input* properties usually receive data values.
*Output* properties expose event producers, such as `EventEmitter` objects.
## Aliasing inputs and outputs
The terms _input_ and _output_ reflect the perspective of the target directive.
Sometimes the public name of an input/output property should be different from the internal name. While it is a best practice to avoid this situation, Angular does
offer a solution.
<figure>
<img src="generated/images/guide/template-syntax/input-output.png" alt="Inputs and outputs">
</figure>
### Aliasing in the metadata
`HeroDetailComponent.hero` is an **input** property from the perspective of `HeroDetailComponent`
because data flows *into* that property from a template binding expression.
Alias inputs and outputs in the metadata using a colon-delimited (`:`) string with
the directive property name on the left and the public alias on the right:
`HeroDetailComponent.deleteRequest` is an **output** property from the perspective of `HeroDetailComponent`
because events stream *out* of that property and toward the handler in a template binding statement.
<h3 id='aliasing-io'>
Aliasing input/output properties
</h3>
Sometimes the public name of an input/output property should be different from the internal name.
This is frequently the case with [attribute directives](guide/attribute-directives).
Directive consumers expect to bind to the name of the directive.
For example, when you apply a directive with a `myClick` selector to a `<div>` tag,
you expect to bind to an event property that is also called `myClick`.
<code-example path="template-syntax/src/app/app.component.html" region="myClick" header="src/app/app.component.html" linenums="false">
<code-example path="inputs-outputs/src/app/aliasing/aliasing.component.ts" region="alias" header="src/app/app.component.html" linenums="false">
</code-example>
However, the directive name is often a poor choice for the name of a property within the directive class.
The directive name rarely describes what the property does.
The `myClick` directive name is not a good name for a property that emits click messages.
Fortunately, you can have a public name for the property that meets conventional expectations,
while using a different name internally.
In the example immediately above, you are actually binding *through the* `myClick` *alias* to
the directive's own `clicks` property.
### Aliasing with the `@Input()`/`@Output()` decorator
You can specify the alias for the property name by passing it into the input/output decorator like this:
You can specify the alias for the property name by passing the alias name to the `@Input()`/`@Output()` decorator. The internal name remains as usual.
<code-example path="template-syntax/src/app/click.directive.ts" region="output-myClick" header="src/app/click.directive.ts" linenums="false">
<code-example path="inputs-outputs/src/app/aliasing/aliasing.component.ts" region="alias-input-output" header="src/app/app.component.html" linenums="false">
</code-example>
<div class="alert is-helpful">
You can also alias property names in the `inputs` and `outputs` arrays.
You write a colon-delimited (`:`) string with
the directive property name on the *left* and the public alias on the *right*:
<code-example path="template-syntax/src/app/click.directive.ts" region="output-myClick2" header="src/app/click.directive.ts" linenums="false">
</code-example>
</div>
<hr/>

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 502 304" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;"><g id="Input-Diagram" serif:id="Input Diagram"><g><path d="M71.445,103.213l4.304,0l0,12.588l3.996,0l-6.148,6.148l-6.148,-6.148l3.996,0l0,-12.588Z"/><g transform="matrix(1,0,0,1,-6,-207)"><text x="60.505px" y="279px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:18px;">child</text><text x="47.501px" y="300.23px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:18px;">selector</text></g></g><g><path d="M177.039,211.489l-10.085,-0.151l0.612,-40.853l-9.364,-0.141l14.622,-14.191l14.192,14.623l-9.365,-0.14l-0.612,40.853Z" style="fill:#df002d;"/><path d="M244.694,214.06c0,-1.137 -0.923,-2.06 -2.06,-2.06l-133.88,0c-1.137,0 -2.06,0.923 -2.06,2.06l0,81.88c0,1.137 0.923,2.06 2.06,2.06l133.88,0c1.137,0 2.06,-0.923 2.06,-2.06l0,-81.88Z" style="fill:#fff;stroke:#df002d;stroke-width:1px;"/><g transform="matrix(1,0,0,1,95.5434,-32.5102)"><text x="39.962px" y="279px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:20px;">@Input()</text><text x="19.552px" y="300.23px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:20px;">property from</text><text x="58.448px" y="321.46px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:20px;">child</text></g><g><rect x="137.114" y="189.938" width="74" height="33" style="fill:#fff;stroke:#df002d;stroke-width:1px;"/><text x="149.229px" y="212.534px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:20px;">target</text></g></g><g><path d="M265.001,74.973l10.251,0.023l-0.077,35.114l9.519,0.021l-14.676,14.612l-14.612,-14.676l9.519,0.021l0.076,-35.115Z" style="fill:#3345cc;"/><path d="M341.287,32.263c0,-0.833 -0.676,-1.51 -1.509,-1.51l-133.982,0c-0.833,0 -1.509,0.677 -1.509,1.51l0,59.981c0,0.833 0.676,1.509 1.509,1.509l133.982,0c0.833,0 1.509,-0.676 1.509,-1.509l0,-59.981Z" style="fill:#fff;stroke:#3345cc;stroke-width:1px;"/><g id="source"><rect x="236.42" y="5.938" width="74" height="33" style="fill:#fff;stroke:#3345cc;stroke-width:1px;"/><text x="243.535px" y="28.534px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:20px;">source</text></g><g transform="matrix(1,0,0,1,193.035,-217.181)"><text x="19.552px" y="279px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:20px;">property from</text><text x="50.66px" y="300.23px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:20px;">parent</text></g></g><g transform="matrix(1.3525,0,0,1.3525,1,129.629)"><text x="0px" y="10.592px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:14.787px;">&lt;app-item-detail [</text><text x="114.66px" y="10.592px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:14.787px;fill:#df002d;">item</text><text x="142.596px" y="10.592px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:14.787px;">]=&quot;</text><text x="160.589px" y="10.592px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:14.787px;fill:#3345cc;">currentItem</text><text x="235.371px" y="10.592px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:14.787px;">&quot;&gt;&lt;/app-item-detail&gt;</text></g></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 671 346" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><use xlink:href="#_Image1" x="11.08" y="148.193" width="248.853px" height="182.853px" transform="matrix(0.999408,0,0,0.999194,0,0)"/><path d="M255,161.679c0,-4.79 -3.889,-8.679 -8.679,-8.679l-221.642,0c-4.79,0 -8.679,3.889 -8.679,8.679l0,155.642c0,4.79 3.889,8.679 8.679,8.679l221.642,0c4.79,0 8.679,-3.889 8.679,-8.679l0,-155.642Z" style="fill:#fff;"/><use xlink:href="#_Image1" x="414.233" y="151.868" width="248.853px" height="182.853px" transform="matrix(0.999408,0,0,0.999194,0,0)"/><path d="M657.914,165.351c0,-4.791 -3.889,-8.68 -8.679,-8.68l-221.642,0c-4.79,0 -8.679,3.889 -8.679,8.68l0,155.641c0,4.79 3.889,8.679 8.679,8.679l221.642,0c4.79,0 8.679,-3.889 8.679,-8.679l0,-155.641Z" style="fill:#fff;"/><text x="229px" y="65px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:64px;">@Input</text><text x="277.507px" y="209.157px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:30px;">data flow</text><text x="38.186px" y="258.279px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:64px;">Parent</text><text x="462.393px" y="258.536px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:64px;">Child</text><path d="M277,248.7l0,-15.4l99,0l0,-14.3l22,22l-22,22l0,-14.3l-99,0Z" style="fill:#1976d2;"/></g><defs><image id="_Image1" width="249px" height="183px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPkAAAC3CAYAAADQHfiMAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFmElEQVR4nO3b21LbSBSG0W1zTCZzeP/HnJlyAGM8F1KjtpANVQOG/LVWVZfBWCQ3H7sly6t6m9WRr089B/x/+1eeW/r5gdfiXJ1YbzkeeB/77vHYWnQs0j7mdVVdjI/9Wp04HnhfLeSn2dqNj0djv1z4ZS3ei3FdjutqfGzPz6c68DH6gHfjeqyq7fj42D3fXv9sHnk/uS+r6npcN+O6ril60xzOo5/iLeqHqrrv1nZ8bZvwz6H3kfcT/KqGqL+N6/v4eFPTRG/b9nYs8P5arC3eNsHvq+pnVW3GdVdDh9s6PH9/EXmb4Dc1hP2jqn6vqt/G72/rMHKTHD7efJJva4h6U9Puej177a5mkfdT/LqGmH9U1Z9V9UcNoX+vaZK3C3ECh/Noke9qmuRt6F6Mr5lflFtV1b6PfF3TeXib4n9U1V/j19/q8K+GKQ7nM5/m/cCtmi7G9Rfi9jVG3k/xpa36jxq267c1XV33PjmcV3+e3V/8rpqme7sY91BD6E9VtZpP8v6C2/eaLrjd1jDFBQ6fp4XeX/R+qsNz9Jvx64caGz22Xb+pIez+bTNbdPhcfXtLvbZh3Pd6EG7//vhVt/q3ywQOn6/12kKf99pfGF+tZwf1d7j1L14X8NW0No91+zzJq17eqz6/R73KFIevpO/y5OdK5hH3oS996gz4OuaNLnW7mm/DfYwUfk3zYf3s2Lm2wOHXs9itC2oQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQTuQQ7ljk+7P+L4D3sNjtPPJ990Khw6+j7/ag3fXCD/dV9TT7XvDw9cwbXep2v1548dNsmezw9fRdLjX73Ot69sJdVT12a9cdCHwtrc1j3e6rpu36PPJttx7r5VQHPk8/vZd67SPfX84Oeqyqh6q6r6q78fGmqi5r+IOw6v6h/mvg48236PNe78bv22DeVw3x9gdtxwN+VtWmqm6r6qqmbf1lVV3UFLjQ4Tz6wNuO+66mVjc1DeZtzSLfzw68Hw+4riHwi/GXP43P9VNd5HAe/Wl1m+A/q+rfqvpnfNzU0G/bsp/crm9qmto1HrCtYevewp9v34GP0183azvuTQ2B/11T5Ce3622aP9QQcNuit238XU3bd9Mczmc+xVuPmxribqudk++6Y54jb79oV0O09+Nz7Ze28/Q2yVvk7Q+B0OFjtHPx+dX0/tpZfz7eb9Wr6mXkNb6gfb90nn5ZJjmc03yS91fV22pvob24GWYp0Bbuxbha1G2Ct+dXJXI4h/6O1F0d3s/S3wBzsE1vjgXaB7yu6UJbvwQO53PstvODG19q4Ya11yJdnVhvOR54H0sfJHvTh8jeGulrd7qJHT7GUrz7V35+4D85mHc4rh09igAAAABJRU5ErkJggg=="/></defs></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 666 340" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><use xlink:href="#_Image1" x="7.078" y="144.19" width="248.853px" height="182.853px" transform="matrix(0.999408,0,0,0.999194,0,0)"/><path d="M251,157.679c0,-4.79 -3.889,-8.679 -8.679,-8.679l-221.642,0c-4.79,0 -8.679,3.889 -8.679,8.679l0,155.642c0,4.79 3.889,8.679 8.679,8.679l221.642,0c4.79,0 8.679,-3.889 8.679,-8.679l0,-155.642Z" style="fill:#fff;"/><use xlink:href="#_Image1" x="410.231" y="147.864" width="248.853px" height="182.853px" transform="matrix(0.999408,0,0,0.999194,0,0)"/><path d="M653.914,161.351c0,-4.791 -3.889,-8.68 -8.679,-8.68l-221.642,0c-4.79,0 -8.679,3.889 -8.679,8.68l0,155.641c0,4.79 3.889,8.679 8.679,8.679l221.642,0c4.79,0 8.679,-3.889 8.679,-8.679l0,-155.641Z" style="fill:#fff;"/><text x="194px" y="61px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:64px;">@Output</text><text x="273.507px" y="205.157px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:30px;">data flow</text><text x="38.186px" y="254.279px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:64px;">Parent</text><text x="458.393px" y="254.536px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:64px;">Child</text><path d="M394,229.3l0,15.4l-99,0l0,14.3l-22,-22l22,-22l0,14.3l99,0Z" style="fill:#1976d2;"/></g><defs><image id="_Image1" width="249px" height="183px" xlink:href="data:image/png;base64,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"/></defs></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB