example(template-syntax): follow style-guide and other updates (#2750)
This commit is contained in:
parent
7619cdf4a4
commit
64a8754386
|
@ -31,14 +31,13 @@ describe('Template Syntax', function () {
|
||||||
expect(specialButtonEle.getAttribute('style')).toMatch('color: red');
|
expect(specialButtonEle.getAttribute('style')).toMatch('color: red');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should two-way bind to sizer', function () {
|
it('should two-way bind to sizer', async () => {
|
||||||
let buttons = element.all(by.css('div#two-way-1 my-sizer button'));
|
let div = element(by.css('div#two-way-1'));
|
||||||
let input = element(by.css('input#fontsize'));
|
let incButton = div.element(by.buttonText('+'));
|
||||||
|
let input = div.element(by.css('input'));
|
||||||
input.getAttribute('value').then(size => {
|
let initSize = await input.getAttribute('value');
|
||||||
buttons.get(1).click();
|
incButton.click();
|
||||||
browser.waitForAngular();
|
expect(input.getAttribute('value')).toEqual((+initSize + 1).toString());
|
||||||
expect(input.getAttribute('value')).toEqual((+size + 1).toString());
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -305,10 +305,10 @@ 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 `ClickDirective` -->
|
||||||
<!-- #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>
|
||||||
|
@ -351,21 +351,22 @@ button</button>
|
||||||
<hr><h2 id="two-way">Two-way Binding</h2>
|
<hr><h2 id="two-way">Two-way Binding</h2>
|
||||||
<div id="two-way-1">
|
<div id="two-way-1">
|
||||||
<!-- #docregion two-way-1 -->
|
<!-- #docregion two-way-1 -->
|
||||||
<my-sizer [(size)]="fontSize"></my-sizer>
|
<my-sizer [(size)]="fontSizePx"></my-sizer>
|
||||||
<div [style.font-size.px]="fontSize">Resizable Text</div>
|
<div [style.font-size.px]="fontSizePx">Resizable Text</div>
|
||||||
<!-- #enddocregion two-way-1 -->
|
<!-- #enddocregion two-way-1 -->
|
||||||
<label>FontSize: <input id="fontsize" [(ngModel)]="fontSize"></label>
|
<label>FontSize (px): <input [(ngModel)]="fontSizePx"></label>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<div id="two-way-2">
|
<div id="two-way-2">
|
||||||
<h3>De-sugared two-way binding</h3>
|
<h3>De-sugared two-way binding</h3>
|
||||||
<!-- #docregion two-way-2 -->
|
<!-- #docregion two-way-2 -->
|
||||||
<my-sizer [size]="fontSize" (sizeChange)="fontSize=$event"></my-sizer>
|
<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>
|
||||||
<!-- #enddocregion two-way-2 -->
|
<!-- #enddocregion two-way-2 -->
|
||||||
</div>
|
</div>
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
<a class="to-toc" href="#toc">top</a>
|
<a class="to-toc" href="#toc">top</a>
|
||||||
|
|
||||||
<!-- Two way data binding unwound;
|
<!-- Two way data binding unwound;
|
||||||
passing the changed display value to the event handler via `$event` -->
|
passing the changed display value to the event handler via `$event` -->
|
||||||
<hr><h2 id="ngModel">NgModel (two-way) Binding</h2>
|
<hr><h2 id="ngModel">NgModel (two-way) Binding</h2>
|
||||||
|
@ -428,6 +429,18 @@ bindon-ngModel
|
||||||
<!-- NgStyle binding -->
|
<!-- NgStyle binding -->
|
||||||
<hr><h2 id="ngStyle">NgStyle Binding</h2>
|
<hr><h2 id="ngStyle">NgStyle Binding</h2>
|
||||||
|
|
||||||
|
<!-- #docregion NgStyle -->
|
||||||
|
<div>
|
||||||
|
<p [ngStyle]="setStyle()" #styleP>Change style of this text!</p>
|
||||||
|
|
||||||
|
<label>Italic: <input type="checkbox" [(ngModel)]="isItalic"></label> |
|
||||||
|
<label>Bold: <input type="checkbox" [(ngModel)]="isBold"></label> |
|
||||||
|
<label>Size: <input type="text" [(ngModel)]="fontSize"></label>
|
||||||
|
|
||||||
|
<p>Style set to: <code>'{{styleP.style.cssText}}'</code></p>
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion NgStyle -->
|
||||||
|
|
||||||
<!-- #docregion NgStyle-1 -->
|
<!-- #docregion NgStyle-1 -->
|
||||||
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
|
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
|
||||||
This div is x-large.
|
This div is x-large.
|
||||||
|
|
|
@ -50,8 +50,6 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||||
this.alert('Deleted hero: ' + (hero && hero.firstName));
|
this.alert('Deleted hero: ' + (hero && hero.firstName));
|
||||||
}
|
}
|
||||||
|
|
||||||
fontSize = 10;
|
|
||||||
|
|
||||||
// #docregion evil-title
|
// #docregion evil-title
|
||||||
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
|
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
|
||||||
// #enddocregion evil-title
|
// #enddocregion evil-title
|
||||||
|
@ -180,6 +178,21 @@ export class AppComponent implements AfterViewInit, OnInit {
|
||||||
}
|
}
|
||||||
// #enddocregion setStyles
|
// #enddocregion setStyles
|
||||||
|
|
||||||
|
// #docregion NgStyle
|
||||||
|
isItalic = false;
|
||||||
|
isBold = false;
|
||||||
|
fontSize: string = 'large';
|
||||||
|
fontSizePx: number | string = 14;
|
||||||
|
|
||||||
|
setStyle() {
|
||||||
|
return {
|
||||||
|
'font-style': this.isItalic ? 'italic' : 'normal',
|
||||||
|
'font-weight': this.isBold ? 'bold' : 'normal',
|
||||||
|
'font-size': this.fontSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// #enddocregion NgStyle
|
||||||
|
|
||||||
toeChoice = '';
|
toeChoice = '';
|
||||||
toeChooser(picker: HTMLFieldSetElement) {
|
toeChooser(picker: HTMLFieldSetElement) {
|
||||||
let choices = picker.children;
|
let choices = picker.children;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { BigHeroDetailComponent, HeroDetailComponent } from './hero-detail.component';
|
import { BigHeroDetailComponent, HeroDetailComponent } from './hero-detail.component';
|
||||||
import { MyClickDirective, MyClickDirective2 } from './my-click.directive';
|
import { ClickDirective, ClickDirective2 } from './click.directive';
|
||||||
import { SizerComponent } from './sizer.component';
|
import { SizerComponent } from './sizer.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -16,8 +16,8 @@ import { SizerComponent } from './sizer.component';
|
||||||
AppComponent,
|
AppComponent,
|
||||||
BigHeroDetailComponent,
|
BigHeroDetailComponent,
|
||||||
HeroDetailComponent,
|
HeroDetailComponent,
|
||||||
MyClickDirective,
|
ClickDirective,
|
||||||
MyClickDirective2,
|
ClickDirective2,
|
||||||
SizerComponent
|
SizerComponent
|
||||||
],
|
],
|
||||||
bootstrap: [ AppComponent ]
|
bootstrap: [ AppComponent ]
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';
|
import { Directive, ElementRef, EventEmitter, Output } from '@angular/core';
|
||||||
|
|
||||||
@Directive({selector: '[myClick]'})
|
@Directive({selector: '[myClick]'})
|
||||||
export class MyClickDirective {
|
export class ClickDirective {
|
||||||
// #docregion my-click-output-1
|
// #docregion output-myClick
|
||||||
@Output('myClick') clicks = new EventEmitter<string>(); // @Output(alias) propertyName = ...
|
@Output('myClick') clicks = new EventEmitter<string>(); // @Output(alias) propertyName = ...
|
||||||
// #enddocregion my-click-output-1
|
// #enddocregion output-myClick
|
||||||
|
|
||||||
toggle = false;
|
toggle = false;
|
||||||
|
|
||||||
|
@ -19,15 +19,15 @@ export 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: ['clicks:myClick'] // propertyName:alias
|
outputs: ['clicks:myClick'] // propertyName:alias
|
||||||
})
|
})
|
||||||
// #enddocregion my-click-output-2
|
// #enddocregion output-myClick2
|
||||||
export class MyClickDirective2 {
|
export class ClickDirective2 {
|
||||||
clicks = new EventEmitter<string>();
|
clicks = new EventEmitter<string>();
|
||||||
toggle = false;
|
toggle = false;
|
||||||
|
|
|
@ -11,15 +11,14 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
</div>`
|
</div>`
|
||||||
})
|
})
|
||||||
export class SizerComponent {
|
export class SizerComponent {
|
||||||
@Input() size: number;
|
@Input() size: number | string;
|
||||||
@Output() sizeChange = new EventEmitter<number>();
|
@Output() sizeChange = new EventEmitter<number>();
|
||||||
|
|
||||||
dec() { this.resize(-1); }
|
dec() { this.resize(-1); }
|
||||||
inc() { this.resize(+1); }
|
inc() { this.resize(+1); }
|
||||||
|
|
||||||
resize(delta: number) {
|
resize(delta: number) {
|
||||||
const size = +this.size + delta;
|
this.size = Math.min(40, Math.max(8, +this.size + delta));
|
||||||
this.size = Math.min(40, Math.max(8, size));
|
|
||||||
this.sizeChange.emit(this.size);
|
this.sizeChange.emit(this.size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -490,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-decorator.html) and
|
[ViewChild](../api/core/index/ViewChild-decorator.html) and
|
||||||
[contentChild](../api/core/index/ContentChild-decorator.html).
|
[ContentChild](../api/core/index/ContentChild-decorator.html).
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### Binding target
|
### Binding target
|
||||||
|
@ -581,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
|
||||||
|
@ -599,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
|
||||||
|
@ -652,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**.
|
||||||
|
@ -668,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}.
|
||||||
|
@ -683,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**.
|
||||||
|
|
||||||
|
@ -747,7 +744,7 @@ block style-property-name-dart-diff
|
||||||
on [aliasing input/output properties](#aliasing-io).
|
on [aliasing input/output properties](#aliasing-io).
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
If the name fails to match element event or output property of a known directive,
|
If the name fails to match an element event or an output property of a known directive,
|
||||||
Angular reports an “unknown directive” error.
|
Angular reports an “unknown directive” error.
|
||||||
|
|
||||||
### *$event* and event handling statements
|
### *$event* and event handling statements
|
||||||
|
@ -778,7 +775,7 @@ block style-property-name-dart-diff
|
||||||
|
|
||||||
<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).
|
||||||
The directive creates an `EventEmitter` and exposes it as a property.
|
The directive creates an `EventEmitter` and exposes it as a property.
|
||||||
|
@ -853,36 +850,44 @@ block style-property-name-dart-diff
|
||||||
|
|
||||||
Angular offers a special _two-way data binding_ syntax for this purpose, **`[(x)]`**.
|
Angular offers a special _two-way data binding_ syntax for this purpose, **`[(x)]`**.
|
||||||
The `[(x)]` syntax combines the brackets
|
The `[(x)]` syntax combines the brackets
|
||||||
of _Property Binding_, `[x]`, with the parentheses of _Event Binding_, `(x)`.
|
of _property binding_, `[x]`, with the parentheses of _event binding_, `(x)`.
|
||||||
|
|
||||||
.callout.is-important
|
.callout.is-important
|
||||||
header [( )] = banana in a box
|
header [( )] = banana in a box
|
||||||
:marked
|
:marked
|
||||||
Visualize a *banana in a box* to remember that the parentheses go _inside_ the brackets.
|
Visualize a *banana in a box* to remember that the parentheses go _inside_ the brackets.
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The `[(x)]` syntax is easy to demonstrate when the element has a settable property called `x`
|
The `[(x)]` syntax is easy to demonstrate when the element has a settable property called `x`
|
||||||
and a corresponding event named `xChange`.
|
and a corresponding event named `xChange`.
|
||||||
Here's a `SizerComponent` that fits the pattern.
|
Here's a `SizerComponent` that fits the pattern.
|
||||||
It has a `size` value property and a companion `sizeChange` event:
|
It has a `size` value property and a companion `sizeChange` event:
|
||||||
+makeExample('template-syntax/ts/app/sizer.component.ts', null, 'app/sizer.component.ts')
|
|
||||||
|
+makeExample('app/sizer.component.ts')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The initial `size` is an input value from a property binding.
|
The initial `size` is an input value from a property binding.
|
||||||
Clicking the buttons increases or decreases the `size`, within min/max values constraints,
|
Clicking the buttons increases or decreases the `size`, within min/max values constraints,
|
||||||
and then raises (_emits_) the `sizeChange` event with the adjusted size.
|
and then raises (_emits_) the `sizeChange` event with the adjusted size.
|
||||||
|
|
||||||
Here's an example in which the `AppComponent.fontSize` is two-way bound to the `SizerComponent`:
|
Here's an example in which the `AppComponent.fontSizePx` is two-way bound to the `SizerComponent`:
|
||||||
+makeExample('template-syntax/ts/app/app.component.html', 'two-way-1')(format=".")
|
|
||||||
|
+makeExcerpt('app/app.component.html', 'two-way-1', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The `AppComponent.fontSize` establishes the initial `SizerComponent.size` value.
|
The `AppComponent.fontSizePx` establishes the initial `SizerComponent.size` value.
|
||||||
Clicking the buttons updates the `AppComponent.fontSize` via the two-way binding.
|
Clicking the buttons updates the `AppComponent.fontSizePx` via the two-way binding.
|
||||||
The revised `AppComponent.fontSize` value flows through to the _style_ binding, making the displayed text bigger or smaller.
|
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</live-example>.
|
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.
|
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:
|
Angular _desugars_ the `SizerComponent` binding into this:
|
||||||
+makeExample('template-syntax/ts/app/app.component.html', 'two-way-2')(format=".")
|
|
||||||
|
+makeExcerpt('app/app.component.html', 'two-way-2', '')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The `$event` variable contains the payload of the `SizerComponent.sizeChange` event.
|
The `$event` variable contains the payload of the `SizerComponent.sizeChange` event.
|
||||||
Angular assigns the `$event` value to the `AppComponent.fontSize` when the user clicks the buttons.
|
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.
|
Clearly the two-way binding syntax is a great convenience compared to separate property and event bindings.
|
||||||
|
|
||||||
|
@ -1418,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.
|
||||||
|
@ -1431,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…
Reference in New Issue