diff --git a/public/docs/_examples/template-syntax/dart/lib/app_component.dart b/public/docs/_examples/template-syntax/dart/lib/app_component.dart index aa0a29b444..2a4f4ba37e 100644 --- a/public/docs/_examples/template-syntax/dart/lib/app_component.dart +++ b/public/docs/_examples/template-syntax/dart/lib/app_component.dart @@ -28,6 +28,7 @@ class AppComponent { // String badCurly = 'bad curly'; // XXX: This isn't working. String badCurly = 'bad'; // XXX: This isn't working. // List 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)}' diff --git a/public/docs/_examples/template-syntax/dart/lib/app_component.html b/public/docs/_examples/template-syntax/dart/lib/app_component.html index e2dff3d119..41c2f8c970 100644 --- a/public/docs/_examples/template-syntax/dart/lib/app_component.html +++ b/public/docs/_examples/template-syntax/dart/lib/app_component.html @@ -70,7 +70,6 @@
Mental Model
-
{{currentHero.fullName}}
@@ -78,7 +77,9 @@
+
Mental Model
+
@@ -103,7 +104,7 @@ - +
click me
{{clickity}} @@ -170,31 +171,38 @@ button - + -
NgClass is special
+
[ngClass] binding to the classes property
- + - - + + + + + - - +Interpolated:
+Property bound: -
The title is {{title}}
-
+
The interpolated title is {{title}}
+
top @@ -298,7 +306,6 @@ button
-
click with myClick
@@ -309,13 +316,12 @@ button - - +
@@ -689,7 +695,7 @@ bindon-ngModel - + diff --git a/public/docs/_examples/template-syntax/dart/lib/hero_detail_component.dart b/public/docs/_examples/template-syntax/dart/lib/hero_detail_component.dart index fe82d76fe1..1b9a85e5a9 100644 --- a/public/docs/_examples/template-syntax/dart/lib/hero_detail_component.dart +++ b/public/docs/_examples/template-syntax/dart/lib/hero_detail_component.dart @@ -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: ''' -
- {{hero?.fullName}} - - delete -
+
+ + + {{prefix}} {{hero?.fullName}} + + +
''' +// #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(); - // #enddocregion deleted + // #docregion deleteRequest + // This component make a request but it can't actually delete a hero. + final EventEmitter deleteRequest = new EventEmitter(); - 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: '''
@@ -63,18 +65,18 @@ class HeroDetailComponent {
Rate/hr: {{hero?.rate | currency:'EUR'}}

- +
''') class BigHeroDetailComponent extends HeroDetailComponent { // #docregion input-output-1 @Input() Hero hero; - @Output() final EventEmitter deleted = new EventEmitter(); + @Output() final EventEmitter deleteRequest = new EventEmitter(); // #enddocregion input-output-1 String heroImageUrl = 'assets/images/hero.png'; - onDelete() { - deleted.emit(hero); + delete() { + deleteRequest.emit(hero); } } diff --git a/public/docs/_examples/template-syntax/ts/app/app.component.html b/public/docs/_examples/template-syntax/ts/app/app.component.html index a7e58275e0..4de9728c11 100644 --- a/public/docs/_examples/template-syntax/ts/app/app.component.html +++ b/public/docs/_examples/template-syntax/ts/app/app.component.html @@ -70,7 +70,6 @@
Mental Model
-
{{currentHero.fullName}}
@@ -78,7 +77,9 @@
+
Mental Model
+
@@ -103,7 +104,7 @@ - +
click me
{{clicked}} @@ -170,29 +171,36 @@ button - + -
NgClass is special
+
[ngClass] binding to the classes property
- + - - + + + + + - - +Interpolated:
+Property bound: -
The title is {{title}}
-
+
The interpolated title is {{title}}
+
top @@ -295,7 +303,6 @@ button
-
click with myClick
@@ -306,13 +313,12 @@ button - - +
@@ -685,7 +691,7 @@ After setClasses(), the classes are "{{classDiv.className}}" - + diff --git a/public/docs/_examples/template-syntax/ts/app/app.component.ts b/public/docs/_examples/template-syntax/ts/app/app.component.ts index cb0440f6aa..5fee042818 100644 --- a/public/docs/_examples/template-syntax/ts/app/app.component.ts +++ b/public/docs/_examples/template-syntax/ts/app/app.component.ts @@ -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 '+ (event.target).innerText : ''; this.alert('Saved.'+evtMsg) diff --git a/public/docs/_examples/template-syntax/ts/app/hero-detail.component.ts b/public/docs/_examples/template-syntax/ts/app/hero-detail.component.ts index 6ed0714b9b..f27f68719d 100644 --- a/public/docs/_examples/template-syntax/ts/app/hero-detail.component.ts +++ b/public/docs/_examples/template-syntax/ts/app/hero-detail.component.ts @@ -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: `
- {{hero?.fullName}} - - delete -
`, - styles:['a { cursor: pointer; cursor: hand; }'] + + + {{prefix}} {{hero?.fullName}} + + +
` + // #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(); + + delete() { + this.deleteRequest.emit(this.hero); + // #enddocregion deleteRequest + this.lineThrough = this.lineThrough ? '' : 'line-through'; + // #docregion deleteRequest } - -// #docregion deleted - deleted = new EventEmitter(); - 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 {
Rate/hr: {{hero?.rate | currency:'EUR'}}

- +
` }) @@ -68,10 +69,10 @@ export class BigHeroDetailComponent extends HeroDetailComponent { // #docregion input-output-1 @Input() hero: Hero; - @Output() deleted = new EventEmitter(); + @Output() deleteRequest = new EventEmitter(); // #enddocregion input-output-1 - onDelete() { - this.deleted.emit(this.hero); + delete() { + this.deleteRequest.emit(this.hero); } } diff --git a/public/docs/dart/latest/guide/template-syntax-NEW.jade b/public/docs/dart/latest/guide/template-syntax-NEW.jade index f7b003d616..d2174c82f1 100644 --- a/public/docs/dart/latest/guide/template-syntax-NEW.jade +++ b/public/docs/dart/latest/guide/template-syntax-NEW.jade @@ -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') diff --git a/public/docs/ts/latest/guide/template-syntax.jade b/public/docs/ts/latest/guide/template-syntax.jade index 9c62f5d557..3d9be06b29 100644 --- a/public/docs/ts/latest/guide/template-syntax.jade +++ b/public/docs/ts/latest/guide/template-syntax.jade @@ -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 diff --git a/public/resources/images/devguide/template-syntax/input-output.png b/public/resources/images/devguide/template-syntax/input-output.png index 9df8efa5c4..528d7d62ab 100644 Binary files a/public/resources/images/devguide/template-syntax/input-output.png and b/public/resources/images/devguide/template-syntax/input-output.png differ