docs: rewrite inputs/outputs section of Template Syntax (#27685)
PR Close #27685
This commit is contained in:
parent
e2fd628618
commit
f41242f18e
|
@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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 */
|
||||
|
|
@ -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>
|
||||
|
|
@ -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!');
|
||||
}));
|
||||
});
|
|
@ -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
|
||||
|
|
@ -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 { }
|
|
@ -0,0 +1,3 @@
|
|||
<p>Latest clearance item: {{clearanceItem}}</p>
|
||||
|
||||
<button (click)="buyIt()"> Buy it with an Output!</button>
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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 */
|
|
@ -0,0 +1,2 @@
|
|||
<span [style.text-decoration]="lineThrough">Item: {{item}}</span>
|
||||
<button (click)="delete()">Delete it with an Output!</button>
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<h2>Child component with @Input()</h2>
|
||||
|
||||
<!-- #docregion property-in-template -->
|
||||
<p>
|
||||
Today's item: {{item}}
|
||||
</p>
|
||||
<!-- #enddocregion property-in-template -->
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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
|
|
@ -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 -->
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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
|
|
@ -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>
|
|
@ -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));
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"description": "Inputs and Outputs",
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
],
|
||||
"file": "src/app/app.component.ts",
|
||||
"tags": ["Inputs and Outputs"]
|
||||
}
|
|
@ -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—`@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—that is, what's
|
||||
in square brackets—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()`—a decorator function marking the property as a way for data to go from the child to the parent
|
||||
* `newItemEvent`—the name of the `@Output()`
|
||||
* `EventEmitter<string>`—the `@Output()`'s type
|
||||
* `new EventEmitter<string>()`—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/>
|
||||
|
||||
|
|
|
@ -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;"><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;">]="</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;">"></app-item-detail></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 |
|
@ -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,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"/></defs></svg>
|
After Width: | Height: | Size: 3.7 KiB |
|
@ -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,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 |
Loading…
Reference in New Issue