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:
parent
5dcffd69dc
commit
9e9666b2cc
@ -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 {
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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 element’s `class` attribute with
|
We can add and remove CSS class names from an element’s `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 we’ve met so far flow data in one direction: *from the component to an element*.
|
The bindings we’ve met so far flow data in one direction: **from a component to an element**.
|
||||||
|
|
||||||
Users don’t just stare at the screen. They enter text into input boxes. They pick items from lists.
|
Users don’t 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** — for example, `(click)` —
|
A **name between parentheses** — for example, `(click)` —
|
||||||
identifies the target event. In the following example, the target is the button’s click event.
|
identifies the target event. In the following example, the target is the button’s 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
|
||||||
We’re binding the input box `value` to a `firstName` property, and we’re listening for changes by binding to the input box’s `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
|
||||||
There’s 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
|
||||||
That’s cumbersome. Who can remember which element property to set and what event reports user changes?
|
That’s 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 component’s data property and set it
|
We shouldn't have to mention the data property twice. Angular should be able to capture the component’s data property and set it
|
||||||
with a single declaration — which it can with the `[( )]` syntax:
|
with a single declaration — 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_" <==> [_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 don’t need many of those directives in Angular 2.
|
We don’t 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 — in fact, it will work with any custom web component.
|
It can be used with native DOM elements but also with Angular components — 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user