docs(template syntax): improvements based on review feedback Feb 16
closes #851
This commit is contained in:
parent
f07ca644a6
commit
157d6d48cc
|
@ -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)}'
|
||||
|
|
|
@ -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 -->
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 -->
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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 can’t 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 we’re sending in the property binding:
|
||||
The `hero` property of the `HeroDetail` component expects a `Hero` object, which is exactly what we’re 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.
|
||||
That’s 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** — for example, `(keyup)` —
|
||||
A **name between enclosing parentheses** — for example, `(click)` —
|
||||
identifies the target event. In the following example, the target is the button’s 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 |
Loading…
Reference in New Issue