docs(template-syntax/dart): updates to match TS (#2751)

* docs(template-syntax): refresh _cache

* docs(template-syntax/dart): updates to match TS

- Propagates TS-side changes:
  - update #2639 - new two-way binding section, and
  - fix #2687 - invalid attr syntax
- Fixes
  - #1898 - currency symbols
  - #2748 - Dart template-syntax e2e is failing
  - #2749 - deprecated `[className]`

* updated _cache file following Kathy's post-review edits

* Post Ward's review w/ cache updated

- Keep `my-` and `my` prefixes on selectors (for components and
directives, respectively).
- Drop `my-` from file names.
- Drop `My` as component class prefix.
This commit is contained in:
Patrice Chalin 2016-11-14 08:34:10 -08:00 committed by Kathy Walrath
parent 5dcffd69dc
commit 9e9666b2cc
6 changed files with 187 additions and 102 deletions

View File

@ -7,7 +7,8 @@ import 'package:angular2/common.dart';
import 'hero.dart'; import 'hero.dart';
import 'hero_detail_component.dart'; import 'hero_detail_component.dart';
import 'my_click_directive.dart'; import 'click_directive.dart';
import 'sizer_component.dart';
enum Color { red, green, blue } enum Color { red, green, blue }
@ -18,7 +19,8 @@ enum Color { red, green, blue }
HeroDetailComponent, HeroDetailComponent,
BigHeroDetailComponent, BigHeroDetailComponent,
MyClickDirective, MyClickDirective,
MyClickDirective2 MyClickDirective2,
MySizerComponent
]) ])
class AppComponent implements OnInit, AfterViewInit { class AppComponent implements OnInit, AfterViewInit {
@override @override
@ -165,6 +167,7 @@ class AppComponent implements OnInit, AfterViewInit {
bool isItalic = false; bool isItalic = false;
bool isBold = false; bool isBold = false;
String fontSize = 'large'; String fontSize = 'large';
String fontSizePx = '14';
Map<String, String> setStyle() { Map<String, String> setStyle() {
return { return {

View File

@ -14,7 +14,7 @@
</div> </div>
<br> <br>
<a href="#event-binding">Event Binding</a><br> <a href="#event-binding">Event Binding</a><br>
<a href="#two-way">Two-way Binding</a><br>
<br> <br>
<div>Directives</div> <div>Directives</div>
<div style="margin-left:8px"> <div style="margin-left:8px">
@ -242,9 +242,6 @@ button</button>
<button [attr.disabled]="!isUnchanged">Disabled as well</button> <button [attr.disabled]="!isUnchanged">Disabled as well</button>
<!-- can't remove it with [attr.disabled] either -->
<button disabled [attr.disabled]>Still disabled</button>
<!-- we'd have to remove it with property binding --> <!-- we'd have to remove it with property binding -->
<button disabled [disabled]="false">Enabled (but inert)</button> <button disabled [disabled]="false">Enabled (but inert)</button>
</div> </div>
@ -262,7 +259,7 @@ button</button>
<!-- #docregion class-binding-2 --> <!-- #docregion class-binding-2 -->
<!-- reset/override all class names with a binding --> <!-- reset/override all class names with a binding -->
<div class="bad curly special" <div class="bad curly special"
[className]="badCurly">Bad curly</div> [class]="badCurly">Bad curly</div>
<!-- #enddocregion class-binding-2 --> <!-- #enddocregion class-binding-2 -->
<!-- #docregion class-binding-3 --> <!-- #docregion class-binding-3 -->
@ -309,9 +306,9 @@ button</button>
<div> <div>
<!-- #docregion event-binding-3 --> <!-- #docregion event-binding-3 -->
<!-- `myClick` is an event on the custom `MyClickDirective` --> <!-- `myClick` is an event on the custom `MyClickDirective` -->
<!-- #docregion my-click --> <!-- #docregion myClick -->
<div (myClick)="clickMessage=$event">click with myClick</div> <div (myClick)="clickMessage=$event">click with myClick</div>
<!-- #enddocregion my-click --> <!-- #enddocregion myClick -->
<!-- #enddocregion event-binding-3 --> <!-- #enddocregion event-binding-3 -->
{{clickMessage}} {{clickMessage}}
</div> </div>
@ -349,6 +346,24 @@ button</button>
</div> </div>
<!-- #enddocregion event-binding-propagation --> <!-- #enddocregion event-binding-propagation -->
<br><br> <br><br>
<a class="to-toc" href="#toc">top</a>
<hr><h2 id="two-way">Two-way Binding</h2>
<div id="two-way-1">
<!-- #docregion two-way-1 -->
<my-sizer [(size)]="fontSizePx"></my-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>
<!-- #enddocregion two-way-1 -->
<label>FontSize (px): <input [(ngModel)]="fontSizePx"></label>
</div>
<br>
<div id="two-way-2">
<h3>De-sugared two-way binding</h3>
<!-- #docregion two-way-2 -->
<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>
<!-- #enddocregion two-way-2 -->
</div>
<br><br>
<a class="to-toc" href="#toc">top</a> <a class="to-toc" href="#toc">top</a>
@ -746,7 +761,7 @@ bindon-ngModel
<div> <div>
<!-- pipe price to USD and display the $ symbol --> <!-- pipe price to USD and display the $ symbol -->
<label>Price: </label>{{product['price'] | currency:'USD':false}} <label>Price: </label>{{product['price'] | currency:'USD':true}}
</div> </div>
<a class="to-toc" href="#toc">top</a> <a class="to-toc" href="#toc">top</a>

View File

@ -5,11 +5,11 @@ import 'package:angular2/core.dart';
@Directive(selector: '[myClick]') @Directive(selector: '[myClick]')
class MyClickDirective { class MyClickDirective {
// #docregion my-click-output-1 // #docregion output-myClick
// @Output(alias) [type info] propertyName = ... // @Output(alias) [type info] propertyName = ...
@Output('myClick') final EventEmitter clicks = new EventEmitter<String>(); @Output('myClick') final EventEmitter clicks = new EventEmitter<String>();
// #enddocregion my-click-output-1 // #enddocregion output-myClick
bool _toggle = false; bool _toggle = false;
MyClickDirective(ElementRef el) { MyClickDirective(ElementRef el) {
@ -21,14 +21,14 @@ class MyClickDirective {
} }
} }
// #docregion my-click-output-2 // #docregion output-myClick2
@Directive( @Directive(
// #enddocregion my-click-output-2 // #enddocregion output-myClick2
selector: '[myClick2]', selector: '[myClick2]',
// #docregion my-click-output-2 // #docregion output-myClick2
// ... // ...
outputs: const ['clicks:myClick']) // propertyName:alias outputs: const ['clicks:myClick']) // propertyName:alias
// #enddocregion my-click-output-2 // #enddocregion output-myClick2
class MyClickDirective2 { class MyClickDirective2 {
final EventEmitter clicks = new EventEmitter<String>(); final EventEmitter clicks = new EventEmitter<String>();
bool _toggle = false; bool _toggle = false;

View File

@ -0,0 +1,30 @@
// #docregion
import 'dart:math';
import 'package:angular2/core.dart';
@Component(
selector: 'my-sizer',
template: '''
<div>
<button (click)="dec()" title="smaller">-</button>
<button (click)="inc()" title="bigger">+</button>
<label [style.font-size.px]="size">FontSize: {{size}}px</label>
</div>''')
class MySizerComponent {
static final defaultPxSize = 14;
@Input()
String size;
@Output()
var sizeChange = new EventEmitter<String>();
void dec() => resize(-1);
void inc() => resize(1);
void resize(num delta) {
final numSize = num.parse(size, (_) => defaultPxSize);
size = min(40, max(8, numSize + delta)).toString();
sizeChange.emit(size);
}
}

View File

@ -48,16 +48,6 @@ block dart-type-exception-example
In checked mode, the code above will result in a type exception: In checked mode, the code above will result in a type exception:
`String` isn't a subtype of `Hero`. `String` isn't a subtype of `Hero`.
block dart-class-binding-bug
.callout.is-helpful
header Angular Issue #6901
:marked
Issue [#6901][6901] prevents us from using `[class]`. As is illustrated
above, in the meantime we can achieve the same effect by binding to
`className`.
[6901]: http://github.com/angular/angular/issues/6901
block style-property-name-dart-diff block style-property-name-dart-diff
.callout.is-helpful .callout.is-helpful
header Dart difference: Style property names header Dart difference: Style property names

View File

@ -21,6 +21,7 @@ block includes
* [Property binding](#property-binding) * [Property binding](#property-binding)
* [Attribute, class, and style bindings](#other-bindings) * [Attribute, class, and style bindings](#other-bindings)
* [Event binding](#event-binding) * [Event binding](#event-binding)
* [Two-way data binding](#two-way)
* [Two-way data binding with `NgModel`](#ngModel) * [Two-way data binding with `NgModel`](#ngModel)
* [Built-in directives](#directives) * [Built-in directives](#directives)
* [NgClass](#ngClass) * [NgClass](#ngClass)
@ -44,7 +45,7 @@ block includes
HTML is the language of the Angular template. Our [QuickStart](../quickstart.html) application has a template that is pure HTML: HTML is the language of the Angular template. Our [QuickStart](../quickstart.html) application has a template that is pure HTML:
code-example(language="html" escape="html"). code-example(language="html" escape="html").
<h1>My First Angular 2 App</h1> <h1>My First Angular App</h1>
:marked :marked
Almost all HTML syntax is valid template syntax. The `<script>` element is a notable exception; it is forbidden, eliminating the risk of script injection attacks. (In practice, `<script>` is simply ignored.) Almost all HTML syntax is valid template syntax. The `<script>` element is a notable exception; it is forbidden, eliminating the risk of script injection attacks. (In practice, `<script>` is simply ignored.)
@ -387,7 +388,7 @@ table
.callout.is-helpful .callout.is-helpful
header A world without attributes header A world without attributes
:marked :marked
In the world of Angular 2, the only role of attributes is to initialize element and directive state. In the world of Angular, the only role of attributes is to initialize element and directive state.
When we data bind, we're dealing exclusively with element and directive properties and events. When we data bind, we're dealing exclusively with element and directive properties and events.
Attributes effectively disappear. Attributes effectively disappear.
:marked :marked
@ -489,8 +490,8 @@ table
If we must read a target element property or call one of its methods, If we must read a target element property or call one of its methods,
we'll need a different technique. we'll need a different technique.
See the API reference for See the API reference for
[viewChild](../api/core/index/ViewChild-var.html) and [ViewChild](../api/core/index/ViewChild-decorator.html) and
[contentChild](../api/core/index/ContentChild-var.html). [ContentChild](../api/core/index/ContentChild-decorator.html).
:marked :marked
### Binding target ### Binding target
@ -580,7 +581,7 @@ a(id="one-time-initialization")
:marked :marked
#### Content Security #### Content security
Imagine the following *malicious content*. Imagine the following *malicious content*.
+makeExample('template-syntax/ts/app/app.component.ts', 'evil-title')(format=".") +makeExample('template-syntax/ts/app/app.component.ts', 'evil-title')(format=".")
:marked :marked
@ -598,10 +599,10 @@ figure.image-display
.l-main-section .l-main-section
:marked :marked
<a id="other-bindings"></a> <a id="other-bindings"></a>
## Attribute, Class, and Style Bindings ## Attribute, class, and style bindings
The template syntax provides specialized one-way bindings for scenarios less well suited to property binding. The template syntax provides specialized one-way bindings for scenarios less well suited to property binding.
### Attribute Binding ### Attribute binding
We can set the value of an attribute directly with an **attribute binding**. We can set the value of an attribute directly with an **attribute binding**.
.l-sub-section .l-sub-section
:marked :marked
@ -651,7 +652,7 @@ code-example(format="nocode").
is to set ARIA attributes, as in this example: is to set ARIA attributes, as in this example:
+makeExample('template-syntax/ts/app/app.component.html', 'attrib-binding-aria')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'attrib-binding-aria')(format=".")
:marked :marked
### Class Binding ### Class binding
We can add and remove CSS class names from an elements `class` attribute with We can add and remove CSS class names from an elements `class` attribute with
a **class binding**. a **class binding**.
@ -667,9 +668,6 @@ code-example(format="nocode").
We can replace that with a binding to a string of the desired class names; this is an all-or-nothing, replacement binding. We can replace that with a binding to a string of the desired class names; this is an all-or-nothing, replacement binding.
+makeExample('template-syntax/ts/app/app.component.html', 'class-binding-2')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'class-binding-2')(format=".")
block dart-class-binding-bug
//- N/A
:marked :marked
Finally, we can bind to a specific class name. Finally, we can bind to a specific class name.
Angular adds the class when the template expression evaluates to #{_truthy}. Angular adds the class when the template expression evaluates to #{_truthy}.
@ -682,7 +680,7 @@ block dart-class-binding-bug
we generally prefer the [NgClass directive](#ngClass) for managing multiple class names at the same time. we generally prefer the [NgClass directive](#ngClass) for managing multiple class names at the same time.
:marked :marked
### Style Binding ### Style binding
We can set inline styles with a **style binding**. We can set inline styles with a **style binding**.
@ -711,12 +709,12 @@ block style-property-name-dart-diff
.l-main-section .l-main-section
:marked :marked
## Event Binding ## Event binding
The bindings weve met so far flow data in one direction: *from the component to an element*. The bindings weve met so far flow data in one direction: **from a component to an element**.
Users dont just stare at the screen. They enter text into input boxes. They pick items from lists. Users dont just stare at the screen. They enter text into input boxes. They pick items from lists.
They click buttons. Such user actions may result in a flow of data in the opposite direction: They click buttons. Such user actions may result in a flow of data in the opposite direction:
*from an element to the component*. **from an element to a component**.
The only way to know about a user action is to listen for certain events such as The only way to know about a user action is to listen for certain events such as
keystrokes, mouse movements, clicks, and touches. keystrokes, mouse movements, clicks, and touches.
@ -728,12 +726,12 @@ block style-property-name-dart-diff
the component's `onSave()` method whenever a click occurs: the component's `onSave()` method whenever a click occurs:
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-1')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'event-binding-1')(format=".")
:marked :marked
### Target Event ### Target event
A **name between enclosing parentheses** &mdash; for example, `(click)` &mdash; A **name between parentheses** &mdash; for example, `(click)` &mdash;
identifies the target event. In the following example, the target is the buttons click event. identifies the target event. In the following example, the target is the buttons click event.
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-1')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'event-binding-1')(format=".")
:marked :marked
Some people prefer the `on-` prefix alternative, known as the *canonical form*: Some people prefer the `on-` prefix alternative, known as the **canonical form**:
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-2')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'event-binding-2')(format=".")
:marked :marked
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
@ -742,8 +740,8 @@ block style-property-name-dart-diff
.l-sub-section .l-sub-section
:marked :marked
The `myClick` directive is further described below in the section The `myClick` directive is further described in the section
on [Aliasing input/output properties](#aliasing-io). on [aliasing input/output properties](#aliasing-io).
:marked :marked
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,
@ -753,36 +751,35 @@ block style-property-name-dart-diff
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.
When the event is raised, the handler executes the template statement. When the event is raised, the handler executes the template statement.
The template statement typically involves a receiver that wants to do something The template statement typically involves a receiver, which performs an action
in response to the event, such as take a value from the HTML control and store it in response to the event, such as storing a value from the HTML control
in a model. into a model.
The binding conveys information about the event, including data values, through The binding conveys information about the event, including data values, through
an **event object named `$event`**. an **event object named `$event`**.
The shape of the event object is determined by the target event itself. The shape of the event object is determined by the target event.
If the target event is a native DOM element event, the `$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:
+makeExample('template-syntax/ts/app/app.component.html', 'without-NgModel')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'without-NgModel')(format=".")
:marked :marked
Were binding the input box `value` to a `firstName` property, and were listening for changes by binding to the input boxs `input` event. This code sets the input box `value` property by binding to the `firstName` property. To listen for changes to the value, the code binds to the input box's `input` event.
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`. 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`.
To update the `firstName` property, we must get the changed text by following To update the `firstName` property, the changed text is retrieved by following the path `$event.target.value`.
the path `$event.target.value`.
If the event belongs to a directive (remember: components are directives), `$event` has whatever shape the directive chose to produce. If the event belongs to a directive (recall that components are directives), `$event` has whatever shape the directive decides to produce.
<a id="eventemitter"></a> <a id="eventemitter"></a>
<a id="custom-event"></a> <a id="custom-event"></a>
### Custom Events with EventEmitter ### Custom events with *EventEmitter*
Directives typically raise custom events with an Angular [EventEmitter](../api/core/index/EventEmitter-class.html). Directives typically raise custom events with an Angular [EventEmitter](../api/core/index/EventEmitter-class.html).
A 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 that 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 a `HeroDetailComponent` that presents hero information and responds to user actions.
@ -797,8 +794,8 @@ block style-property-name-dart-diff
:marked :marked
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,
which tells the `EventEmitter` to emit a `Hero` object. telling the `EventEmitter` to emit a `Hero` 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 `HeroDetailComponent`'s `deleteRequest` event.
@ -810,12 +807,11 @@ block style-property-name-dart-diff
### Template statements have side effects ### Template statements have side effects
The `deleteHero` method has a side effect: it deletes a hero. The `deleteHero` method has a side effect: it deletes a hero.
Template statement side effects are not just OK, they are expected. Template statement side effects are not just OK, but expected.
Deleting the hero updates the model, perhaps triggering other changes Deleting the hero updates the model, perhaps triggering other changes
including queries and saves to a remote server. including queries and saves to a remote server.
These changes percolate through the system and are ultimately displayed in this and other views. These changes percolate through the system and are ultimately displayed in this and other views.
It's all good.
// //
:marked :marked
@ -843,19 +839,72 @@ block style-property-name-dart-diff
and the outer `<div>`, causing a double save. and the outer `<div>`, causing a double save.
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-propagation')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'event-binding-propagation')(format=".")
#two-way
.l-main-section
:marked
## Two-way binding
We often want to both display a data property and update that property when the user makes changes.
On the element side that takes a combination of setting a specific element property
and listening for an element change event.
Angular offers a special _two-way data binding_ syntax for this purpose, **`[(x)]`**.
The `[(x)]` syntax combines the brackets
of _property binding_, `[x]`, with the parentheses of _event binding_, `(x)`.
.callout.is-important
header [( )] = banana in a box
:marked
Visualize a *banana in a box* to remember that the parentheses go _inside_ the brackets.
:marked
The `[(x)]` syntax is easy to demonstrate when the element has a settable property called `x`
and a corresponding event named `xChange`.
Here's a `SizerComponent` that fits the pattern.
It has a `size` value property and a companion `sizeChange` event:
+makeExample('app/sizer.component.ts')
:marked
The initial `size` is an input value from a property binding.
Clicking the buttons increases or decreases the `size`, within min/max values constraints,
and then raises (_emits_) the `sizeChange` event with the adjusted size.
Here's an example in which the `AppComponent.fontSizePx` is two-way bound to the `SizerComponent`:
+makeExcerpt('app/app.component.html', 'two-way-1', '')
:marked
The `AppComponent.fontSizePx` establishes the initial `SizerComponent.size` value.
Clicking the buttons updates the `AppComponent.fontSizePx` via the two-way binding.
The revised `AppComponent.fontSizePx` value flows through to the _style_ binding, making the displayed text bigger or smaller.
Try it in the <live-example></live-example>.
The two-way binding syntax is really just syntactic sugar for a _property_ binding and an _event_ binding.
Angular _desugars_ the `SizerComponent` binding into this:
+makeExcerpt('app/app.component.html', 'two-way-2', '')
:marked
The `$event` variable contains the payload of the `SizerComponent.sizeChange` event.
Angular assigns the `$event` value to the `AppComponent.fontSizePx` when the user clicks the buttons.
Clearly the two-way binding syntax is a great convenience compared to separate property and event bindings.
We'd like to use two-way binding with HTML form elements like `<input>` and `<select>`.
Sadly, no native HTML element follows the `x` value and `xChange` event pattern.
Fortunately, the Angular [_NgModel_](#ngModel) directive is a bridge that enables two-way binding to form elements.
a#ngModel
.l-main-section .l-main-section
:marked :marked
<a id="ngModel"></a>
## Two-way binding with NgModel ## Two-way binding with NgModel
When developing data entry forms, we often want to both display a data property and update that property when the user makes changes. When developing data entry forms, we often want to both display a data property and update that property when the user makes changes.
The `[(ngModel)]` two-way data binding syntax makes that easy. Here's an example: Two-way data binding with the `NgModel` directive makes that easy. Here's an example:
+makeExample('template-syntax/ts/app/app.component.html', 'NgModel-1')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'NgModel-1')(format=".")
.callout.is-important
header [()] = banana in a box
:marked
To remember that the parentheses go inside the brackets, visualize a *banana in a box*.
+ifDocsFor('ts|js') +ifDocsFor('ts|js')
.callout.is-important .callout.is-important
@ -865,18 +914,18 @@ block style-property-name-dart-diff
we must import the `FormsModule` and add it to the Angular module's `imports` list. we must import the `FormsModule` and add it to the Angular module's `imports` list.
Learn more about the `FormsModule` and `ngModel` in the Learn more about the `FormsModule` and `ngModel` in the
[Forms](../guide/forms.html#ngModel) chapter. [Forms](../guide/forms.html#ngModel) chapter.
:marked
Here's how to import the `FormsModule` to make `[(ngModel)]` available.
+makeExample('template-syntax/ts/app/app.module.1.ts', '', 'app.module.ts (FormsModule import)') +makeExample('template-syntax/ts/app/app.module.1.ts', '', 'app.module.ts (FormsModule import)')
:marked :marked
Theres a story behind this construction, a story that builds on the property and event binding techniques we learned previously.
### Inside `[(ngModel)]` ### Inside `[(ngModel)]`
We could have achieved the same result with separate bindings to Looking back at the `firstName` binding, it's important to note that
we could have achieved the same result with separate bindings to
the `<input>` element's `value` property and `input` event. the `<input>` element's `value` property and `input` event.
+makeExample('template-syntax/ts/app/app.component.html', 'without-NgModel')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'without-NgModel')(format=".")
:marked :marked
Thats cumbersome. Who can remember which element property to set and what event reports user changes? Thats cumbersome. Who can remember which element property to set and which element event emits user changes?
How do we extract the currently displayed text from the input box so we can update the data property? How do we extract the currently displayed text from the input box so we can update the data property?
Who wants to look that up each time? Who wants to look that up each time?
@ -884,35 +933,29 @@ block style-property-name-dart-diff
+makeExample('template-syntax/ts/app/app.component.html', 'NgModel-3')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'NgModel-3')(format=".")
.l-sub-section .l-sub-section
:marked :marked
The `ngModel` input property sets the element's value property and the `ngModelChange` output property The `ngModel` data property sets the element's value property and the `ngModelChange` event property
listens for changes to the element's value. listens for changes to the element's value.
The details are specific to each kind of element and therefore the `NgModel` directive only works for elements,
such as the input text box, that are supported by a [ControlValueAccessor](../api/common/index/ControlValueAccessor-interface.html). The details are specific to each kind of element and therefore the `NgModel` directive only works for specific form elements,
We can't apply `[(ngModel)]` to our custom components until we write a suitable *value accessor*, such as the input text box, that are supported by a [ControlValueAccessor](../api/forms/index/ControlValueAccessor-interface.html).
We can't apply `[(ngModel)]` to a custom component until we write a suitable *value accessor*,
a technique that is beyond the scope of this chapter. a technique that is beyond the scope of this chapter.
That's something we might want to do for an Angular component or a WebComponent whose API we can't control.
It's completely unnecessary for an Angular component that we _do_ control ... because we can name the value and event properties
to suit Angular's basic [two-way binding syntax](#two-way) and skip `NgModel` altogether.
:marked :marked
Separate `ngModel` bindings is an improvement. We can do better. Separate `ngModel` bindings is an improvement over binding to the element's native properties. We can do better.
We shouldn't have to mention the data property twice. Angular should be able to capture the components data property and set it We shouldn't have to mention the data property twice. Angular should be able to capture the components data property and set it
with a single declaration &mdash; which it can with the `[( )]` syntax: with a single declaration &mdash; which it can with the `[(ngModel)]` syntax:
+makeExample('template-syntax/ts/app/app.component.html', 'NgModel-1')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'NgModel-1')(format=".")
.l-sub-section
:marked
`[(ngModel)]` is a specific example of a more general pattern in which Angular "de-sugars" the `[(x)]` syntax
into an `x` input property for property binding and an `xChange` output property for event binding.
Angular constructs the event property binding's template statement by appending `=$event`
to the literal string of the template expression.
> <span style="font-family:courier">[(_x_)]="_e_" &lt;==> [_x_]="_e_" (<i>x</i>Change)="_e_=$event"</span>
We can write a two-way binding directive of our own to exploit this behavior.
:marked :marked
Is `[(ngModel)]` all we need? Is there ever a reason to fall back to its expanded form? Is `[(ngModel)]` all we need? Is there ever a reason to fall back to its expanded form?
The `[( )]` syntax can only _set_ a data-bound property. The `[(ngModel)]` syntax can only _set_ a data-bound property.
If we need to do something more or something different, we need to write the expanded form ourselves. If we need to do something more or something different, we need to write the expanded form ourselves.
Let's try something silly like forcing the input value to uppercase: Let's try something silly like forcing the input value to uppercase:
@ -931,8 +974,8 @@ figure.image-display
The community contributed many more, and countless private directives The community contributed many more, and countless private directives
have been created for internal applications. have been created for internal applications.
We dont need many of those directives in Angular 2. We dont need many of those directives in Angular.
Quite often we can achieve the same results with the more capable and expressive Angular 2 binding system. Quite often we can achieve the same results with the more capable and expressive Angular binding system.
Why create a directive to handle a click when we can write a simple binding such as this? Why create a directive to handle a click when we can write a simple binding such as this?
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-1')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'event-binding-1')(format=".")
:marked :marked
@ -1203,7 +1246,7 @@ block remember-the-brackets
:marked :marked
### Expanding `*ngSwitch` ### Expanding `*ngSwitch`
A similar transformation applies to `*ngSwitch`. We can de-sugar the syntax ourselves. A similar transformation applies to `*ngSwitch`. We can unfold the syntax ourselves.
Here's an example, first with `*ngSwitchCase` and `*ngSwitchDefault` and then again with `<template>` tags: Here's an example, first with `*ngSwitchCase` and `*ngSwitchDefault` and then again with `<template>` tags:
+makeExample('template-syntax/ts/app/app.component.html', 'NgSwitch-expanded')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'NgSwitch-expanded')(format=".")
:marked :marked
@ -1241,14 +1284,18 @@ figure.image-display
A **template reference variable** is a reference to a DOM element or directive within a template. A **template reference variable** is a reference to a DOM element or directive within a template.
It can be used with native DOM elements but also with Angular 2 components &mdash; in fact, it will work with any custom web component. It can be used with native DOM elements but also with Angular components &mdash; in fact, it will work with any custom web component.
:marked :marked
### Referencing a template reference variable ### Referencing a template reference variable
We can reference a template reference variable on the same element, on a sibling element, or on We can refer to a template reference variable _anywhere_ in the current template.
any child elements. .l-sub-section
:marked
Do not define the same variable name more than once in the same template.
The runtime value will be unpredictable.
:marked
Here are two other examples of creating and consuming a Template reference variable: Here are two other examples of creating and consuming a Template reference variable:
+makeExample('template-syntax/ts/app/app.component.html', 'ref-phone')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'ref-phone')(format=".")
:marked :marked
@ -1376,7 +1423,7 @@ h3#aliasing-io Aliasing input/output properties
Directive consumers expect to bind to the name of the directive. Directive consumers expect to bind to the name of the directive.
For example, when we apply a directive with a `myClick` selector to a `<div>` tag, For example, when we apply a directive with a `myClick` selector to a `<div>` tag,
we expect to bind to an event property that is also called `myClick`. we expect to bind to an event property that is also called `myClick`.
+makeExample('template-syntax/ts/app/app.component.html', 'my-click')(format=".") +makeExample('template-syntax/ts/app/app.component.html', 'myClick')(format=".")
:marked :marked
However, the directive name is often a poor choice for the name of a property within the directive class. However, the directive name is often a poor choice for the name of a property within the directive class.
The directive name rarely describes what the property does. The directive name rarely describes what the property does.
@ -1389,14 +1436,14 @@ h3#aliasing-io Aliasing input/output properties
We can specify the alias for the property name by passing it into the input/output decorator like this: We can specify the alias for the property name by passing it into the input/output decorator like this:
+makeExample('template-syntax/ts/app/my-click.directive.ts', 'my-click-output-1')(format=".") +makeExample('template-syntax/ts/app/click.directive.ts', 'output-myClick')(format=".")
.l-sub-section .l-sub-section
:marked :marked
We can also alias property names in the `inputs` and `outputs` #{_array}s. We can also alias property names in the `inputs` and `outputs` #{_array}s.
We write a colon-delimited (`:`) string with We write a colon-delimited (`:`) string with
the directive property name on the *left* and the public alias on the *right*: the directive property name on the *left* and the public alias on the *right*:
+makeExample('template-syntax/ts/app/my-click.directive.ts', 'my-click-output-2')(format=".") +makeExample('template-syntax/ts/app/click.directive.ts', 'output-myClick2')(format=".")
<a id="expression-operators"></a> <a id="expression-operators"></a>
.l-main-section .l-main-section