docs(template syntax): improvements based on review feedback Feb 16

closes #851
This commit is contained in:
Ward Bell 2016-02-17 10:59:14 -08:00
parent f07ca644a6
commit 157d6d48cc
9 changed files with 171 additions and 122 deletions

View File

@ -28,6 +28,7 @@ class AppComponent {
// String badCurly = 'bad curly'; // XXX: This isn't working.
String badCurly = 'bad'; // XXX: This isn't working.
// List<String> badCurly = ['bad', 'curly']; // XXX: This isn't working.
String classes = 'special';
bool canSave = true;
bool isActive = false;
bool isSpecial = true;
@ -96,6 +97,8 @@ class AppComponent {
alerter('Click me. $evtMsg');
}
void deleteHero([Hero hero]) => alerter('Deleted hero: ${hero?.firstName}');
bool onSave([MouseEvent event = null]) {
var evtMsg =
event != null ? ' Event target is ${event.target.innerHtml}.' : '';
@ -103,8 +106,6 @@ class AppComponent {
return false;
}
void onHeroDeleted([Hero hero]) => alerter('Deleted hero: ${hero?.firstName}');
void onSubmit(NgForm form) {
var evtMsg = form.valid
? ' Form value is ${JSON.encode(form.value)}'

View File

@ -70,7 +70,6 @@
<!-- Public Domain terms of use: http://www.wpclipart.com/terms.html -->
<!-- #docregion img+button -->
<div class="special">Mental Model</div>
<div><b>{{currentHero.fullName}}</b></div>
<img src="assets/images/hero.png">
<button disabled>Save</button>
<!-- #enddocregion img+button -->
@ -78,7 +77,9 @@
<div>
<!-- #docregion hero-detail-1 -->
<!-- Normal HTML -->
<div class="special">Mental Model</div>
<!-- Wow! A new element! -->
<hero-detail></hero-detail>
<!-- #enddocregion hero-detail-1 -->
</div>
@ -103,7 +104,7 @@
<!-- #docregion event-binding-syntax-1 -->
<button (click) = "onSave()">Save</button>
<hero-detail (deleted)="onHeroDeleted()"></hero-detail>
<hero-detail (deleteRequest)="deleteHero()"></hero-detail>
<div (myClick)="clickity=$event">click me</div>
<!-- #enddocregion event-binding-syntax-1 -->
{{clickity}}
@ -170,31 +171,38 @@ button</button>
<img [src]="heroImageUrl">
<!-- #enddocregion property-binding-1 -->
<!-- #docregion property-binding-2 -->
<button [disabled]="isUnchanged">Cancel</button>
<button [disabled]="isUnchanged">Cancel is disabled</button>
<!-- #enddocregion property-binding-2 -->
<!-- #docregion property-binding-3 -->
<div [ngClass]="'special'">NgClass is special</div>
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
<!-- #enddocregion property-binding-3 -->
<!-- #docregion property-binding-4 -->
<hero-detail [hero]="selectedHero"></hero-detail>
<hero-detail [hero]="currentHero"></hero-detail>
<!-- #enddocregion property-binding-4 -->
<!-- #docregion property-binding-5 -->
<img bind-src="heroImageUrl">
<!-- #enddocregion property-binding-5 -->
<!-- #docregion property-binding-6 -->
<!-- Doesn't work! HeroDetailComponent expects a Hero, not a string -->
<!-- <hero-detail hero="…what do we do here??? …"></hero-detail> -->
<!--
BAD!
HeroDetailComponent.hero expects a Hero object,
not the string "currentHero"
-->
<hero-detail hero="currentHero"></hero-detail>
<!-- #enddocregion property-binding-6 -->
<!-- In checked mode, uncommenting the hero-detail above causes this:
EXCEPTION: type 'String' is not a subtype of type 'Hero' of 'value'. -->
<!-- #docregion property-binding-7 -->
<hero-detail prefix="You are my" [hero]="currentHero"></hero-detail>
<!-- #enddocregion property-binding-7 -->
<!-- #docregion property-binding-vs-interpolation -->
<img src="{{heroImageUrl}}">
<img [src]="heroImageUrl">
Interpolated: <img src="{{heroImageUrl}}"><br>
Property bound: <img [src]="heroImageUrl">
<div>The title is {{title}}</div>
<div [textContent]="'The title is ' + title"></div>
<div>The interpolated title is {{title}}</div>
<div [textContent]="'The [textContent] title is '+title"></div>
<!-- #enddocregion property-binding-vs-interpolation -->
<a class="to-toc" href="#toc">top</a>
@ -298,7 +306,6 @@ button</button>
<div>
<!-- #docregion event-binding-3 -->
<!-- `myClick` is an event on the custom `MyClickDirective` -->
<!-- #docregion my-click -->
<div (myClick)="clickMessage=$event">click with myClick</div>
<!-- #enddocregion my-click -->
@ -309,13 +316,12 @@ button</button>
<!-- binding to a nested component -->
<!-- #docregion event-binding-to-component -->
<hero-detail (deleted)="onHeroDeleted($event)" [hero]="currentHero">
</hero-detail>
<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>
<!-- #enddocregion event-binding-to-component -->
<br>
<big-hero-detail
(deleted)="onHeroDeleted($event)"
(deleteRequest)="deleteHero($event)"
[hero]="currentHero">
</big-hero-detail>
@ -689,7 +695,7 @@ bindon-ngModel
<!-- #enddocregion io-1 -->
<!-- #docregion io-2 -->
<hero-detail [hero]="currentHero" (deleted)="onHeroDeleted($event)">
<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
</hero-detail>
<!-- #enddocregion io-2 -->

View File

@ -13,15 +13,20 @@ var nextHeroDetailId = 1;
// #docregion input-output-2
// ...
inputs: const ['hero'],
outputs: const ['deleted'],
outputs: const ['deleteRequest'],
// #enddocregion input-output-2
styles:['button { margin-left: 8px} div {margin: 8px 0} img {height:24px}'],
// #docregion template-1
template: '''
<div>
<span [style.text-decoration]="lineThrough" >{{hero?.fullName}}</span>
<img src="{{heroImageUrl}}" style="height:24px">
<a (click)="onDelete()">delete</a>
</div>
<div>
<img src="{{heroImageUrl}}">
<span [style.text-decoration]="lineThrough">
{{prefix}} {{hero?.fullName}}
</span>
<button (click)="delete()">Delete</button>
</div>
'''
// #enddocregion template-1
// #docregion input-output-2
)
// #enddocregion input-output-2
@ -29,29 +34,26 @@ class HeroDetailComponent {
Hero hero = new Hero('Zzzzzzzz'); // default sleeping hero
String heroImageUrl = 'assets/images/hero.png';
String lineThrough = ''; // PENDING: use null instead?
@Input() String prefix = '';
// #docregion deleted
final EventEmitter deleted = new EventEmitter<Hero>();
// #enddocregion deleted
// #docregion deleteRequest
// This component make a request but it can't actually delete a hero.
final EventEmitter deleteRequest = new EventEmitter<Hero>();
HeroDetailComponent() {
deleted.listen((Hero _) {
lineThrough = (lineThrough == '') ? 'line-through' : '';
});
delete() {
deleteRequest.emit(hero);
// #enddocregion deleteRequest
lineThrough = (lineThrough == '') ? 'line-through' : '';
// #docregion deleteRequest
}
// #docregion deleted
onDelete() {
deleted.emit(hero);
}
// #enddocregion deleted
// #enddocregion deleteRequest
}
@Component(
selector: 'big-hero-detail',
/*
inputs: ['hero'],
outputs: ['deleted'],
outputs: ['deleteRequest'],
*/
template: '''
<div style="border: 1px solid black; padding:3px">
@ -63,18 +65,18 @@ class HeroDetailComponent {
<div>Web: <a href="{{hero?.url}}" target="_blank">{{hero?.url}}</a></div>
<div>Rate/hr: {{hero?.rate | currency:'EUR'}}</div>
<br clear="all">
<button (click)="onDelete()">Delete</button>
<button (click)="delete()">Delete</button>
</div>
''')
class BigHeroDetailComponent extends HeroDetailComponent {
// #docregion input-output-1
@Input() Hero hero;
@Output() final EventEmitter deleted = new EventEmitter<Hero>();
@Output() final EventEmitter deleteRequest = new EventEmitter<Hero>();
// #enddocregion input-output-1
String heroImageUrl = 'assets/images/hero.png';
onDelete() {
deleted.emit(hero);
delete() {
deleteRequest.emit(hero);
}
}

View File

@ -70,7 +70,6 @@
<!-- Public Domain terms of use: http://www.wpclipart.com/terms.html -->
<!-- #docregion img+button -->
<div class="special">Mental Model</div>
<div><b>{{currentHero.fullName}}</b></div>
<img src="images/hero.png">
<button disabled>Save</button>
<!-- #enddocregion img+button -->
@ -78,7 +77,9 @@
<div>
<!-- #docregion hero-detail-1 -->
<!-- Normal HTML -->
<div class="special">Mental Model</div>
<!-- Wow! A new element! -->
<hero-detail></hero-detail>
<!-- #enddocregion hero-detail-1 -->
</div>
@ -103,7 +104,7 @@
<!-- #docregion event-binding-syntax-1 -->
<button (click) = "onSave()">Save</button>
<hero-detail (deleted)="onHeroDeleted()"></hero-detail>
<hero-detail (deleteRequest)="deleteHero()"></hero-detail>
<div (myClick)="clicked=$event">click me</div>
<!-- #enddocregion event-binding-syntax-1 -->
{{clicked}}
@ -170,29 +171,36 @@ button</button>
<img [src]="heroImageUrl">
<!-- #enddocregion property-binding-1 -->
<!-- #docregion property-binding-2 -->
<button [disabled]="isUnchanged">Cancel</button>
<button [disabled]="isUnchanged">Cancel is disabled</button>
<!-- #enddocregion property-binding-2 -->
<!-- #docregion property-binding-3 -->
<div [ngClass]="'special'">NgClass is special</div>
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
<!-- #enddocregion property-binding-3 -->
<!-- #docregion property-binding-4 -->
<hero-detail [hero]="selectedHero"></hero-detail>
<hero-detail [hero]="currentHero"></hero-detail>
<!-- #enddocregion property-binding-4 -->
<!-- #docregion property-binding-5 -->
<img bind-src="heroImageUrl">
<!-- #enddocregion property-binding-5 -->
<!-- #docregion property-binding-6 -->
<!-- Doesn't work! HeroDetailComponent expects a Hero, not a string -->
<hero-detail hero="…what do we do here??? …"></hero-detail>
<!--
BAD!
HeroDetailComponent.hero expects a Hero object,
not the string "currentHero"
-->
<hero-detail hero="currentHero"></hero-detail>
<!-- #enddocregion property-binding-6 -->
<!-- #docregion property-binding-7 -->
<hero-detail prefix="You are my" [hero]="currentHero"></hero-detail>
<!-- #enddocregion property-binding-7 -->
<!-- #docregion property-binding-vs-interpolation -->
<img src="{{heroImageUrl}}">
<img [src]="'' + heroImageUrl">
Interpolated: <img src="{{heroImageUrl}}"><br>
Property bound: <img [src]="heroImageUrl">
<div>The title is {{title}}</div>
<div [textContent]="'The title is '+title"></div>
<div>The interpolated title is {{title}}</div>
<div [textContent]="'The [textContent] title is '+title"></div>
<!-- #enddocregion property-binding-vs-interpolation -->
<a class="to-toc" href="#toc">top</a>
@ -295,7 +303,6 @@ button</button>
<div>
<!-- #docregion event-binding-3 -->
<!-- `myClick` is an event on the custom `MyClickDirective` -->
<!-- #docregion my-click -->
<div (myClick)="clickMessage=$event">click with myClick</div>
<!-- #enddocregion my-click -->
@ -306,13 +313,12 @@ button</button>
<!-- binding to a nested component -->
<!-- #docregion event-binding-to-component -->
<hero-detail (deleted)="onHeroDeleted($event)" [hero]="currentHero">
</hero-detail>
<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>
<!-- #enddocregion event-binding-to-component -->
<br>
<big-hero-detail
(deleted)="onHeroDeleted($event)"
(deleteRequest)="deleteHero($event)"
[hero]="currentHero">
</big-hero-detail>
@ -685,7 +691,7 @@ After setClasses(), the classes are "{{classDiv.className}}"
<!-- #enddocregion io-1 -->
<!-- #docregion io-2 -->
<hero-detail [hero]="currentHero" (deleted)="onHeroDeleted($event)">
<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
</hero-detail>
<!-- #enddocregion io-2 -->

View File

@ -38,6 +38,8 @@ export class AppComponent implements AfterViewInit, OnInit {
actionName = 'Go for it';
alert = alerter;
badCurly = 'bad curly';
classes = 'special';
callFax(value:string) {this.alert(`Faxing ${value} ...`)}
callPhone(value:string) {this.alert(`Calling ${value} ...`)}
canSave = true;
@ -48,6 +50,10 @@ export class AppComponent implements AfterViewInit, OnInit {
currentHero = Hero.MockHeroes[0];
deleteHero(hero:Hero){
this.alert('Deleted hero: '+ (hero && hero.firstName))
}
// DevMode memoization fields
private _priorClasses:{};
private _priorStyles:{};
@ -88,10 +94,6 @@ export class AppComponent implements AfterViewInit, OnInit {
this.alert('Click me.'+evtMsg)
}
onHeroDeleted(hero:Hero){
this.alert('Deleted hero: '+ (hero && hero.firstName))
}
onSave(event:KeyboardEvent){
let evtMsg = event ? ' Event target is '+ (<HTMLElement>event.target).innerText : '';
this.alert('Saved.'+evtMsg)

View File

@ -11,41 +11,42 @@ let nextHeroDetailId = 1;
selector: 'hero-detail',
// #docregion input-output-2
inputs: ['hero'],
outputs: ['deleted'],
outputs: ['deleteRequest'],
// #enddocregion input-output-2
styles:['button { margin-left: 8px} div {margin: 8px 0} img {height:24px}'],
// #docregion template-1
template: `
<div>
<span [style.text-decoration]="lineThrough" >{{hero?.fullName}}</span>
<img src="{{heroImageUrl}}" style="height:24px">
<a (click)="onDelete()">delete</a>
</div>`,
styles:['a { cursor: pointer; cursor: hand; }']
<img src="{{heroImageUrl}}">
<span [style.text-decoration]="lineThrough">
{{prefix}} {{hero?.fullName}}
</span>
<button (click)="delete()">Delete</button>
</div>`
// #enddocregion template-1
// #docregion input-output-2
})
// #enddocregion input-output-2
export class HeroDetailComponent {
constructor() {
// Toggle the line-through style so we see something happen
// even if no one attaches to the `deleted` event.
// Subscribing in ctor rather than the more obvious thing of doing it in
// OnDelete because don't want this mess to distract the chapter reader.
this.deleted.subscribe(() => {
this.lineThrough = this.lineThrough ? '' : 'line-through';
})
// #docregion deleteRequest
// This component make a request but it can't actually delete a hero.
deleteRequest = new EventEmitter<Hero>();
delete() {
this.deleteRequest.emit(this.hero);
// #enddocregion deleteRequest
this.lineThrough = this.lineThrough ? '' : 'line-through';
// #docregion deleteRequest
}
// #docregion deleted
deleted = new EventEmitter<Hero>();
onDelete() {
this.deleted.emit(this.hero);
}
// #enddocregion
// #enddocregion deleteRequest
hero: Hero = new Hero('','Zzzzzzzz'); // default sleeping hero
// heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png';
// Public Domain terms of use: http://www.wpclipart.com/terms.html
heroImageUrl = 'images/hero.png';
lineThrough = '';
lineThrough = '';
@Input() prefix = '';
}
@Component({
@ -60,7 +61,7 @@ export class HeroDetailComponent {
<div>Web: <a href="{{hero?.url}}" target="_blank">{{hero?.url}}</a></div>
<div>Rate/hr: {{hero?.rate | currency:'EUR'}}</div>
<br clear="all">
<button (click)="onDelete()">Delete</button>
<button (click)="delete()">Delete</button>
</div>
`
})
@ -68,10 +69,10 @@ export class BigHeroDetailComponent extends HeroDetailComponent {
// #docregion input-output-1
@Input() hero: Hero;
@Output() deleted = new EventEmitter<Hero>();
@Output() deleteRequest = new EventEmitter<Hero>();
// #enddocregion input-output-1
onDelete() {
this.deleted.emit(this.hero);
delete() {
this.deleteRequest.emit(this.hero);
}
}

View File

@ -163,8 +163,10 @@ table
+includeShared('../../../ts/latest/guide/template-syntax.jade', 'property-binding-11')
+makeExample('template-syntax/dart/lib/app_component.html', 'property-binding-6')(format=".")
+includeShared('../../../ts/latest/guide/template-syntax.jade', 'property-binding-12')
+makeExample('template-syntax/dart/lib/app_component.html', 'property-binding-vs-interpolation')(format=".")
+makeExample('template-syntax/dart/lib/app_component.html', 'property-binding-7')(format=".")
+includeShared('../../../ts/latest/guide/template-syntax.jade', 'property-binding-13')
+makeExample('template-syntax/dart/lib/app_component.html', 'property-binding-vs-interpolation')(format=".")
+includeShared('../../../ts/latest/guide/template-syntax.jade', 'property-binding-14')
+includeShared('../../../ts/latest/guide/template-syntax.jade', 'other-bindings-1')
+includeShared('../../../ts/latest/guide/template-syntax.jade', 'other-bindings-2')
@ -200,7 +202,10 @@ table
+includeShared('../../../ts/latest/guide/template-syntax.jade', 'event-binding-5')
+makeExample('template-syntax/dart/lib/app_component.html', 'without-NgModel')(format=".")
+includeShared('../../../ts/latest/guide/template-syntax.jade', 'event-binding-6')
+makeExample('template-syntax/dart/lib/hero_detail_component.dart', 'deleted', 'lib/hero_detail_component.dart (excerpt)')(format=".")
+makeExample('template-syntax/dart/lib/hero_detail_component.dart',
'template-1', 'HeroDetailComponent.ts (template)')(format=".")
+makeExample('template-syntax/dart/lib/hero_detail_component.dart',
'deleteRequest', 'HeroDetailComponent.ts (delete logic)')(format=".")
+includeShared('../../../ts/latest/guide/template-syntax.jade', 'event-binding-7')
+makeExample('template-syntax/dart/lib/app_component.html', 'event-binding-to-component')(format=".")
+includeShared('../../../ts/latest/guide/template-syntax.jade', 'event-binding-8')

View File

@ -172,8 +172,10 @@ include ../../../../_includes/_util-fns
The view should be stable throughout a single rendering pass.
#### Quick execution
Angular executes template expressions more often than we might think.
Angular executes template expressions more often than we think.
They can be called after every keypress or mouse move.
Expressions should finish quickly or the user experience may drag, especially on slower devices.
Consider caching values computed from other values when the computation is expensive.
#### Simplicity
Although it's possible to write quite complex template expressions, we really shouldn't.
@ -565,40 +567,57 @@ table
:marked
If the name fails to match a property of a known directive or element, Angular reports an “unknown directive” error.
### Template expressions in property binding
### Avoid side effects
As we've already discussed, evaluation of a template expression should have no visible side effects. The expression language itself does its part to keep us safe. We cant assign a value to anything in a property binding expression nor use the increment and decorator operators.
Of course, our expression might invoke a property or method that has side effects. Angular has no way of knowing that or stopping us.
The expression could call something like `getFoo()`. Only we know what `getFoo()` does.
If `getFoo()` changes something and we happen to be binding to that something, we risk an unpleasant experience. Angular may or may not display the changed value. Angular may detect the change and throw a warning error. Our general advice: stick to data properties and to methods that return values and do no more.
### Return the proper type
The template expression should evaluate to the type of value expected by the target property.
Return a string if the target property expects a string.
Return a number if the target property expects a number.
Return an object if the target property expects an object.
The template expression should evaluate to a value of the type expected by the target property. Most native element properties expect a string. For example, the image `src` should be set to a string that's an URL for the resource providing the image. On the other hand, the `disabled` property of a button expects a Boolean value, so an expression that's assigned to `disabled` should evaluate to `true` or `false`.
The `hero` property of the `HeroDetail` component expects a `Hero` object, which is exactly what were sending in the property binding:
The `hero` property of the `HeroDetail` component expects a `Hero` object, which is exactly what were sending in the property binding:
// #enddocregion property-binding-10
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-4')(format=".")
// #docregion property-binding-11
:marked
This is good news.
If `hero` were an attribute, we could not set it to a `Hero` object.
### Remember the brackets
The brackets tell Angular to evaluate the template expression.
If we forget the brackets, Angular treats the string as a constant and *initializes the target property* with that string.
It does *not* evaluate the string!
Don't make the following mistake:
// #enddocregion property-binding-11
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-6')(format=".")
// #docregion property-binding-12
a(id="one-time-initialization")
:marked
We can't set an attribute to an object. We can only set it to a string.
Internally, the attribute may be able to convert that string to an object before setting the like-named element property.
Thats good to know but not helpful to us when we're trying to pass a significant data object
from one component element to another.
The power of property binding is its ability to bypass the attribute and
set the element property directly with a value of the appropriate type.
### One-time string initialization
We *should omit the brackets* when
* the target property accepts a string value
* the string is a fixed value that we can bake into the template
* this initial value never changes
We routinely initialize attributes this way in standard HTML and it works
just as well for directive and component property initialization.
In the following example, we initialize the `prefix` property of the `HeroDetailComponent` to a fixed string,
not a template expression. Angular sets it and forgets it.
// #enddocregion property-binding-12
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-7')(format=".")
// #docregion property-binding-13
:marked
The `[hero]` binding, on the other hand, remains a live binding to the component's `currentHero` property.
### Property binding or interpolation?
We often have a choice between interpolation and property binding. The following binding pairs do the same thing:
// #enddocregion property-binding-12
// #enddocregion property-binding-13
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-vs-interpolation')(format=".")
// #docregion property-binding-13
// #docregion property-binding-14
:marked
Interpolation is a convenient alternative for property binding in many cases.
In fact, Angular translates those interpolations into the corresponding property bindings
@ -608,7 +627,7 @@ table
We lean toward readability, which tends to favor interpolation.
We suggest establishing coding style rules for the organization and choosing the form that
both conforms to the rules and feels most natural for the task at hand.
// #enddocregion property-binding-13
// #enddocregion property-binding-14
// #docregion other-bindings-1
.l-main-section
@ -754,7 +773,7 @@ code-example(format="", language="html").
// #docregion event-binding-2
:marked
### Target event
A **name between enclosing parentheses** &mdash; for example, `(keyup)` &mdash;
A **name between enclosing parentheses** &mdash; for example, `(click)` &mdash;
identifies the target event. In the following example, the target is the buttons click event.
// #enddocregion event-binding-2
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-1')(format=".")
@ -812,29 +831,36 @@ code-example(format="", language="html").
The directive calls `EventEmitter.emit(payload)` to fire an event, passing in a message payload that can be anything.
Parent directives listen for the event by binding to this property and accessing the payload through the `$event` object.
Consider a `HeroDetailComponent` that produces `deleted` events with an `EventEmitter`.
Consider a `HeroDetailComponent` that presents hero information and responds to user actions.
Although the `HeroDetailComponent` has a delete button it doesn't know how to delete the hero itself.
The best it can do is raise an event reporting the user's delete request.
Here are the pertinent excerpts from that `HeroDetailComponent`:
// #enddocregion event-binding-6
+makeExample('template-syntax/ts/app/hero-detail.component.ts',
'deleted', 'HeroDetailComponent.ts (excerpt)')(format=".")
'template-1', 'HeroDetailComponent.ts (template)')(format=".")
+makeExample('template-syntax/ts/app/hero-detail.component.ts',
'deleteRequest', 'HeroDetailComponent.ts (delete logic)')(format=".")
// #docregion event-binding-7
:marked
When the user clicks a button, the component invokes the `onDelete()` method, which emits a `Hero` object.
The `HeroDetailComponent` doesn't know how to delete a hero. Its job is to present information and
respond to user actions.
Now imagine a parent component that binds to the `HeroDetailComponent`'s `deleted` event.
The component defines a `deleteRequest` property that returns an `EventEmitter`.
When the user clicks *delete*, the component invokes the `delete()` method
which tells the `EventEmitter` to emit a `Hero` object.
Now imagine a hosting parent component that binds to the `HeroDetailComponent`'s `deleteRequest` event.
// #enddocregion event-binding-7
+makeExample('template-syntax/ts/app/app.component.html',
'event-binding-to-component')(format=".")
// #docregion event-binding-8
:marked
When the `deleted` event fires, Angular calls the parent component's `onHeroDeleted` method,
When the `deleteRequest` event fires, Angular calls the parent component's `deleteHero` method,
passing the *hero-to-delete* (emitted by `HeroDetail`) in the `$event` variable.
The `onHeroDeleted` method has a side effect: It deletes a hero.
Side effects are not just OK, they are expected.
### Template statements have side effects
The `deleteHero` method has a side effect: It deletes a hero.
Template statement side effects are not just OK, they are expected.
Deleting the hero updates the model, perhaps triggering other changes
including queries and saves to a remote server.
@ -1452,9 +1478,9 @@ figure.image-display
+makeExample('template-syntax/ts/app/app.component.html', 'io-2')(format=".")
// #docregion inputs-outputs-3
:marked
Both `HeroDetailComponent.hero` and `HeroDetailComponent.deleted` are on the **left side** of binding declarations.
Both `HeroDetailComponent.hero` and `HeroDetailComponent.deleteRequest` are on the **left side** of binding declarations.
`HeroDetailComponent.hero` is inside brackets; it is the target of a property binding.
`HeroDetailComponent.deleted` is inside parentheses; it is the target of an event binding.
`HeroDetailComponent.deleteRequest` is inside parentheses; it is the target of an event binding.
### Declaring input and output properties
Target properties must be explicitly marked as inputs or outputs.
@ -1488,7 +1514,7 @@ figure.image-display
`HeroDetailComponent.hero` is an **input** property from the perspective of `HeroDetailComponent`
because data flows *into* that property from a template binding expression.
`HeroDetailComponent.deleted` is an **output** property from the perspective of `HeroDetailComponent`
`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.
// #enddocregion inputs-outputs-4

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB