docs: rewrite event binding section and add example (#26162)
PR Close #26162
This commit is contained in:
parent
197676a6dd
commit
75074d009f
|
@ -0,0 +1,71 @@
|
||||||
|
'use strict'; // necessary for es6 output in node
|
||||||
|
|
||||||
|
import { browser, element, by, protractor } from 'protractor';
|
||||||
|
|
||||||
|
describe('Event binding example', function () {
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
browser.get('');
|
||||||
|
});
|
||||||
|
|
||||||
|
let saveButton = element.all(by.css('button')).get(0);
|
||||||
|
let onSaveButton = element.all(by.css('button')).get(1);
|
||||||
|
let myClick = element.all(by.css('button')).get(2);
|
||||||
|
let deleteButton = element.all(by.css('button')).get(3);
|
||||||
|
let saveNoProp = element.all(by.css('button')).get(4);
|
||||||
|
let saveProp = element.all(by.css('button')).get(5);
|
||||||
|
|
||||||
|
|
||||||
|
it('should display Event Binding with Angular', function () {
|
||||||
|
expect(element(by.css('h1')).getText()).toEqual('Event Binding');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display 6 buttons', function() {
|
||||||
|
expect(saveButton.getText()).toBe('Save');
|
||||||
|
expect(onSaveButton.getText()).toBe('on-click Save');
|
||||||
|
expect(myClick.getText()).toBe('click with myClick');
|
||||||
|
expect(deleteButton.getText()).toBe('Delete');
|
||||||
|
expect(saveNoProp.getText()).toBe('Save, no propagation');
|
||||||
|
expect(saveProp.getText()).toBe('Save with propagation');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support user input', function () {
|
||||||
|
let input = element(by.css('input'));
|
||||||
|
let bindingResult = element.all(by.css('h4')).get(1);
|
||||||
|
expect(bindingResult.getText()).toEqual('Result: teapot');
|
||||||
|
input.sendKeys('abc');
|
||||||
|
expect(bindingResult.getText()).toEqual('Result: teapotabc');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide the item img', async () => {
|
||||||
|
let deleteButton = element.all(by.css('button')).get(3);
|
||||||
|
await deleteButton.click();
|
||||||
|
browser.switchTo().alert().accept();
|
||||||
|
expect(element.all(by.css('img')).get(0).getCssValue('display')).toEqual('none');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show two alerts', async () => {
|
||||||
|
let parentDiv = element.all(by.css('.parent-div'));
|
||||||
|
let childDiv = element.all(by.css('div > div')).get(1);
|
||||||
|
await parentDiv.click();
|
||||||
|
browser.switchTo().alert().accept();
|
||||||
|
expect(childDiv.getText()).toEqual('Click me too! (child)');
|
||||||
|
await childDiv.click();
|
||||||
|
expect(browser.switchTo().alert().getText()).toEqual('Click me. Event target class is child-div');
|
||||||
|
browser.switchTo().alert().accept();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show 1 alert from Save, no prop, button', async () => {
|
||||||
|
await saveNoProp.click();
|
||||||
|
expect(browser.switchTo().alert().getText()).toEqual('Saved. Event target is Save, no propagation');
|
||||||
|
browser.switchTo().alert().accept();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show 2 alerts from Save w/prop button', async () => {
|
||||||
|
await saveProp.click();
|
||||||
|
expect(browser.switchTo().alert().getText()).toEqual('Saved.');
|
||||||
|
browser.switchTo().alert().accept();
|
||||||
|
expect(browser.switchTo().alert().getText()).toEqual('Saved.');
|
||||||
|
browser.switchTo().alert().accept();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,25 @@
|
||||||
|
.group {
|
||||||
|
background-color: #dae8f9;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parent-div {
|
||||||
|
background-color: #bdd1f7;
|
||||||
|
border: solid 1px rgb(25, 118, 210);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parent-div:hover {
|
||||||
|
background-color: #8fb4f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.child-div {
|
||||||
|
margin-top: 1rem;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.child-div:hover {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
<h1 id="event-binding">Event Binding</h1>
|
||||||
|
|
||||||
|
<div class="group">
|
||||||
|
<h3>Target event</h3>
|
||||||
|
<!-- #docregion event-binding-1 -->
|
||||||
|
<button (click)="onSave($event)">Save</button>
|
||||||
|
<!-- #enddocregion event-binding-1 -->
|
||||||
|
|
||||||
|
<!-- #docregion event-binding-2 -->
|
||||||
|
<button on-click="onSave($event)">on-click Save</button>
|
||||||
|
<!-- #enddocregion event-binding-2 -->
|
||||||
|
|
||||||
|
<!-- #docregion custom-directive -->
|
||||||
|
<h4>myClick is an event on the custom ClickDirective:</h4>
|
||||||
|
<button (myClick)="clickMessage=$event" clickable>click with myClick</button>
|
||||||
|
{{clickMessage}}
|
||||||
|
<!-- #enddocregion custom-directive -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group">
|
||||||
|
<h3>$event and event handling statements</h3>
|
||||||
|
<h4>Result: {{currentItem.name}}</h4>
|
||||||
|
|
||||||
|
<!-- #docregion event-binding-3-->
|
||||||
|
<input [value]="currentItem.name"
|
||||||
|
(input)="currentItem.name=$event.target.value" >
|
||||||
|
without NgModel
|
||||||
|
<!-- #enddocregion event-binding-3-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group">
|
||||||
|
<h3>Binding to a nested component</h3>
|
||||||
|
<h4>Custom events with EventEmitter</h4>
|
||||||
|
<!-- #docregion event-binding-to-component -->
|
||||||
|
<app-item-detail (deleteRequest)="deleteItem($event)" [item]="currentItem"></app-item-detail>
|
||||||
|
<!-- #enddocregion event-binding-to-component -->
|
||||||
|
|
||||||
|
|
||||||
|
<h4>Click to see event target class:</h4>
|
||||||
|
<div class="parent-div" (click)="onClickMe($event)" clickable>Click me (parent)
|
||||||
|
<div class="child-div">Click me too! (child) </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Saves only once:</h3>
|
||||||
|
<div (click)="onSave()" clickable>
|
||||||
|
<button (click)="onSave($event)">Save, no propagation</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Saves twice:</h3>
|
||||||
|
<div (click)="onSave()" clickable>
|
||||||
|
<button (click)="onSave()">Save with propagation</button>
|
||||||
|
</div>
|
|
@ -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 'Featured product:'`, async(() => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
const app = fixture.debugElement.componentInstance;
|
||||||
|
expect(app.title).toEqual('Featured product:');
|
||||||
|
}));
|
||||||
|
it('should render title in a p tag', async(() => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.debugElement.nativeElement;
|
||||||
|
expect(compiled.querySelector('p').textContent).toContain('Featured product:');
|
||||||
|
}));
|
||||||
|
});
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Item } from './item';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.css']
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
|
||||||
|
currentItem = { name: 'teapot'} ;
|
||||||
|
clickMessage = '';
|
||||||
|
|
||||||
|
onSave(event: KeyboardEvent) {
|
||||||
|
const evtMsg = event ? ' Event target is ' + (<HTMLElement>event.target).textContent : '';
|
||||||
|
alert('Saved.' + evtMsg);
|
||||||
|
if (event) { event.stopPropagation(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteItem(item: Item) {
|
||||||
|
alert(`Delete the ${item}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickMe(event: KeyboardEvent) {
|
||||||
|
const evtMsg = event ? ' Event target class is ' + (<HTMLElement>event.target).className : '';
|
||||||
|
alert('Click me.' + evtMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
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 { ClickDirective } from './click.directive';
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
ItemDetailComponent,
|
||||||
|
ClickDirective
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserModule
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
|
@ -0,0 +1,18 @@
|
||||||
|
/* tslint:disable use-output-property-decorator directive-class-suffix */
|
||||||
|
import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({selector: '[myClick]'})
|
||||||
|
export class ClickDirective {
|
||||||
|
@Output('myClick') clicks = new EventEmitter<string>(); // @Output(alias) propertyName = ...
|
||||||
|
|
||||||
|
toggle = false;
|
||||||
|
|
||||||
|
constructor(el: ElementRef) {
|
||||||
|
el.nativeElement
|
||||||
|
.addEventListener('click', (event: Event) => {
|
||||||
|
this.toggle = !this.toggle;
|
||||||
|
this.clicks.emit(this.toggle ? 'Click!' : '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
.detail {
|
||||||
|
border: 1px solid rgb(25, 118, 210);
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100px;
|
||||||
|
display: block;
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<div class="detail">
|
||||||
|
<p>This is the ItemDetailComponent</p>
|
||||||
|
<!-- #docregion line-through -->
|
||||||
|
<img src="{{itemImageUrl}}" [style.display]="displayNone">
|
||||||
|
<span [style.text-decoration]="lineThrough">{{ item.name }}
|
||||||
|
</span>
|
||||||
|
<button (click)="delete()">Delete</button>
|
||||||
|
<!-- #enddocregion line-through -->
|
||||||
|
</div>
|
|
@ -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,30 @@
|
||||||
|
/* tslint:disable use-input-property-decorator use-output-property-decorator */
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
|
||||||
|
import { Item } from '../item';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-item-detail',
|
||||||
|
styleUrls: ['./item-detail.component.css'],
|
||||||
|
templateUrl: './item-detail.component.html'
|
||||||
|
})
|
||||||
|
export class ItemDetailComponent {
|
||||||
|
|
||||||
|
@Input() item;
|
||||||
|
itemImageUrl = 'assets/teapot.svg';
|
||||||
|
lineThrough = '';
|
||||||
|
displayNone = '';
|
||||||
|
@Input() prefix = '';
|
||||||
|
|
||||||
|
// #docregion deleteRequest
|
||||||
|
// This component makes a request but it can't actually delete a hero.
|
||||||
|
@Output() deleteRequest = new EventEmitter<Item>();
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
this.deleteRequest.emit(this.item.name);
|
||||||
|
this.displayNone = this.displayNone ? '' : 'none';
|
||||||
|
this.lineThrough = this.lineThrough ? '' : 'line-through';
|
||||||
|
}
|
||||||
|
// #enddocregion deleteRequest
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export class Item {
|
||||||
|
name: '';
|
||||||
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 23 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>EventBinding</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": "Event Binding",
|
||||||
|
"files": [
|
||||||
|
"!**/*.d.ts",
|
||||||
|
"!**/*.js",
|
||||||
|
"!**/*.[1,2].*"
|
||||||
|
],
|
||||||
|
"file": "src/app/app.component.ts",
|
||||||
|
"tags": ["Event Binding"]
|
||||||
|
}
|
|
@ -797,7 +797,6 @@ content harmlessly.
|
||||||
|
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
{@a other-bindings}
|
{@a other-bindings}
|
||||||
|
|
||||||
## Attribute, class, and style bindings
|
## Attribute, class, and style bindings
|
||||||
|
@ -944,56 +943,44 @@ Note that a _style property_ name can be written in either
|
||||||
|
|
||||||
{@a event-binding}
|
{@a event-binding}
|
||||||
|
|
||||||
## Event binding ( <span class="syntax">(event)</span> )
|
## Event binding `(event)`
|
||||||
|
|
||||||
The bindings directives you've met so far flow data in one direction: **from a component to an element**.
|
Event binding allows you to listen for certain events such as
|
||||||
|
keystrokes, mouse movements, clicks, and touches. For an example
|
||||||
|
demonstrating all of the points in this section, see the <live-example name="event-binding">event binding example</live-example>.
|
||||||
|
|
||||||
Users don't just stare at the screen. They enter text into input boxes. They pick items from lists.
|
Angular event binding syntax consists of a **target event** name
|
||||||
They click buttons. Such user actions may result in a flow of data in the opposite direction:
|
|
||||||
**from an element to a component**.
|
|
||||||
|
|
||||||
The only way to know about a user action is to listen for certain events such as
|
|
||||||
keystrokes, mouse movements, clicks, and touches.
|
|
||||||
You declare your interest in user actions through Angular event binding.
|
|
||||||
|
|
||||||
Event binding syntax consists of a **target event** name
|
|
||||||
within parentheses on the left of an equal sign, and a quoted
|
within parentheses on the left of an equal sign, and a quoted
|
||||||
[template statement](guide/template-syntax#template-statements) on the right.
|
template statement on the right.
|
||||||
The following event binding listens for the button's click events, calling
|
The following event binding listens for the button's click events, calling
|
||||||
the component's `onSave()` method whenever a click occurs:
|
the component's `onSave()` method whenever a click occurs:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="event-binding-1" header="src/app/app.component.html" linenums="false">
|
<figure>
|
||||||
</code-example>
|
<img src='generated/images/guide/event-binding/syntax-diagram.svg' alt="Syntax diagram">
|
||||||
|
</figure>
|
||||||
|
|
||||||
### Target event
|
### Target event
|
||||||
|
|
||||||
A **name between parentheses** — for example, `(click)` —
|
As above, the target is the button's click event.
|
||||||
identifies the target event. In the following example, the target is the button's click event.
|
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="event-binding-1" header="src/app/app.component.html" linenums="false">
|
<code-example path="event-binding/src/app/app.component.html" region="event-binding-1" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Some people prefer the `on-` prefix alternative, known as the **canonical form**:
|
Alternatively, use the `on-` prefix, known as the canonical form:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="event-binding-2" header="src/app/app.component.html" linenums="false">
|
<code-example path="event-binding/src/app/app.component.html" region="event-binding-2" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
Element events may be the more common targets, but Angular looks first to see if the name matches an event property
|
Element events may be the more common targets, but Angular looks first to see if the name matches an event property
|
||||||
of a known directive, as it does in the following example:
|
of a known directive, as it does in the following example:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="event-binding-3" header="src/app/app.component.html" linenums="false">
|
<code-example path="event-binding/src/app/app.component.html" region="custom-directive" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
<div class="alert is-helpful">
|
|
||||||
|
|
||||||
The `myClick` directive is further described in the section
|
|
||||||
on [aliasing input/output properties](guide/template-syntax#aliasing-io).
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
If the name fails to match an element event or an output property of a known directive,
|
If the name fails to match an element event or an output property of a known directive,
|
||||||
Angular reports an “unknown directive” error.
|
Angular reports an “unknown directive” error.
|
||||||
|
|
||||||
|
|
||||||
### *$event* and event handling statements
|
### *$event* and event handling statements
|
||||||
|
|
||||||
In an event binding, Angular sets up an event handler for the target event.
|
In an event binding, Angular sets up an event handler for the target event.
|
||||||
|
@ -1003,72 +990,73 @@ The template statement typically involves a receiver, which performs an action
|
||||||
in response to the event, such as storing a value from the HTML control
|
in response to the event, such as storing a value from the HTML control
|
||||||
into a model.
|
into a model.
|
||||||
|
|
||||||
The binding conveys information about the event, including data values, through
|
The binding conveys information about the event. This information can include data values such as an event object, string, or number named `$event`.
|
||||||
an **event object named `$event`**.
|
|
||||||
|
|
||||||
The shape of the event object is determined by the target event.
|
The target event determines the shape of the `$event` object.
|
||||||
If the target event is a native DOM element event, then `$event` is a
|
If the target event is a native DOM element event, then `$event` is a
|
||||||
[DOM event object](https://developer.mozilla.org/en-US/docs/Web/Events),
|
[DOM event object](https://developer.mozilla.org/en-US/docs/Web/Events),
|
||||||
with properties such as `target` and `target.value`.
|
with properties such as `target` and `target.value`.
|
||||||
|
|
||||||
Consider this example:
|
Consider this example:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" region="without-NgModel" header="src/app/app.component.html" linenums="false">
|
<code-example path="event-binding/src/app/app.component.html" region="event-binding-3" header="src/app/app.component.html" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
This code sets the input box `value` property by binding to the `name` property.
|
This code sets the `<input>` `value` property by binding to the `name` property.
|
||||||
To listen for changes to the value, the code binds to the input box's `input` event.
|
To listen for changes to the value, the code binds to the `input`
|
||||||
|
event of the `<input>` element.
|
||||||
When the user makes changes, the `input` event is raised, and the binding executes
|
When the user makes changes, the `input` event is raised, and the binding executes
|
||||||
the statement within a context that includes the DOM event object, `$event`.
|
the statement within a context that includes the DOM event object, `$event`.
|
||||||
|
|
||||||
To update the `name` property, the changed text is retrieved by following the path `$event.target.value`.
|
To update the `name` property, the changed text is retrieved by following the path `$event.target.value`.
|
||||||
|
|
||||||
If the event belongs to a directive (recall that components are directives),
|
If the event belongs to a directive—recall that components
|
||||||
`$event` has whatever shape the directive decides to produce.
|
are directives—`$event` has whatever shape the directive produces.
|
||||||
|
|
||||||
{@a eventemitter}
|
|
||||||
|
|
||||||
{@a custom-event}
|
### Custom events with `EventEmitter`
|
||||||
|
|
||||||
### Custom events with <span class="syntax">EventEmitter</span>
|
|
||||||
|
|
||||||
Directives typically raise custom events with an Angular [EventEmitter](api/core/EventEmitter).
|
Directives typically raise custom events with an Angular [EventEmitter](api/core/EventEmitter).
|
||||||
The directive creates an `EventEmitter` and exposes it as a property.
|
The directive creates an `EventEmitter` and exposes it as a property.
|
||||||
The directive calls `EventEmitter.emit(payload)` to fire an event, passing in a message payload, which can be anything.
|
The directive calls `EventEmitter.emit(payload)` to fire an event, passing in a message payload, which can be anything.
|
||||||
Parent directives listen for the event by binding to this property and accessing the payload through the `$event` object.
|
Parent directives listen for the event by binding to this property and accessing the payload through the `$event` object.
|
||||||
|
|
||||||
Consider a `HeroDetailComponent` that presents hero information and responds to user actions.
|
Consider an `ItemDetailComponent` that presents item information and responds to user actions.
|
||||||
Although the `HeroDetailComponent` has a delete button it doesn't know how to delete the hero itself.
|
Although the `ItemDetailComponent` has a delete button, it doesn't know how to delete the hero. It can only raise an event reporting the user's delete request.
|
||||||
The best it can do is raise an event reporting the user's delete request.
|
|
||||||
|
|
||||||
Here are the pertinent excerpts from that `HeroDetailComponent`:
|
Here are the pertinent excerpts from that `ItemDetailComponent`:
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/hero-detail.component.ts" linenums="false" header="src/app/hero-detail.component.ts (template)" region="template-1">
|
|
||||||
|
<code-example path="event-binding/src/app/item-detail/item-detail.component.html" linenums="false" header="src/app/item-detail/item-detail.component.ts (template)" region="line-through">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/hero-detail.component.ts" linenums="false" header="src/app/hero-detail.component.ts (deleteRequest)" region="deleteRequest">
|
<code-example path="event-binding/src/app/item-detail/item-detail.component.ts" linenums="false" header="src/app/item-detail/item-detail.component.ts (deleteRequest)" region="deleteRequest">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
The component defines a `deleteRequest` property that returns an `EventEmitter`.
|
The component defines a `deleteRequest` property that returns an `EventEmitter`.
|
||||||
When the user clicks *delete*, the component invokes the `delete()` method,
|
When the user clicks *delete*, the component invokes the `delete()` method,
|
||||||
telling the `EventEmitter` to emit a `Hero` object.
|
telling the `EventEmitter` to emit an `Item` object.
|
||||||
|
|
||||||
Now imagine a hosting parent component that binds to the `HeroDetailComponent`'s `deleteRequest` event.
|
Now imagine a hosting parent component that binds to the `deleteRequest` event
|
||||||
|
of the `ItemDetailComponent`.
|
||||||
|
|
||||||
<code-example path="template-syntax/src/app/app.component.html" linenums="false" header="src/app/app.component.html (event-binding-to-component)" region="event-binding-to-component">
|
<code-example path="event-binding/src/app/app.component.html" linenums="false" header="src/app/app.component.html (event-binding-to-component)" region="event-binding-to-component">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
When the `deleteRequest` event fires, Angular calls the parent component's `deleteHero` method,
|
When the `deleteRequest` event fires, Angular calls the parent component's
|
||||||
passing the *hero-to-delete* (emitted by `HeroDetail`) in the `$event` variable.
|
`deleteItem()` method, passing the *item-to-delete* (emitted by `ItemDetail`)
|
||||||
|
in the `$event` variable.
|
||||||
|
|
||||||
### Template statements have side effects
|
### Template statements have side effects
|
||||||
|
|
||||||
The `deleteHero` method has a side effect: it deletes a hero.
|
Though [template expressions](guide/template-syntax#template-expressions) shouldn't have [side effects](guide/template-syntax#avoid-side-effects), template
|
||||||
Template statement side effects are not just OK, but expected.
|
statements usually do. The `deleteItem()` method does have
|
||||||
|
a side effect: it deletes an item.
|
||||||
|
|
||||||
Deleting the hero updates the model, perhaps triggering other changes
|
Deleting an item updates the model, and depending on your code, triggers
|
||||||
including queries and saves to a remote server.
|
other changes including queries and saving to a remote server.
|
||||||
These changes percolate through the system and are ultimately displayed in this and other views.
|
These changes propagate through the system and ultimately display in this and other views.
|
||||||
|
|
||||||
|
|
||||||
<hr/>
|
<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 600 125" 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 transform="matrix(0.469484,0,0,0.469925,-158.685,-184.211)"><text x="374px" y="472px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:64px;"><button</text><text x="607.094px" y="472px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:64px;fill:#df002d;">(click)</text><text x="774.156px" y="472px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:64px;">="</text><text x="834.25px" y="472px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:64px;fill:#0022be;">onSave()</text><text x="1093.94px" y="472px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:64px;">">Save</button></text></g><path d="M142.827,95.824l-6.663,-3.032l17.828,-39.26l-6.187,-2.815l13.842,-5.191l5.195,13.852l-6.187,-2.815l-17.828,39.261Z" style="fill:#df002d;"/><path d="M337.08,93.548l-6.915,2.399l-13.342,-38.531l-6.422,2.228l6.458,-13.308l13.3,6.453l-6.421,2.228l13.342,38.531Z" style="fill:#3345cc;"/><g><rect x="23.005" y="76.128" width="197.653" height="31.485" style="fill:#fff;stroke:#000;stroke-width:0.7px;"/><text x="31.989px" y="98.214px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:22.556px;fill:#df002d;">target even<tspan x="144.743px " y="98.214px ">t</tspan> name</text></g><g><rect x="275.117" y="76.128" width="228.169" height="31.955" style="fill:#fff;stroke:#000;stroke-width:0.7px;"/><text x="293.078px" y="98.214px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:22.556px;fill:#3345cc;">template st<tspan x="403.3px " y="98.214px ">a</tspan>tement</text></g></svg>
|
After Width: | Height: | Size: 2.0 KiB |
Loading…
Reference in New Issue