parent
31051d29f0
commit
7cf097ea68
|
@ -23,7 +23,7 @@
|
|||
|
||||
<!-- #docregion sum-2 -->
|
||||
<!-- "The sum of 1 + 1 is not 4" -->
|
||||
<p>The sum of 1 + 1 is not {{1+1+getVal()}}</p>
|
||||
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>
|
||||
<!-- #enddocregion sum-2 -->
|
||||
|
||||
|
||||
|
@ -60,22 +60,23 @@
|
|||
<!-- #docregion property-binding-syntax-1 -->
|
||||
<img [src] = "heroImageUrl">
|
||||
<hero-detail [hero]="currentHero"></hero-detail>
|
||||
<div [ng-class] = "{selected: isSelected}"></div>
|
||||
<div [ngClass] = "{selected: isSelected}"></div>
|
||||
<!-- #enddocregion property-binding-syntax-1 -->
|
||||
</div>
|
||||
<br><br>
|
||||
|
||||
<!-- See https://github.com/angular/angular/issues/5707 about "myClick (myClick)" -->
|
||||
<!-- #docregion event-binding-syntax-1 -->
|
||||
<button (click) = "onSave()">Save</button>
|
||||
<hero-detail (deleted)="onHeroDeleted()"></hero-detail>
|
||||
<div my-click (my-click)="clicked=$event">click me</div>
|
||||
<div myClick (myClick)="clicked=$event">click me</div>
|
||||
<!-- #enddocregion event-binding-syntax-1 -->
|
||||
{{clicked}}
|
||||
<br><br>
|
||||
|
||||
<div>
|
||||
<!-- #docregion 2-way-binding-syntax-1 -->
|
||||
<input [(ng-model)]="heroName">
|
||||
<input [(ngModel)]="heroName">
|
||||
<!-- #enddocregion 2-way-binding-syntax-1 -->
|
||||
Hero Name: {{heroName}}
|
||||
</div>
|
||||
|
@ -96,20 +97,6 @@
|
|||
<!-- #enddocregion style-binding-syntax-1 -->
|
||||
button</button>
|
||||
|
||||
<hr><h2>kebab-case</h2>
|
||||
<!-- #docregion bad-mixed-case -->
|
||||
<!-- BAD: mixed case target names don't work
|
||||
<label [textContent] = "title"></label>
|
||||
<hero-detail [isActive] = "isActive"></hero-detail>
|
||||
-->
|
||||
<!-- #enddocregion bad-mixed-case -->
|
||||
|
||||
<!-- #docregion kebab-case -->
|
||||
<!-- lower kebab-case -->
|
||||
<label [text-content] = "title"></label>
|
||||
<hero-detail [is-active] = "isActive"></hero-detail>
|
||||
<!-- #enddocregion kebab-case -->
|
||||
|
||||
<!-- property vs. attribute -->
|
||||
<hr><h2>Property vs. Attribute (img examples)</h2>
|
||||
<!-- examine the following <img> tag in the browser tools -->
|
||||
|
@ -148,7 +135,7 @@ button</button>
|
|||
<button [disabled]="isUnchanged">Cancel</button>
|
||||
<!-- #enddocregion property-binding-2 -->
|
||||
<!-- #docregion property-binding-3 -->
|
||||
<div [ng-class]="'special'">NgClass is special</div>
|
||||
<div [ngClass]="'special'">NgClass is special</div>
|
||||
<!-- #enddocregion property-binding-3 -->
|
||||
<!-- #docregion property-binding-4 -->
|
||||
<hero-detail [hero]="selectedHero"></hero-detail>
|
||||
|
@ -162,13 +149,13 @@ button</button>
|
|||
<hero-detail hero="…what do we do here??? …"></hero-detail>
|
||||
<!-- #enddocregion property-binding-6 -->
|
||||
|
||||
<!-- #docregion property-binding-v-interpolation -->
|
||||
<!-- #docregion property-binding-vs-interpolation -->
|
||||
<img src="{{heroImageUrl}}">
|
||||
<img [src]="'' + heroImageUrl">
|
||||
|
||||
<div>The title is {{title}}</div>
|
||||
<div [text-content]="'The title is '+title"></div>
|
||||
<!-- #enddocregion property-binding-v-interpolation -->
|
||||
<div [textContent]="'The title is '+title"></div>
|
||||
<!-- #enddocregion property-binding-vs-interpolation -->
|
||||
|
||||
<!-- attribute binding -->
|
||||
<hr><h2>Attribute Binding</h2>
|
||||
|
@ -180,7 +167,7 @@ button</button>
|
|||
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
|
||||
|
||||
<!-- ERROR: There is no `colspan` property to set!
|
||||
<tr><td colspan="{{1+1}}">Three-Four</td></tr>
|
||||
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
|
||||
-->
|
||||
|
||||
<tr><td>Five</td><td>Six</td></tr>
|
||||
|
@ -246,12 +233,12 @@ button</button>
|
|||
|
||||
<!-- #docregion style-binding-1 -->
|
||||
<button [style.color] = "isSpecial ? 'red' : 'green'">Red</button>
|
||||
<button [style.background-color]="canSave ?'cyan' : 'grey'" >Save</button>
|
||||
<button [style.backgroundColor]="canSave ?'cyan' : 'grey'" >Save</button>
|
||||
<!-- #enddocregion style-binding-1 -->
|
||||
|
||||
<!-- #docregion style-binding-2 -->
|
||||
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
|
||||
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
|
||||
<button [style.fontSize.em]="isSpecial ? 3 : 1" >Big</button>
|
||||
<button [style.fontSize.%]="!isSpecial ? 150 : 50" >Small</button>
|
||||
<!-- #enddocregion style-binding-2 -->
|
||||
|
||||
<!-- event binding -->
|
||||
|
@ -267,8 +254,8 @@ button</button>
|
|||
|
||||
<div>
|
||||
<!-- #docregion event-binding-3 -->
|
||||
<!-- `my-click` is an event on the custom `MyClickDirective` -->
|
||||
<div my-click (my-click)="clickity=$event">click with my-click</div>
|
||||
<!-- `myClick` is an event on the custom `MyClickDirective` -->
|
||||
<div myClick (myClick)="clickity=$event">click with myClick</div>
|
||||
<!-- #enddocregion event-binding-3 -->
|
||||
{{clickity}}
|
||||
</div>
|
||||
|
@ -322,28 +309,28 @@ button</button>
|
|||
without NgModel
|
||||
<br>
|
||||
<!-- #docregion NgModel-1 -->
|
||||
<input [(ng-model)]="currentHero.firstName">
|
||||
<input [(ngModel)]="currentHero.firstName">
|
||||
<!-- #enddocregion NgModel-1 -->
|
||||
[(ng-model)]
|
||||
[(ngModel)]
|
||||
<br>
|
||||
<!-- #docregion NgModel-2 -->
|
||||
<input bindon-ng-model="currentHero.firstName">
|
||||
<input bindon-ngModel="currentHero.firstName">
|
||||
<!-- #enddocregion NgModel-2 -->
|
||||
bindon-ng-model
|
||||
bindon-ngModel
|
||||
<br>
|
||||
<!-- #docregion NgModel-3 -->
|
||||
<input
|
||||
[ng-model]="currentHero.firstName"
|
||||
(ng-model-change)="currentHero.firstName=$event">
|
||||
[ngModel]="currentHero.firstName"
|
||||
(ngModelChange)="currentHero.firstName=$event">
|
||||
<!-- #enddocregion NgModel-3 -->
|
||||
(ng-model-change) = "...firstName=$event"
|
||||
(ngModelChange) = "...firstName=$event"
|
||||
<br>
|
||||
<!-- #docregion NgModel-4 -->
|
||||
<input
|
||||
[ng-model]="currentHero.firstName"
|
||||
(ng-model-change)="setUpperCaseFirstName($event)">
|
||||
[ngModel]="currentHero.firstName"
|
||||
(ngModelChange)="setUpperCaseFirstName($event)">
|
||||
<!-- #enddocregion NgModel-4 -->
|
||||
(ng-model-change) = "setUpperCaseFirstName($event)"
|
||||
(ngModelChange) = "setUpperCaseFirstName($event)"
|
||||
<br>
|
||||
|
||||
|
||||
|
@ -353,36 +340,36 @@ bindon-ng-model
|
|||
|
||||
<p>setClasses returns {{setClasses() | json}}</p>
|
||||
<!-- #docregion NgClass-1 -->
|
||||
<div [ng-class]="setClasses()">This div is saveable and special</div>
|
||||
<div [ngClass]="setClasses()">This div is saveable and special</div>
|
||||
<!-- #enddocregion NgClass-1 -->
|
||||
<div [ng-class]="setClasses()" #class-div>
|
||||
<div [ngClass]="setClasses()" #classDiv>
|
||||
After setClasses(), the classes are "{{classDiv.className}}"
|
||||
</div>
|
||||
|
||||
<!-- not used in chapter -->
|
||||
|
||||
<div [ng-class]="isSpecial ? 'special' : ''">This div is special</div>
|
||||
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
|
||||
|
||||
<div class="bad curly special">Bad curly special</div>
|
||||
<div [ng-class]="{bad:false, curly:true, special:true}">Curly special</div>
|
||||
<div [ngClass]="{bad:false, curly:true, special:true}">Curly special</div>
|
||||
|
||||
|
||||
<!-- NgStyle binding -->
|
||||
<hr><h2>NgStyle Binding</h2>
|
||||
|
||||
<!-- #docregion NgStyle-1 -->
|
||||
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
|
||||
<div [style.fontSize]="isSpecial ? 'x-large' : 'smaller'" >
|
||||
This div is x-large
|
||||
</div>
|
||||
<!-- #enddocregion NgStyle-1 -->
|
||||
|
||||
<p>setStyles returns {{setStyles() | json}}</p>
|
||||
<!-- #docregion NgStyle-2 -->
|
||||
<div [ng-style]="setStyles()">
|
||||
<div [ngStyle]="setStyles()">
|
||||
This div is italic, normal weight, and x-large
|
||||
</div>
|
||||
<!-- #enddocregion NgStyle-2 -->
|
||||
<div [ng-style]="setStyles()" #style-div>
|
||||
<div [ngStyle]="setStyles()" #styleDiv>
|
||||
After setStyles(), the styles are "{{getStyles(styleDiv)}}"
|
||||
</div>
|
||||
|
||||
|
@ -394,26 +381,26 @@ After setClasses(), the classes are "{{classDiv.className}}"
|
|||
<hr><h2>NgIf Binding</h2>
|
||||
|
||||
<!-- #docregion NgIf-1 -->
|
||||
<div *ng-if="currentHero">Hello, {{currentHero.firstName}}</div>
|
||||
<div *ngIf="currentHero">Hello, {{currentHero.firstName}}</div>
|
||||
<!-- #enddocregion NgIf-1 -->
|
||||
|
||||
<!-- #docregion NgIf-2 -->
|
||||
<!-- not displayed because nullHero is falsey.
|
||||
`nullHero.firstName` never has a chance to fail -->
|
||||
<div *ng-if="nullHero">Hello, {{nullHero.firstName}}</div>
|
||||
<div *ngIf="nullHero">Hello, {{nullHero.firstName}}</div>
|
||||
|
||||
<!-- Hero Detail is not in the DOM because isActive is false-->
|
||||
<hero-detail *ng-if="isActive"></hero-detail>
|
||||
<hero-detail *ngIf="isActive"></hero-detail>
|
||||
<!-- #enddocregion NgIf-2 -->
|
||||
|
||||
|
||||
<!-- NgIf binding with template (no *) -->
|
||||
|
||||
<template [ng-if]="currentHero">Add {{currentHero.firstName}} with template</template>
|
||||
<template [ngIf]="currentHero">Add {{currentHero.firstName}} with template</template>
|
||||
|
||||
<!-- Does not show because isActive is false! -->
|
||||
<div>Hero Detail removed from DOM (via template) because isActive is false</div>
|
||||
<template [ng-if]="isActive">
|
||||
<template [ngIf]="isActive">
|
||||
<hero-detail></hero-detail>
|
||||
</template>
|
||||
|
||||
|
@ -434,7 +421,7 @@ After setClasses(), the classes are "{{classDiv.className}}"
|
|||
<!-- NgSwitch binding -->
|
||||
<hr><h2>NgSwitch Binding</h2>
|
||||
|
||||
<fieldset #toe-picker (click)="null" >
|
||||
<fieldset #toePicker (click)="null" >
|
||||
<input type="radio" name="toes" value="Eenie">Eenie
|
||||
<input type="radio" name="toes" checked value="Meanie">Meanie
|
||||
<input type="radio" name="toes" value="Miney">Miney
|
||||
|
@ -443,12 +430,12 @@ After setClasses(), the classes are "{{classDiv.className}}"
|
|||
</fieldset>
|
||||
<!-- #docregion NgSwitch -->
|
||||
<div class="toe">You picked
|
||||
<span [ng-switch]="toeChoice(toePicker)">
|
||||
<template [ng-switch-when]="'Eenie'">Eenie</template>
|
||||
<template [ng-switch-when]="'Meanie'">Meanie</template>
|
||||
<template [ng-switch-when]="'Miney'">Miney</template>
|
||||
<template [ng-switch-when]="'Moe'">Moe</template>
|
||||
<template ng-switch-default>Other</template>
|
||||
<span [ngSwitch]="toeChoice(toePicker)">
|
||||
<template [ngSwitchWhen]="'Eenie'">Eenie</template>
|
||||
<template [ngSwitchWhen]="'Meanie'">Meanie</template>
|
||||
<template [ngSwitchWhen]="'Miney'">Miney</template>
|
||||
<template [ngSwitchWhen]="'Moe'">Moe</template>
|
||||
<template ngSwitchDefault>Other</template>
|
||||
</span>
|
||||
</div>
|
||||
<!-- #enddocregion NgSwitch -->
|
||||
|
@ -459,15 +446,15 @@ After setClasses(), the classes are "{{classDiv.className}}"
|
|||
|
||||
<div class="box">
|
||||
<!-- #docregion NgFor-1 -->
|
||||
<div *ng-for="#hero of heroes">{{hero.fullName}}</div>
|
||||
<div *ngFor="#hero of heroes">{{hero.fullName}}</div>
|
||||
<!-- #enddocregion NgFor-1 -->
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="box">
|
||||
<!-- *ng-for w/ hero-detail Component -->
|
||||
<!-- *ngFor w/ hero-detail Component -->
|
||||
<!-- #docregion NgFor-2 -->
|
||||
<hero-detail *ng-for="#hero of heroes" [hero]="hero"></hero-detail>
|
||||
<hero-detail *ngFor="#hero of heroes" [hero]="hero"></hero-detail>
|
||||
<!-- #enddocregion NgFor-2 -->
|
||||
</div>
|
||||
<br>
|
||||
|
@ -475,7 +462,7 @@ After setClasses(), the classes are "{{classDiv.className}}"
|
|||
<div class="box">
|
||||
<!-- Ex: 1 - Hercules Son of Zeus -->
|
||||
<!-- #docregion NgFor-3 -->
|
||||
<div *ng-for="#hero of heroes, #i=index">{{i+1}} - {{hero.fullName}}</div>
|
||||
<div *ngFor="#hero of heroes, #i=index">{{i+1}} - {{hero.fullName}}</div>
|
||||
<!-- #enddocregion NgFor-3 -->
|
||||
</div>
|
||||
<br>
|
||||
|
@ -485,28 +472,28 @@ After setClasses(), the classes are "{{classDiv.className}}"
|
|||
|
||||
<h3>NgIf expansion</h3>
|
||||
<!-- #docregion Template-1 -->
|
||||
<hero-detail *ng-if="currentHero" [hero]="currentHero"></hero-detail>
|
||||
<hero-detail *ngIf="currentHero" [hero]="currentHero"></hero-detail>
|
||||
<!-- #enddocregion Template-1 -->
|
||||
|
||||
<!-- #docregion Template-2 -->
|
||||
<template [ng-if]="currentHero">
|
||||
<template [ngIf]="currentHero">
|
||||
<hero-detail [hero]="currentHero"></hero-detail>
|
||||
</template>
|
||||
<!-- #enddocregion Template-2 -->
|
||||
|
||||
<h3>NgFor expansion</h3>
|
||||
<div class="box">
|
||||
<!-- ng-for w/ hero-detail Component and a template "attribute" directive -->
|
||||
<!-- ngFor w/ hero-detail Component and a template "attribute" directive -->
|
||||
<!-- #docregion Template-3 -->
|
||||
<hero-detail template="ng-for #hero of heroes" [hero]="hero"></hero-detail>
|
||||
<hero-detail template="ngFor #hero of heroes" [hero]="hero"></hero-detail>
|
||||
<!-- #enddocregion Template-3 -->
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="box">
|
||||
<!-- ng-for w/ hero-detail Component inside a template element -->
|
||||
<!-- ngFor w/ hero-detail Component inside a template element -->
|
||||
<!-- #docregion Template-4 -->
|
||||
<template ng-for #hero [ng-for-of]="heroes">
|
||||
<template ngFor #hero [ngForOf]="heroes">
|
||||
<hero-detail [hero]="hero"></hero-detail>
|
||||
</template>
|
||||
<!-- #enddocregion Template-4 -->
|
||||
|
@ -530,12 +517,12 @@ After setClasses(), the classes are "{{classDiv.className}}"
|
|||
<h4>Example Form</h4>
|
||||
<!-- #docregion var-form -->
|
||||
<!-- #docregion var-form-a -->
|
||||
<form (ng-submit)="onSubmit(theForm)" #theForm="form">
|
||||
<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm">
|
||||
<!-- #enddocregion var-form-a -->
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input class="form-control" required ng-control="firstName"
|
||||
[(ng-model)]="currentHero.firstName">
|
||||
<input class="form-control" required ngControl="firstName"
|
||||
[(ngModel)]="currentHero.firstName">
|
||||
</div>
|
||||
<!-- #docregion var-form-a -->
|
||||
<button type="submit" [disabled]="!theForm.form.valid">Submit</button>
|
||||
|
@ -545,7 +532,7 @@ After setClasses(), the classes are "{{classDiv.className}}"
|
|||
<br><br>
|
||||
|
||||
<!-- btn refers to the button element; show its disabled state -->
|
||||
<button #btn disabled [text-content]="'disabled by attribute: '+btn.disabled"></button>
|
||||
<button #btn disabled [textContent]="'disabled by attribute: '+btn.disabled"></button>
|
||||
|
||||
<!-- inputs and output -->
|
||||
<hr><h2>Inputs and Outputs</h2>
|
||||
|
@ -560,7 +547,7 @@ After setClasses(), the classes are "{{classDiv.className}}"
|
|||
</hero-detail>
|
||||
<!-- #enddocregion io-2 -->
|
||||
|
||||
<div my-click2 (my-click)="clicked2=$event">my-click2</div>
|
||||
<div myClick2 (myClick)="clicked2=$event">myClick2</div>
|
||||
{{clicked2}}
|
||||
|
||||
<!-- Pipes -->
|
||||
|
@ -632,7 +619,7 @@ See console log
|
|||
|
||||
<!-- #docregion elvis-4 -->
|
||||
<!--No hero, div not displayed, no error -->
|
||||
<div *ng-if="nullHero">The null hero's name is {{nullHero.firstName}}</div>
|
||||
<div *ngIf="nullHero">The null hero's name is {{nullHero.firstName}}</div>
|
||||
<!-- #enddocregion elvis-4 -->
|
||||
|
||||
<div>
|
|
@ -1,10 +1,10 @@
|
|||
import {Directive, Output, ElementRef, EventEmitter} from 'angular2/core';
|
||||
|
||||
@Directive({selector:'[my-click]'})
|
||||
@Directive({selector:'[mClick]'})
|
||||
export class MyClickDirective {
|
||||
// #docregion my-click-output-1
|
||||
// #docregion myClick-output-1
|
||||
@Output('myClick') clicks = new EventEmitter<string>();
|
||||
// #enddocregion my-click-output-1
|
||||
// #enddocregion myClick-output-1
|
||||
constructor(el: ElementRef){
|
||||
el.nativeElement
|
||||
.addEventListener('click', (event:Event) => {
|
||||
|
@ -13,14 +13,14 @@ export class MyClickDirective {
|
|||
}
|
||||
}
|
||||
|
||||
// #docregion my-click-output-2
|
||||
// #docregion myClick-output-2
|
||||
@Directive({
|
||||
// #enddocregion my-click-output-2
|
||||
selector:'[my-click2]',
|
||||
// #docregion my-click-output-2
|
||||
// #enddocregion myClick-output-2
|
||||
selector:'[myClick2]',
|
||||
// #docregion myClick-output-2
|
||||
outputs:['clicks:myClick']
|
||||
})
|
||||
// #enddocregion my-click-output-2
|
||||
// #enddocregion myClick-output-2
|
||||
export class MyClickDirective2 {
|
||||
clicks = new EventEmitter<string>();
|
||||
constructor(el: ElementRef){
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
@ -24,7 +24,7 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
>[Event Binding](#event-binding)
|
||||
|
||||
>[Two-way data binding with `NgModel`](#ng-model)
|
||||
>[Two-way data binding with `NgModel`](#ngModel)
|
||||
|
||||
>[Built-in Directives](#directives)
|
||||
|
||||
|
@ -36,14 +36,14 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
>[Template Expression Operators](#expression-operators)
|
||||
|
||||
[Live Example](/resources/live-examples/template-syntax/ts/src/plnkr.html).
|
||||
[Live Example](/resources/live-examples/template-syntax/ts/plnkr.html).
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## HTML
|
||||
HTML is the language of the Angular template. Our “[QuickStart](./quickstart.html)” application had a template that was pure HTML
|
||||
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'my-first-app')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'my-first-app')(format=".")
|
||||
:marked
|
||||
Almost all HTML syntax is valid template syntax. The `<script>` element is a notable exception; it is forbidden in order to eliminate any possibility of JavaScript injection attacks (in practice it is simply ignored).
|
||||
|
||||
|
@ -57,11 +57,11 @@ include ../../../../_includes/_util-fns
|
|||
:marked
|
||||
## Interpolation
|
||||
We met the double-curly braces of interpolation, `{{` and `}}`, early in our Angular education.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'first-interpolation')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'first-interpolation')(format=".")
|
||||
:marked
|
||||
We use interpolation to weave calculated strings into the text between HTML element tags and within attribute assignments.
|
||||
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'title+image')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'title+image')(format=".")
|
||||
:marked
|
||||
The material between the braces is often the name of a component property. Angular replaces that name with the
|
||||
string value of the corresponding component property. In this example, Angular evaluates the `title` and `heroImageUrl` properties
|
||||
|
@ -69,10 +69,10 @@ include ../../../../_includes/_util-fns
|
|||
|
||||
More generally, the material between the braces is a **template expression** that Angular first **evaluates**
|
||||
and then **converts to a string**. The following interpolation illustrates the point by adding the two numbers within braces:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'sum-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'sum-1')(format=".")
|
||||
:marked
|
||||
The expression can invoke methods of the host component as we do here with `getVal()`:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'sum-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'sum-2')(format=".")
|
||||
:marked
|
||||
Angular evaluates all expressions in double curly braces, converts the expression results to strings, and concatenates them with neighboring literal strings. Finally,
|
||||
it assigns this composite interpolated result to an **element or directive property**.
|
||||
|
@ -194,19 +194,19 @@ table
|
|||
|
||||
In the normal course of HTML development, we create a visual structure with HTML elements and
|
||||
we modify those elements by setting element attributes with string constants.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'img+button')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'img+button')(format=".")
|
||||
:marked
|
||||
We still create a structure and initialize attribute values this way in Angular templates.
|
||||
|
||||
Then we learn to create new elements with Components that encapsulate HTML
|
||||
and drop them into our templates as if they were native HTML elements
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'hero-detail-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'hero-detail-1')(format=".")
|
||||
:marked
|
||||
That’s “HTML Plus”.
|
||||
|
||||
Now we start to learn about data binding. The first binding we meet might look like this:
|
||||
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'disabled-button-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'disabled-button-1')(format=".")
|
||||
:marked
|
||||
We’ll get to that peculiar bracket notation in a moment. Looking beyond it,
|
||||
our intuition tells us that we’re binding to the button's `disabled` attribute and setting
|
||||
|
@ -293,7 +293,7 @@ table
|
|||
Component Property<br>
|
||||
Directive property
|
||||
td
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'property-binding-syntax-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-syntax-1')(format=".")
|
||||
tr
|
||||
td Event
|
||||
td.
|
||||
|
@ -301,61 +301,34 @@ table
|
|||
Component Event<br>
|
||||
Directive Event
|
||||
td
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'event-binding-syntax-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-syntax-1')(format=".")
|
||||
tr
|
||||
td Two-way
|
||||
td.
|
||||
Directive Event
|
||||
Property
|
||||
td
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', '2-way-binding-syntax-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', '2-way-binding-syntax-1')(format=".")
|
||||
tr
|
||||
td Attribute
|
||||
td.
|
||||
Attribute
|
||||
(the exception)
|
||||
td
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'attribute-binding-syntax-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'attribute-binding-syntax-1')(format=".")
|
||||
tr
|
||||
td Class
|
||||
td.
|
||||
<code>class</code> Property
|
||||
td
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'class-binding-syntax-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'class-binding-syntax-1')(format=".")
|
||||
tr
|
||||
td Style
|
||||
td.
|
||||
<code>style</code> Property
|
||||
td
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'style-binding-syntax-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'style-binding-syntax-1')(format=".")
|
||||
</div>
|
||||
:marked
|
||||
### Spelling target names
|
||||
.alert.is-critical.
|
||||
These are current rules. New rules will apply as of alpha 49
|
||||
:marked
|
||||
The target name – whether between punctuation or after a prefix -
|
||||
should be spelled with **lowercase**. Although we can spell it in mixed-case,
|
||||
Angular sees it only after it has been forced to lowercase.
|
||||
|
||||
That’s a problem when we need to reference a property with a mixed-case name.
|
||||
HTML elements have many mixed-case properties: `innerHTML`, `textContent`, `className` to name a few.
|
||||
Our custom elements (e.g., `<hero-detail>`) may have mixed-case property names
|
||||
(`HeroDetailComponent.isActive`).
|
||||
|
||||
We’ll be tempted to write bindings that target such properties in mixed-case:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'bad-mixed-case')(format=".")
|
||||
:marked
|
||||
Try it and the entire display goes blank. A debugger reveals the error:
|
||||
code-example(format="", language="html").
|
||||
Template parse errors:
|
||||
Can't bind to 'textcontent' since it isn't a known native property in ...
|
||||
:marked
|
||||
Notice that it forced mixed-case "text**C**ontent" to lowercase "text**c**ontent".
|
||||
|
||||
The solution is to write the target property name in **lower kebab-case** (aka *dash-case*),
|
||||
all lowercase with a dash before each former uppercase letter. We can correct the example as follows:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'kebab-case')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
@ -371,34 +344,34 @@ code-example(format="", language="html").
|
|||
|
||||
The most common Property Binding sets an element property to a component property value as when
|
||||
we bind the source property of an image element to the component’s `heroImageUrl` property.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'property-binding-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-1')(format=".")
|
||||
:marked
|
||||
… or disable a button when the component says that it `isUnchanged`.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'property-binding-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-2')(format=".")
|
||||
:marked
|
||||
… or set a property of a directive
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'property-binding-3')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-3')(format=".")
|
||||
:marked
|
||||
… or set the model property of a custom component (a great way
|
||||
for parent and child components to communicate)
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'property-binding-4')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-4')(format=".")
|
||||
:marked
|
||||
People often describe Property Binding as “one way data binding” because it can flow a value in one direction, from a component’s data property to an element property.
|
||||
|
||||
### Binding Target
|
||||
A name between enclosing square brackets identifies the target property. The target property in this example is the image element’s `src` property.
|
||||
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'property-binding-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-1')(format=".")
|
||||
:marked
|
||||
Some developers prefer the `bind-` prefix alternative, known as the “canonical form”:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'property-binding-5')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-5')(format=".")
|
||||
:marked
|
||||
The target name is always the name of a property, even when it appears to be the name of something else. We see `src` and may think it’s the name of an attribute. No. It’s the name of an image element property.
|
||||
|
||||
Element properties may be the more common targets,
|
||||
but Angular looks first to see if the name is a property of a known directive
|
||||
as it is in the following example:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'property-binding-3')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-3')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
@ -419,11 +392,11 @@ code-example(format="", language="html").
|
|||
The template expression should evaluate to a value of the type expected by the target property. Most native element properties do expect a string. The image `src` should be set to a string, an URL for the resource providing the image. On the other hand, the `disabled` property of a button expects a Boolean value so our expression should evaluate to `true` or `false`.
|
||||
|
||||
The `hero` property of the `HeroDetail` component expects a `Hero` object which is exactly what we’re sending in the Property Binding:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'property-binding-4')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-4')(format=".")
|
||||
:marked
|
||||
This is good news for the Angular developer.
|
||||
If `hero` were an attribute we could not set it to a `Hero` object.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'property-binding-6')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-6')(format=".")
|
||||
:marked
|
||||
We can't set an attribute to an object. We can only set it to a string.
|
||||
Internally, the attribute may be able to convert that string to an object before setting the like-named element property.
|
||||
|
@ -435,7 +408,7 @@ code-example(format="", language="html").
|
|||
### Property Binding or Interpolation?
|
||||
We often have a choice between Interpolation and Property Binding. The following binding pairs do the same thing
|
||||
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'property-binding-v-interpolation')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'property-binding-vs-interpolation')(format=".")
|
||||
:marked
|
||||
Interpolation is actually a convenient alternative for Property Binding in many cases.
|
||||
In fact, Angular translates those Interpolations into the corresponding Property Bindings
|
||||
|
@ -471,7 +444,7 @@ code-example(format="", language="html").
|
|||
|
||||
We become painfull aware of this fact when we try to write something like this:
|
||||
code-example(language="html").
|
||||
<tr><td colspan="{{1+1}}">Three-Four</td></tr>
|
||||
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
|
||||
:marked
|
||||
… and get the error:
|
||||
code-example(format="", language="html").
|
||||
|
@ -489,7 +462,7 @@ code-example(format="", language="html").
|
|||
followed by the name of the attribute and then set it with an expression that resolves to a string.
|
||||
|
||||
Here we bind `[attr.colspan]` to a calulated value:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'attrib-binding-colspan')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'attrib-binding-colspan')(format=".")
|
||||
:marked
|
||||
Here's how the table renders:
|
||||
<table border="1px">
|
||||
|
@ -499,7 +472,7 @@ code-example(format="", language="html").
|
|||
|
||||
One of the primary use cases for the Attribute Binding
|
||||
is to set aria attributes as we see in this example
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'attrib-binding-aria')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'attrib-binding-aria')(format=".")
|
||||
:marked
|
||||
### Class Binding
|
||||
|
||||
|
@ -511,20 +484,20 @@ code-example(format="", language="html").
|
|||
|
||||
In the following examples we see how to add and remove the application's "special" class
|
||||
with class bindings. We start by setting the attribute without binding:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'class-binding-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'class-binding-1')(format=".")
|
||||
:marked
|
||||
We 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/src/app/app.component.html', 'class-binding-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'class-binding-2')(format=".")
|
||||
:marked
|
||||
Finally, we bind to a specific class name.
|
||||
Angular adds the class when the template expression evaluates to something truthy and
|
||||
removes the class name when the expression is falsey.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'class-binding-3')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'class-binding-3')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
While this is a fine way to toggle a single class name,
|
||||
we generally prefer the [NgClass directive](#ng-class) 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
|
||||
### Style Binding
|
||||
|
@ -535,15 +508,15 @@ code-example(format="", language="html").
|
|||
Instead of an element property between brackets, we start with the key word `style`
|
||||
followed by the name of a CSS style property: `[style.style-property]`.
|
||||
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'style-binding-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'style-binding-1')(format=".")
|
||||
:marked
|
||||
Some Style Binding styles have unit extension. Here we conditionally set the `font-size` in “em” and “%” units .
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'style-binding-2')(format=".")
|
||||
Some Style Binding styles have unit extension. Here we conditionally set the `fontSize` in “em” and “%” units .
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'style-binding-2')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
While this is a fine way to set a single style,
|
||||
we generally prefer the [NgStyle directive](#ng-style) when setting several inline styles at the same time.
|
||||
we generally prefer the [NgStyle directive](#ngStyle) when setting several inline styles at the same time.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -559,20 +532,20 @@ code-example(format="", language="html").
|
|||
We declare our interest in user actions through Angular Event Binding.
|
||||
|
||||
The following Event Binding listens for the button’s click event and calls the component's `onSave()` method:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'event-binding-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-1')(format=".")
|
||||
:marked
|
||||
Event Binding syntax consists of a target event on the left of an equal sign and a template expression on the right: `(click)="onSave()"`
|
||||
|
||||
### Binding target
|
||||
A **name between enclosing parentheses** identifies the target event. In the following example, the target is the button’s click event.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'event-binding-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-1')(format=".")
|
||||
:marked
|
||||
Some developers prefer the `on-` prefix alternative, known as the “canonical form”:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'event-binding-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-2')(format=".")
|
||||
:marked
|
||||
Element events may be the more common targets, but Angular looks first to see if the name matches an event property
|
||||
of a known directive as it does in the following example:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'event-binding-3')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-3')(format=".")
|
||||
:marked
|
||||
If the name fails to match an element event or an output property of a known directive,
|
||||
Angular reports an “unknown directive” error.
|
||||
|
@ -593,7 +566,7 @@ code-example(format="", language="html").
|
|||
[DOM event object]( https://developer.mozilla.org/en-US/docs/Web/Events) with properties such as `target` and `target.value`.
|
||||
|
||||
Consider this example:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'without-NgModel')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'without-NgModel')(format=".")
|
||||
: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.
|
||||
When the user makes changes, the `input` event is raised, and the binding executes the expression within a context that includes the DOM event object, `$event`.
|
||||
|
@ -602,12 +575,12 @@ code-example(format="", language="html").
|
|||
|
||||
If the event belongs to a directive (remember: components are directives), `$event` has whatever shape the directive chose to produce.
|
||||
Consider a `HeroDetailComponent` that produces `deleted` events with an `EventEmitter`.
|
||||
+makeExample('template-syntax/ts/src/app/hero-detail.component.ts', 'deleted', 'HeroDetailComponent.ts (excerpt)')(format=".")
|
||||
+makeExample('template-syntax/ts/app/hero-detail.component.ts', 'deleted', 'HeroDetailComponent.ts (excerpt)')(format=".")
|
||||
:marked
|
||||
When something invokes the `onDeleted()` method, we "emit" a `Hero` object.
|
||||
|
||||
Now imagine a parent component that listens for that event with an Event Binding.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'event-binding-to-component')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-to-component')(format=".")
|
||||
:marked
|
||||
The event binding surfaces the *hero-to-delete* emitted by `HeroDetail` to the expression via the `$hero` variable.
|
||||
We pass it along to the parent `onHeroDeleted()` method.
|
||||
|
@ -622,7 +595,7 @@ code-example(format="", language="html").
|
|||
|
||||
### Event bubbling and propagation
|
||||
Angular invokes the event-handling expression if the event is raised by the current element or one of its child elements.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'event-binding-bubbling')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-bubbling')(format=".")
|
||||
:marked
|
||||
Many DOM events, both [native](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Overview_of_Events_and_Handlers ) and [custom](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events ), “bubble” events up their ancestor tree of DOM elements until an event handler along the way prevents further propagation.
|
||||
|
||||
|
@ -638,65 +611,65 @@ code-example(format="", language="html").
|
|||
Event propagation stops if the binding expression returns a falsey value (as does a method with no return value).
|
||||
Clicking the button in this next example triggers a save;
|
||||
the click doesn't make it to the outer `<div>` so it's save is not called:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'event-binding-no-propagation')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-no-propagation')(format=".")
|
||||
:marked
|
||||
Propagation continues if the expression returns a truthy value. The click is heard both by the button
|
||||
and the outer `<div>`, causing a double save:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'event-binding-propagation')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-propagation')(format=".")
|
||||
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
<a name="ng-model"></a>
|
||||
<a name="ngModel"></a>
|
||||
## 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.
|
||||
|
||||
The `NgModel` directive serves that purpose as seen in this example:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgModel-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgModel-1')(format=".")
|
||||
:marked
|
||||
If we prefer the “canonical” prefix form to the punctuation form, we can write
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgModel-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgModel-2')(format=".")
|
||||
:marked
|
||||
There’s a story behind this construction, a story that builds on the Property and Event Binding techniques we learned previously.
|
||||
|
||||
We could have achieved the same result as `NgModel` with separate bindings to the
|
||||
the `<input>` element's `value` property and `input` event.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'without-NgModel')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'without-NgModel')(format=".")
|
||||
:marked
|
||||
That’s cumbersome. Who can remember what element property to set and what event reports user changes?
|
||||
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?
|
||||
|
||||
That `ng-model` directive hides these onerous details. It wraps the element’s `value` property, listens to the `input` event,
|
||||
That `ngModel` directive hides these onerous details. It wraps the element’s `value` property, listens to the `input` event,
|
||||
and raises its own event with the changed text ready for us to consume.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgModel-3')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgModel-3')(format=".")
|
||||
:marked
|
||||
That’s an improvement. It should be better.
|
||||
|
||||
We shouldn't have to mention the data property twice. Angular should be able to read the component’s data property and set it
|
||||
with a single declaration — which it can with the `[{ }]` syntax:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgModel-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgModel-1')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Internally, Angular maps the term, `ng-model`, to an `ng-model` input property and an
|
||||
`ng-model-change` output property.
|
||||
Internally, Angular maps the term, `ngModel`, to an `ngModel` input property and an
|
||||
`ngModelChange` output property.
|
||||
That’s a specific example of a more general pattern in which it matches `[(x)]` to an `x` input property
|
||||
for Property Binding and an `x-change` output property for Event Binding.
|
||||
|
||||
We can write our own two-way binding directive that follows this pattern if we're ever in the mood to do so.
|
||||
:marked
|
||||
Is `[{ng-model}]` 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?
|
||||
|
||||
Well `NgModel` can only set the target property.
|
||||
What if we need to do something more or something different when the user changes the value?
|
||||
|
||||
Let's try something silly like forcing the input value to uppercase.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgModel-4')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgModel-4')(format=".")
|
||||
:marked
|
||||
Here are all variations in action, including the uppercase version:
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/template-syntax/ng-model-anim.gif' alt="NgModel variations")
|
||||
img(src='/resources/images/devguide/template-syntax/ngModel-anim.gif' alt="NgModel variations")
|
||||
:marked
|
||||
|
||||
.l-main-section
|
||||
|
@ -710,7 +683,7 @@ figure.image-display
|
|||
We don’t need many of those directives in Angular 2.
|
||||
Quite often we can achieve the same results with the more capable and expressive Angular 2 binding system.
|
||||
Why create a directive to handle a click when we can write a simple binding such as this?
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'event-binding-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'event-binding-2')(format=".")
|
||||
:marked
|
||||
We still benefit from directives that simplify complex tasks.
|
||||
Angular still ships with built-in directives; just not as many.
|
||||
|
@ -718,7 +691,7 @@ figure.image-display
|
|||
|
||||
In this segment we review some of the most frequently used built-in directivest.
|
||||
|
||||
<a id="ng-class"></a>
|
||||
<a id="ngClass"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
### NgClass
|
||||
|
@ -728,7 +701,7 @@ figure.image-display
|
|||
We can bind to `NgClass` to add or remove several classes simultaneously.
|
||||
|
||||
We prefer to use a [Class Binding](#class-binding) to add or remove a *single* class.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'class-binding-3a')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'class-binding-3a')(format=".")
|
||||
:marked
|
||||
The `NgClass` directive may be the better choice
|
||||
when we want to add or remove *many* classes at the same time.
|
||||
|
@ -736,12 +709,12 @@ figure.image-display
|
|||
Our favorite way to apply `NgClass` is by binding it to a key:value control object. Each key of the object is a class name and its value is `true` if the class should be added, `false` if it should be removed.
|
||||
|
||||
Consider a component method such as `setClasses` that returns its approval of two class names:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.ts', 'setClasses')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.ts', 'setClasses')(format=".")
|
||||
:marked
|
||||
Now add an `NgClass` property binding to call it like this
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgClass-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgClass-1')(format=".")
|
||||
|
||||
<a id="ng-style"></a>
|
||||
<a id="ngStyle"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
### NgStyle
|
||||
|
@ -749,7 +722,7 @@ figure.image-display
|
|||
We bind to `NgStyle` to set many inline styles simultaneously.
|
||||
|
||||
We prefer to use a [Style Binding](#style-binding) to set a *single* style value.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgStyle-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgStyle-1')(format=".")
|
||||
:marked
|
||||
The `NgStyle` directive may be the better choice
|
||||
when we want to set *many* inline styles at the same time.
|
||||
|
@ -758,29 +731,29 @@ figure.image-display
|
|||
Each key of the object is a style name and its value is whatever is appropriate for that style.
|
||||
|
||||
Consider a component method such as `setStyles` that returns an object defining three styles:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.ts', 'setStyles')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.ts', 'setStyles')(format=".")
|
||||
:marked
|
||||
Now add an `NgStyle` property binding to call it like this
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgStyle-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgStyle-2')(format=".")
|
||||
|
||||
<a id="ng-if"></a>
|
||||
<a id="ngIf"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
### NgIf
|
||||
We can add an element sub-tree (an element and its children) to the DOM by binding an `NgIf` directive to a truthy expression.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgIf-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgIf-1')(format=".")
|
||||
|
||||
.alert.is-critical
|
||||
:marked
|
||||
The leading asterisk (\*) in front of `ng-if` is a critical part of this syntax.
|
||||
The leading asterisk (\*) in front of `ngIf` is a critical part of this syntax.
|
||||
See the section below on [\* and <template>](#star-template).
|
||||
:marked
|
||||
Binding to a falsey expression removes the element sub-tree from the DOM.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgIf-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgIf-2')(format=".")
|
||||
:marked
|
||||
#### Visibility and NgIf are not the same
|
||||
We can show and hide an element sub-tree (the element and its children) with a [Class](#class-binding) or a [Style](#style-binding) binding:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgIf-3')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgIf-3')(format=".")
|
||||
:marked
|
||||
Hiding a sub-tree is quite different from excluding a sub-tree with `NgIf`.
|
||||
|
||||
|
@ -796,7 +769,7 @@ figure.image-display
|
|||
The show/hide technique is probably fine for small element trees.
|
||||
We should be wary when hiding large trees; `NgIf` may be the safer choice. Always measure before leaping to conclusions.
|
||||
|
||||
<a id="ng-switch"></a>
|
||||
<a id="ngSwitch"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
### NgSwitch
|
||||
|
@ -805,7 +778,7 @@ figure.image-display
|
|||
Angular only puts the *selected* element tree into the DOM.
|
||||
|
||||
Here’s an example:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgSwitch')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgSwitch')(format=".")
|
||||
:marked
|
||||
We bind the parent `NgSwitch` element to an expression returning a “switch value”. The value is a string in this example but it can be a value of any type.
|
||||
|
||||
|
@ -816,11 +789,11 @@ figure.image-display
|
|||
If the template’s “match value” equals the “switch value”, Angular adds the template’s sub-tree to the DOM. If no template is a match and there is a default template, Angular adds the default template’s sub-tree to the DOM. Angular removes and destroys the sub-tree’s of all other templates.
|
||||
|
||||
There are three related directives at work here.
|
||||
1. `ng-switch` - bound to an expression that returns the switch value.
|
||||
1. `ng-switch-when` - bound to an expression returning a match value.
|
||||
1. `ng-switch-default` - a marker attribute on the default template.
|
||||
1. `ngSwitch` - bound to an expression that returns the switch value.
|
||||
1. `ngSwitchWhen` - bound to an expression returning a match value.
|
||||
1. `ngSwitchDefault` - a marker attribute on the default template.
|
||||
|
||||
<a id="ng-for"></a>
|
||||
<a id="ngFor"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
### NgFor
|
||||
|
@ -830,22 +803,22 @@ figure.image-display
|
|||
We tell Angular to use that block as a template for rendering each item in the list.
|
||||
|
||||
Here is an example of `NgFor` applied to a simple `<div>`.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgFor-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgFor-1')(format=".")
|
||||
:marked
|
||||
We can apply an `NgFor` to a component element as well as we do in this example:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgFor-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgFor-2')(format=".")
|
||||
|
||||
.alert.is-critical
|
||||
:marked
|
||||
The leading asterisk (\*) in front of `ng-for` is a critical part of this syntax.
|
||||
The leading asterisk (\*) in front of `ngFor` is a critical part of this syntax.
|
||||
See the section below on [\* and <template>](#star-template).
|
||||
:marked
|
||||
The text assigned to `*ng-for` is the instruction that guides the repeater process.
|
||||
The text assigned to `*ngFor` is the instruction that guides the repeater process.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
#### NgFor Micro-syntax
|
||||
The string assigned to `*ng-for` is not a [template expression](#template-expressions).
|
||||
The string assigned to `*ngFor` is not a [template expression](#template-expressions).
|
||||
It’s a little language of its own called a “micro-syntax” that Angular interprets. In this example it means:
|
||||
|
||||
>*Take each hero in the `heroes` array, store it in the local `hero` variable, and make it available to the templated HTML
|
||||
|
@ -854,7 +827,7 @@ figure.image-display
|
|||
Angular translates this instruction into a new set of elements and bindings.
|
||||
We’ll talk about this in the next section about templates.
|
||||
:marked
|
||||
In our two examples, the `ng-for` directive iterates over the `heroes` array returned by the parent component’s `heroes` property
|
||||
In our two examples, the `ngFor` directive iterates over the `heroes` array returned by the parent component’s `heroes` property
|
||||
and stamps out instances of the element to which it is applied.
|
||||
Angular creates a fresh instance of the template for each hero in the array.
|
||||
|
||||
|
@ -864,12 +837,12 @@ figure.image-display
|
|||
or we can pass it in a binding to a component element as we're doing with `hero-detail`.
|
||||
|
||||
#### NgFor with index
|
||||
The `ng-for` directive supports an optional index that increases from 0 to the length of the array for each iteration.
|
||||
The `ngFor` directive supports an optional index that increases from 0 to the length of the array for each iteration.
|
||||
We can capture that in another local template variable (`i`) and use it in our template too.
|
||||
|
||||
This next example stamps out rows that display like "1 - Hercules Son of Zeus":
|
||||
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgFor-3')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgFor-3')(format=".")
|
||||
:marked
|
||||
|
||||
<a name="star-template"></a>
|
||||
|
@ -877,31 +850,31 @@ figure.image-display
|
|||
.l-main-section
|
||||
:marked
|
||||
## * and <template>
|
||||
When we reviewed the `ng-for` and `ng-if` built-in directives we called out an oddity of the syntax, the asterisk (*) that appears before the directive name.
|
||||
When we reviewed the `ngFor` and `ngIf` built-in directives we called out an oddity of the syntax, the asterisk (*) that appears before the directive name.
|
||||
|
||||
This is a bit of “**syntactic sugar**” that makes it easier to read and write directives that modify HTML layout
|
||||
with the help of templates.
|
||||
`NgFor`, `NgIf`, and `NgSwitch` all add and remove element subtrees that are wrapped in `<template>` tags.
|
||||
|
||||
With the [NgSwitch](#ng-switch) directive we always write the `<template>` tags explicitly as we saw [above](#ng-switch).
|
||||
With the [NgSwitch](#ngSwitch) directive we always write the `<template>` tags explicitly as we saw [above](#ngSwitch).
|
||||
There isn’t much choice; we define a different template for each switch choice
|
||||
and let the directive render the template that matches the switch value.
|
||||
|
||||
[NgFor](#ng-for) and [NgIf](#ng-if) only need one template,
|
||||
[NgFor](#ngFor) and [NgIf](#ngIf) only need one template,
|
||||
the *template-to-repeat* and the *template-to-include* respectively.
|
||||
|
||||
The (\*) prefix syntax is a convenient way for developers to skip the `<template>` wrapper tags and
|
||||
focus directly on the HTML element to repeat or include.
|
||||
Angular sees the (*) and expands the HTML into the `<template>` tags for us.
|
||||
|
||||
### Expanding `*ng-if`
|
||||
### Expanding `*ngIf`
|
||||
We can do that ourselves if we wish. Instead of writing ...
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'Template-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'Template-1')(format=".")
|
||||
:marked
|
||||
… we can write:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'Template-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'Template-2')(format=".")
|
||||
:marked
|
||||
Notice the (\*) is gone and we have a [Property Binding](#property-binding) to the `ng-if`
|
||||
Notice the (\*) is gone and we have a [Property Binding](#property-binding) to the `ngIf`
|
||||
directive, applied in this case to the `<template>` rather than
|
||||
the application’s `hero-detail` component.
|
||||
|
||||
|
@ -912,26 +885,26 @@ figure.image-display
|
|||
.callout.is-critical
|
||||
header Remember the brackets!
|
||||
:marked
|
||||
Don’t make the mistake of writing `ng-if="currentHero"`!
|
||||
That syntax assigns the *string* value, "currentHero", to `ng-if`.
|
||||
In JavaScript a non-empty string is a truthy value so `ng-if` would always be
|
||||
Don’t make the mistake of writing `ngIf="currentHero"`!
|
||||
That syntax assigns the *string* value, "currentHero", to `ngIf`.
|
||||
In JavaScript a non-empty string is a truthy value so `ngIf` would always be
|
||||
`true` and Angular will always display the `hero-detail`
|
||||
… even when there is no `currentHero`!
|
||||
|
||||
:marked
|
||||
### Expanding `*ng-for`
|
||||
A similar transformation applies to `*ng-for`. We can "de-sugar" the syntax ourselves and go from ...
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'NgFor-2')(format=".")
|
||||
### Expanding `*ngFor`
|
||||
A similar transformation applies to `*ngFor`. We can "de-sugar" the syntax ourselves and go from ...
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'NgFor-2')(format=".")
|
||||
:marked
|
||||
... to
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'Template-3')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'Template-3')(format=".")
|
||||
:marked
|
||||
... and from there to
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'Template-4')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'Template-4')(format=".")
|
||||
:marked
|
||||
This is a bit more complex than `NgIf` because a repeater has more moving parts to configure.
|
||||
In this case, we have to remember the `NgForOf` directive that identifies the list.
|
||||
It should be clear why we prefer the `*ng-for` syntax to writing out this expanded HTML ourselves.
|
||||
It should be clear why we prefer the `*ngFor` syntax to writing out this expanded HTML ourselves.
|
||||
|
||||
<a id="local-vars"></a>
|
||||
.l-main-section
|
||||
|
@ -941,11 +914,11 @@ figure.image-display
|
|||
A **local template variable** is a vehicle for moving data across element lines.
|
||||
|
||||
We've seen the `#hero` local template variable several times in this chapter,
|
||||
most prominently when writing [NgFor](#ng-for) repeaters.
|
||||
most prominently when writing [NgFor](#ngFor) repeaters.
|
||||
|
||||
In the [* and <templates>](#star-template) segment we learned how Angular expands
|
||||
an `*ng-for` on a component tag into a `<template>` that wraps the component.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'Template-4')(format=".")
|
||||
an `*ngFor` on a component tag into a `<template>` that wraps the component.
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'Template-4')(format=".")
|
||||
:marked
|
||||
The (#) prefix character in front of "hero" means that we're defining a `hero` variable.
|
||||
.l-sub-section
|
||||
|
@ -965,7 +938,7 @@ figure.image-display
|
|||
any of its child elements.
|
||||
|
||||
Here are two other examples of creating and consuming a local template variable:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'var-phone')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'var-phone')(format=".")
|
||||
:marked
|
||||
### How it gets its value
|
||||
The value assigned to the variable depends upon the context.
|
||||
|
@ -985,11 +958,11 @@ figure.image-display
|
|||
|
||||
The HTML for a form can be quite involved as we saw in the [Forms](forms.html) chapter.
|
||||
The following is a *simplified* example — and it's not simple at all.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'var-form')
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'var-form')
|
||||
:marked
|
||||
A local template variable, `theForm`, appears three times in this example, separated
|
||||
by a large amount of HTML.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'var-form-a')
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'var-form-a')
|
||||
:marked
|
||||
What is the value of `theForm`?
|
||||
|
||||
|
@ -1031,12 +1004,12 @@ figure.image-display
|
|||
|
||||
In the following example, `iconUrl` and `onSave` are members of the `AppComponent`
|
||||
referenced within template expressions to the *right* of the (=).
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'io-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'io-1')(format=".")
|
||||
:marked
|
||||
They are *neither inputs nor outputs* of `AppComponent`. They are data sources for their bindings.
|
||||
|
||||
Now look at the `HeroDetailComponent` when it is the **target of a binding**.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'io-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'io-2')(format=".")
|
||||
:marked
|
||||
Both `HeroDetail.hero` and `HeroDetail.deleted` are on the **left side** of binding expressions.
|
||||
`HeroDetail.hero` is the target of a Property Binding. `HeroDetail.deleted` is the target of an Event Binding.
|
||||
|
@ -1050,13 +1023,13 @@ figure.image-display
|
|||
|
||||
When we peek inside `HeroDetailComponent` we see that these properties are marked
|
||||
with decorators as input and output properties.
|
||||
+makeExample('template-syntax/ts/src/app/hero-detail.component.ts', 'input-output-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/hero-detail.component.ts', 'input-output-1')(format=".")
|
||||
:marked
|
||||
.l-sub-section
|
||||
:marked
|
||||
Alternatively, we can identify them in the `inputs` and `outputs` arrays
|
||||
of the directive metadata as seen in this example:
|
||||
+makeExample('template-syntax/ts/src/app/hero-detail.component.ts', 'input-output-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/hero-detail.component.ts', 'input-output-2')(format=".")
|
||||
<br>
|
||||
:marked
|
||||
We can specify an input/output property with a decorator or in one the metadata arrays — but not both.
|
||||
|
@ -1075,12 +1048,12 @@ figure.image-display
|
|||
|
||||
Fortunately, we can alias the internal name to meet the conventional needs of the directive's consumer.
|
||||
We alias in decorator syntax like this:
|
||||
+makeExample('template-syntax/ts/src/app/my-click.directive.ts', 'my-click-output-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/my-click.directive.ts', 'my-click-output-1')(format=".")
|
||||
.l-sub-section
|
||||
:marked
|
||||
The equivalent aliasing with the `outputs` array requires a colon-delimited string with
|
||||
the internal property name on the left and the public name on the right:
|
||||
+makeExample('template-syntax/ts/src/app/my-click.directive.ts', 'my-click-output-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/my-click.directive.ts', 'my-click-output-2')(format=".")
|
||||
|
||||
<a id="expression-operators"></a>
|
||||
.l-main-section
|
||||
|
@ -1096,30 +1069,30 @@ figure.image-display
|
|||
Angular [Pipes](./pipes.html) are a good choice for small transformations such as these.
|
||||
Pipes are simple functions that accept an input value and return a transformed value.
|
||||
They are easy to apply within template expressions using the **pipe operator ( | )**:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'pipes-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'pipes-1')(format=".")
|
||||
:marked
|
||||
The pipe operator passes the result of an expression on the left to a pipe function on the right.
|
||||
|
||||
We can chain expressions through multiple pipes
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'pipes-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'pipes-2')(format=".")
|
||||
:marked
|
||||
And we can configure them too:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'pipes-3')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'pipes-3')(format=".")
|
||||
:marked
|
||||
The `json` pipe is particular helpful for debugging our bindings:
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'pipes-json')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'pipes-json')(format=".")
|
||||
:marked
|
||||
<a id="elvis"></a>
|
||||
### The Elvis Operator ( ?. ) and null property paths
|
||||
|
||||
The Angular **“Elvis” operator ( ?. )** is a fluent and convenient way to guard against null and undefined values in property paths.
|
||||
Here it is, protecting against a view render failure if the `currentHero` is null.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'elvis-2')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'elvis-2')(format=".")
|
||||
:marked
|
||||
Let’s elaborate on the problem and this particular solution.
|
||||
|
||||
What happens when the following data bound `title` property is null?
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'elvis-1')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'elvis-1')(format=".")
|
||||
:marked
|
||||
The view still renders but the displayed value is blank; we see only "`The title is`" with nothing after it.
|
||||
That is reasonable behavior. At least the app doesn't crash.
|
||||
|
@ -1149,12 +1122,12 @@ code-example(format="" language="html").
|
|||
|
||||
Unfortunately, our app crashes when the `currentHero` is null.
|
||||
|
||||
We could code around that problem with [NgIf](#ng-if)
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'elvis-4')(format=".")
|
||||
We could code around that problem with [NgIf](#ngIf)
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'elvis-4')(format=".")
|
||||
:marked
|
||||
Or we could try to chain parts of the property path with `&&`, knowing that the expression bails out
|
||||
when it encounters the first null.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'elvis-5')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'elvis-5')(format=".")
|
||||
:marked
|
||||
These approaches have merit but they can be cumbersome, especially if the property path is long.
|
||||
Imagine guarding against a null somewhere in a long property path such as `a.b.c.d`.
|
||||
|
@ -1162,7 +1135,7 @@ code-example(format="" language="html").
|
|||
The Angular **“Elvis” operator ( ?. )** is a more fluent and convenient way to guard against nulls in property paths.
|
||||
The expression bails out when it hits the first null value.
|
||||
The display is blank but the app keeps rolling and there are no errors.
|
||||
+makeExample('template-syntax/ts/src/app/app.component.html', 'elvis-6')(format=".")
|
||||
+makeExample('template-syntax/ts/app/app.component.html', 'elvis-6')(format=".")
|
||||
:marked
|
||||
It works perfectly with long property paths too:
|
||||
```
|
||||
|
|
Loading…
Reference in New Issue