學(xué)習(xí)資料來(lái)自 Angular.cn 與 Angular.io。
模板語(yǔ)法
在 Angular 中,組件扮演著控制器或視圖模型的角色,模板則扮演視圖的角色。
模板中的 HTML
HTML 是 Angular 模板的語(yǔ)言。
<h1>Hello Angular</h1>
為防范腳本注入攻擊的風(fēng)險(xiǎn),<script> 元素被禁用了(忽略了)。更多內(nèi)容參見(jiàn)安全。
<html>、<body> 和 <base> 元素在此沒(méi)有使用的意義。
插值表達(dá)式 (Interpolation) {{...}}
插值表達(dá)式可以把計(jì)算后的字符串插入到 HTML 元素標(biāo)簽內(nèi)的文本或?qū)?biāo)簽的屬性進(jìn)行賦值。
一般來(lái)說(shuō),括號(hào)間的素材是一個(gè)模板表達(dá)式,Angular 先對(duì)它求值,再把它轉(zhuǎn)換成字符串。
<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}</p>
模板表達(dá)式 (Template expressions)
模板表達(dá)式產(chǎn)生一個(gè)值。Angular 執(zhí)行這個(gè)表達(dá)式,并把它賦值給綁定目標(biāo)的屬性,這個(gè)綁定目標(biāo)可能是 HTML 元素、組件或指令。
模板表達(dá)式不支持的內(nèi)容包括:
- 賦值 (
=,+=,-=, ...) -
new運(yùn)算符 - 使用
;或,的鏈?zhǔn)奖磉_(dá)式 - 自增或自減操作符 (
++和--) - 不支持位運(yùn)算
|和& - 具有新的模板表達(dá)式運(yùn)算符,比如
|和?. - 不能引用全局命名空間中的任何東西。 不能引用
window或document。不能調(diào)用console.log或Math.max。
表達(dá)式上下文 (Expression context)
典型的表達(dá)式上下文是組件實(shí)例。
The expression context is typically the component instance.
下例中 title 和 isUnchanged 均為 AppComponent 的屬性。
{{title}}
<span [hidden]="isUnchanged">changed</span>
表達(dá)式的上下文可以包括組件之外的對(duì)象。 比如模板輸入變量 (let hero)和模板引用變量(#heroInput)就是備選的上下文對(duì)象之一。
<div *ngFor="let hero of heroes">{{hero.name}}</div>
<input #heroInput> {{heroInput.value}}
用術(shù)語(yǔ)來(lái)說(shuō),表達(dá)式上下文由模版變量,指令上下文對(duì)象(如果存在)和組件成員混合而成。如果所引用的名稱在上述命名空間中有沖突,那么將會(huì)按照模板變量,指令上下文和組件成員的順序優(yōu)先選取。
上例展示了一個(gè)命名沖突。組件有一個(gè) hero 屬性,*ngFor 定義了一個(gè) hero 模版變量。{{hero.name}} 中的 hero 指的是模板變量,而非組件屬性。
模板表達(dá)式被局限于只能訪問(wèn)來(lái)自表達(dá)式上下文中的成員。
表達(dá)式指南 (Expression guidelines)
模板表達(dá)式能成就或毀掉一個(gè)應(yīng)用。應(yīng)遵循原則:
1. 沒(méi)有可見(jiàn)的副作用
模板表達(dá)式除了目標(biāo)屬性的值以外,不應(yīng)該改變應(yīng)用的任何狀態(tài)。這條規(guī)則是 Angular “單向數(shù)據(jù)流”策略的基礎(chǔ)。在一次單獨(dú)的渲染過(guò)程中,視圖應(yīng)該總是穩(wěn)定的。
2. 執(zhí)行迅速
Angular 執(zhí)行模板表達(dá)式比我們想象的頻繁。當(dāng)計(jì)算代價(jià)較高時(shí),應(yīng)該考慮緩存那些從其它值計(jì)算得出的值。
TODO: 如何緩存?
3. 非常簡(jiǎn)單
模板表達(dá)式應(yīng)保持簡(jiǎn)單,不要過(guò)于復(fù)雜。應(yīng)在組件中實(shí)現(xiàn)應(yīng)用和業(yè)務(wù)邏輯,使開(kāi)發(fā)和測(cè)試變得更容易。
4. 冪等性
使用冪等的表達(dá)式?jīng)]有副作用,并且能提升 Angular 變更檢測(cè)的性能。
在 Angular 的術(shù)語(yǔ)中,冪等的表達(dá)式應(yīng)該總是返回完全相同的東西,直到某個(gè)依賴值發(fā)生改變。
模板語(yǔ)句 (Template statements)
模板語(yǔ)句用來(lái)響應(yīng)由綁定目標(biāo)(如 HTML 元素、組件或指令)觸發(fā)的事件。
A template statement responds to an event raised by a binding target such as an element, component, or directive.
語(yǔ)法:(event)="statement"
<button (click)="deleteHero()">Delete hero</button>
模板語(yǔ)句有副作用。這是事件的關(guān)鍵所在,可以根據(jù)用戶的活動(dòng)更新應(yīng)用狀態(tài)。
A template statement has a side effect. That's the whole point of an event. It's how you update application state from user action.
模板語(yǔ)句解析器 (template statement parser) 和模板表達(dá)式解析器 (template expression parser) 有所不同,特別之處在于它支持基本賦值 (=) 和表達(dá)式鏈 ( ; 和 , )。
某些 JavaScript 語(yǔ)法仍然是不允許的:
-
new運(yùn)算符 - 自增和自減運(yùn)算符:
++和-- - 操作并賦值,例如
+=和-= - 位操作符
|和& - 模板表達(dá)式運(yùn)算符
語(yǔ)句上下文 (Statement context)
和表達(dá)式中一樣,語(yǔ)句只能引用語(yǔ)句上下文中——通常是正在綁定事件的那個(gè)組件實(shí)例。
語(yǔ)句上下文可以引用模板自身上下文中的屬性。下例把模板的 $event 對(duì)象、模板輸入變量(let hero)和模板引用變量(#heroForm)傳給了組件中的一個(gè)事件處理器方法。
<button (click)="onSave($event)">Save</button>
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>
模板語(yǔ)句不能引用全局命名空間的任何東西。比如不能引用 window 或 document,也不能調(diào)用 console.log 或 Math.max。
語(yǔ)句指南 (Expression guidelines)
和表達(dá)式一樣,避免寫(xiě)復(fù)雜的模板語(yǔ)句。常規(guī)是函數(shù)調(diào)用或者屬性賦值。
綁定語(yǔ)法:概覽
綁定的類型可以根據(jù)數(shù)據(jù)流的方向分成三類:
- 從數(shù)據(jù)源到視圖(source-to-view)
- 從視圖到數(shù)據(jù)源(view-to-source)
- 雙向的從視圖到數(shù)據(jù)源再到視圖(view-to-source-to-view)。
單向 source-to-view
| 語(yǔ)法 | 綁定類型 |
|---|---|
{{expression}} |
插值表達(dá)式 |
[target]="expression" / bind-target="expression"
|
Property, Attribute 類樣式 |
單向 view-to-source
| 語(yǔ)法 | 綁定類型 |
|---|---|
(target)="statement" / on-target="statement"
|
事件 |
雙向
| 語(yǔ)法 | 綁定類型 |
|---|---|
[(target)]="expression" / bindon-target="expression"
|
雙向 |
綁定類型(插值表達(dá)式除外)有一個(gè)目標(biāo)名,它位于等號(hào)左邊,它是 Property 的名字(注意它不是 Attribute 的名字)。
新的思維模型
HTML attribute 與 DOM property 的對(duì)比
attribute 是由 HTML 定義的。property 是由 DOM (Document Object Model) 定義的。
- 少量 HTML attribute 和 property 之間有著 1:1 的映射,如 id。
- 有些 HTML attribute 沒(méi)有對(duì)應(yīng)的 property,如 colspan。
- 有些 DOM property 沒(méi)有對(duì)應(yīng)的 attribute,如 textContent。
<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Save</button>
一旦開(kāi)始數(shù)據(jù)綁定,就不再跟 HTML attribute 打交道了。這里不是設(shè)置 attribute,而是設(shè)置 DOM 元素、組件和指令的 property。
attribute 初始化 DOM property,然后它們的任務(wù)就完成了。property 的值可以改變,是當(dāng)前值;attribute 的值不能改變,是初始值。
即使名字相同,HTML attribute 和 DOM property 也不是同一樣?xùn)|西。
模板綁定是通過(guò) property 和事件來(lái)工作的,而不是 attribute。在 Angular 的世界中,attribute 唯一的作用是用來(lái)初始化元素和指令的狀態(tài)。 當(dāng)進(jìn)行數(shù)據(jù)綁定時(shí),只是在與元素和指令的 property 和事件打交道。
綁定目標(biāo) (Binding targets)
數(shù)據(jù)綁定的目標(biāo)是 DOM 中的某些東西。
Property 綁定類型
| 目標(biāo) | 范例 |
|---|---|
| Element property | <img [src]="heroImageUrl"> |
| Component property | <hero-detail [hero]="currentHero"></hero-detail> |
| Directive property | <div [ngClass]="{special: isSpecial}"></div> |
事件綁定類型
| 目標(biāo) | 范例 |
|---|---|
| Element event | <button (click)="onSave()">Save</button> |
| Component event | <hero-detail (deleteRequest)="deleteHero()"></hero-detail> |
| Directive event | <div (myClick)="clicked=$event" clickable>click me</div> |
雙向綁定類型
| 目標(biāo) | 范例 |
|---|---|
| Event and property | <input [(ngModel)]="name"> |
Attribute 綁定類型
| 目標(biāo) | 范例 |
|---|---|
| Attribute(例外情況) | <button [attr.aria-label]="help">help</button> |
CSS 類綁定類型
| 目標(biāo) | 范例 |
|---|---|
class property |
<div [class.special]="isSpecial">Special</div> |
樣式綁定類型
| 目標(biāo) | 范例 |
|---|---|
style property |
<button [style.color]="isSpecial ? 'red' : 'green'"> |
屬性綁定 (Property binding) [property]
當(dāng)要把視圖元素的屬性 (property) 設(shè)置為模板表達(dá)式時(shí),就要寫(xiě)模板的屬性 (property) 綁定。
最常用的屬性綁定是把元素屬性設(shè)置為組件屬性的值。
<img [src]="heroImageUrl">
上例說(shuō)明:
image元素的src屬性會(huì)被綁定到組件的heroImageUrl屬性上。
其他示例:
<button [disabled]="isUnchanged">Cancel is disabled</button>
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
<hero-detail [hero]="currentHero"></hero-detail>
單向輸入 (One-way in)
人們經(jīng)常把屬性綁定描述成單向數(shù)據(jù)綁定,因?yàn)橹档牧鲃?dòng)是單向的,從組件的數(shù)據(jù)屬性流動(dòng)到目標(biāo)元素的屬性。
注意:
- 不能使用屬性綁定來(lái)從目標(biāo)元素拉取值
- 不能綁定到目標(biāo)元素的屬性來(lái)讀取它。只能設(shè)置它。
- 也不能使用屬性 綁定 來(lái)調(diào)用目標(biāo)元素上的方法。
如果必須讀取目標(biāo)元素上的屬性或調(diào)用它的某個(gè)方法, 參見(jiàn) API 參考手冊(cè)中的 ViewChild 和 ContentChild。
綁定目標(biāo)
兩種寫(xiě)法:
<img [src]="heroImageUrl">

bind- 前綴的寫(xiě)法被稱為規(guī)范形式 (canonical form)。
元素屬性可能是最常見(jiàn)的綁定目標(biāo),但 Angular 會(huì)先去看這個(gè)名字是否是某個(gè)已知指令的屬性名。
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
嚴(yán)格來(lái)說(shuō),Angular 正在匹配指令的輸入屬性的名字。 這個(gè)名字是指令的 inputs 數(shù)組中所列的名字,或者是帶有 @Input() 裝飾器的屬性。 這些輸入屬性被映射為指令自己的屬性。
Technically, Angular is matching the name to a directive input, one of the property names listed in the directive's inputs array or a property decorated with @Input(). Such inputs map to the directive's own properties.
如果名字沒(méi)有匹配上已知指令或元素的屬性,Angular 就會(huì)報(bào)告“未知指令”的錯(cuò)誤。
消除副作用
一般建議是,只綁定數(shù)據(jù)屬性和那些只返回值而不做其它事情的方法。
返回恰當(dāng)?shù)念愋?/h3>
模板表達(dá)式應(yīng)該返回目標(biāo)屬性所需類型的值。
別忘了方括號(hào)
一次性字符串初始化
當(dāng)滿足下列條件時(shí),應(yīng)該省略括號(hào):
- 目標(biāo)屬性接受字符串值。
- 字符串是個(gè)固定值,可以直接合并到模塊中。
- 這個(gè)初始值永不改變。
<hero-detail prefix="You are my" [hero]="currentHero"></hero-detail>
屬性綁定還是插值表達(dá)式?
在多數(shù)情況下,插值表達(dá)式是更方便的備選項(xiàng)。實(shí)際上,在渲染視圖之前,Angular 把這些插值表達(dá)式翻譯成相應(yīng)的屬性綁定。
<p> is the <i>interpolated</i> image.</p>
會(huì)被自動(dòng)翻譯成
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
會(huì)被自動(dòng)翻譯成
<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
當(dāng)要渲染的數(shù)據(jù)類型是字符串時(shí),基于可讀性考慮,建議使用插值表達(dá)式。其他情況則必須使用屬性綁定。
內(nèi)容安全
不管是插值表達(dá)式還是屬性綁定,都不會(huì)允許帶有 script 標(biāo)簽的 HTML 泄漏到瀏覽器中。
插值表達(dá)式處理 script 標(biāo)簽與屬性綁定有所不同,但是二者都只渲染沒(méi)有危害的內(nèi)容。
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
<!--Angular generates warnings for these two lines as it sanitizes them
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).-->
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
attribute、class 和 style 綁定
attribute 綁定
語(yǔ)法:[attr.attribute-name]
可以通過(guò) attribute 綁定來(lái)直接設(shè)置 attribute 的值,因?yàn)楫?dāng)元素沒(méi)有屬性可綁的時(shí)候,就必須使用 attribute 綁定。
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
該語(yǔ)句會(huì)報(bào)錯(cuò)如下:
Template parse errors:
Can't bind to 'colspan' since it isn't a known native property
(模板解析錯(cuò)誤:不能綁定到 'colspan',因?yàn)樗皇且阎脑鷮傩裕?
正確的寫(xiě)法:
<table border=1>
<!-- expression calculates colspan=2 -->
<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>Five</td><td>Six</td></tr>
</table>
attribute 綁定的主要用例之一是設(shè)置 ARIA attribute
ARIA 指可訪問(wèn)性,用于給殘障人士訪問(wèn)互聯(lián)網(wǎng)提供便利。
<!-- create and set an aria attribute for assistive technology -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
CSS 類綁定
語(yǔ)法:[class.class-name]
<!-- standard class attribute setting -->
<div class="bad curly special">Bad curly special</div>
<!-- reset/override all class names with a binding -->
<div class="bad curly special" [class]="badCurly">Bad curly</div>
<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>
<!-- binding to `class.special` trumps the class attribute -->
<div class="special" [class.special]="!isSpecial">This one is not so special</div>
通常更喜歡使用 ngClass 指令來(lái)同時(shí)管理多個(gè)類名。
樣式綁定
語(yǔ)法:[style.style-property]
<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
帶有單位的綁定:
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
通常更喜歡使用 ngStyle 指令來(lái)同時(shí)設(shè)置多個(gè)內(nèi)聯(lián)樣式。
事件綁定 (Event binding) (event)
語(yǔ)法:(目標(biāo)事件)="模版語(yǔ)句"
(target event)="template statement"
<button (click)="onSave()">Save</button>
目標(biāo)事件
元素事件可能是更常見(jiàn)的目標(biāo),但 Angular 會(huì)先看這個(gè)名字是否能匹配上已知指令的事件屬性,如:
<!-- `myClick` is an event on the custom `ClickDirective` -->
<div (myClick)="clickMessage=$event" clickable>click with myClick</div>
如果這個(gè)名字沒(méi)能匹配到元素事件或已知指令的輸出屬性,Angular 就會(huì)報(bào)“未知指令”錯(cuò)誤。
$event 和事件處理語(yǔ)句(event handling statements)
在事件綁定中,Angular 會(huì)為目標(biāo)事件設(shè)置事件處理器。當(dāng)事件發(fā)生時(shí),這個(gè)處理器會(huì)執(zhí)行模板語(yǔ)句。典型的模板語(yǔ)句通常涉及到響應(yīng)事件執(zhí)行動(dòng)作的接收器,例如從 HTML 控件中取得值,并存入模型。
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. The template statement typically involves a receiver, which performs an action in response to the event, such as storing a value from the HTML control into a model.
綁定會(huì)通過(guò)名叫 $event 的事件對(duì)象傳遞關(guān)于此事件的信息(包括數(shù)據(jù)值)。
事件對(duì)象的形態(tài)取決于目標(biāo)事件。
The shape of the event object is determined by the target event.
如果目標(biāo)事件是原生 DOM 元素事件, $event 就是 DOM事件對(duì)象,它有像 target 和 target.value 這樣的屬性。
<input [value]="currentHero.name"
(input)="currentHero.name=$event.target.value" >
如果事件屬于指令,那么 $event 具體是什么由指令決定。
使用 EventEmitter 實(shí)現(xiàn)自定義事件
通常,指令使用 EventEmitter 來(lái)觸發(fā)自定義事件。指令創(chuàng)建一個(gè) EventEmitter 實(shí)例,并且把它作為屬性暴露出來(lái)。指令調(diào)用 EventEmitter.emit(payload) 來(lái)觸發(fā)事件,可以傳入任何東西作為消息載荷。 父指令通過(guò)綁定到這個(gè)屬性來(lái)監(jiān)聽(tīng)事件,并通過(guò) $event 對(duì)象來(lái)訪問(wèn)載荷。
示例
假設(shè) HeroDetailComponent 用于顯示英雄的信息,并響應(yīng)用戶的動(dòng)作。 雖然 HeroDetailComponent 包含刪除按鈕,但它自己并不知道該如何刪除這個(gè)英雄。 最好的做法是觸發(fā)事件來(lái)報(bào)告“刪除用戶”的請(qǐng)求。
src/app/hero-detail.component.ts (template)
template: `
<div>

<span [style.text-decoration]="lineThrough">
{{prefix}} {{hero?.name}}
</span>
<button (click)="delete()">Delete</button>
</div>`
src/app/hero-detail.component.ts (deleteRequest)
// This component make a request but it can't actually delete a hero.
deleteRequest = new EventEmitter<Hero>();
delete() {
this.deleteRequest.emit(this.hero);
}
說(shuō)明:組件定義了
deleteRequest屬性,它是EventEmitter實(shí)例。 當(dāng)用戶點(diǎn)擊刪除時(shí),組件會(huì)調(diào)用delete()方法,讓EventEmitter發(fā)出一個(gè)Hero對(duì)象。
現(xiàn)在,假設(shè)有個(gè)宿主的父組件,它綁定了 HeroDetailComponent 的 deleteRequest 事件。
<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>
當(dāng) deleteRequest 事件觸發(fā)時(shí),Angular 調(diào)用父組件的 deleteHero 方法, 在 $event 變量中傳入要?jiǎng)h除的英雄(來(lái)自 HeroDetail)。
模板語(yǔ)句有副作用
模板語(yǔ)句的副作用不僅沒(méi)問(wèn)題,反而正是所期望的。
雙向數(shù)據(jù)綁定 (Two-way binding) [(...)]
語(yǔ)法:[(x)]
[(x)] 語(yǔ)法結(jié)合了屬性綁定的方括號(hào) [x] 和事件綁定的圓括號(hào) (x)。當(dāng)一個(gè)元素?fù)碛锌梢栽O(shè)置的屬性 x 和對(duì)應(yīng)的事件 xChange 時(shí),就可以使用 [(x)] 語(yǔ)法 。
示例 src/app/sizer.component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';
@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>`
})
export class SizerComponent {
@Input() size: number | string;
@Output() sizeChange = new EventEmitter<number>();
dec() { this.resize(-1); }
inc() { this.resize(+1); }
resize(delta: number) {
this.size = Math.min(40, Math.max(8, +this.size + delta));
this.sizeChange.emit(this.size);
}
}
AppComponent.fontSize 被雙向綁定到 SizerComponent:
<my-sizer [(size)]="fontSizePx"></my-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>
Angular 將 SizerComponent 的綁定分解成這樣:
<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>
說(shuō)明:
$event變量包含了SizerComponent.sizeChange事件的荷載。 當(dāng)用戶點(diǎn)擊按鈕時(shí),Angular 將$event賦值給AppComponent.fontSizePx。
內(nèi)置指令
內(nèi)置屬性型指令(Built-in attribute directives)
屬性型指令會(huì)監(jiān)聽(tīng)和修改其它 HTML 元素或組件的行為、元素 Attribute、DOM Property。 它們通常會(huì)作為 HTML Attribute 的名稱而應(yīng)用在元素上。
常見(jiàn)的內(nèi)置屬性型指令:
-
NgClass添加或移除一組CSS類 -
NgStyle添加或移除一組CSS樣式 -
NgModel雙向綁定到 HTML 表單元素
NgClass
示例:組件方法 setCurrentClasses 可以把組件的屬性 currentClasses 設(shè)置為一個(gè)對(duì)象,它將會(huì)根據(jù)三個(gè)其它組件的狀態(tài)為 true 或 false 而添加或移除三個(gè)類。
currentClasses: {};
setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
saveable: this.canSave,
modified: !this.isUnchanged,
special: this.isSpecial
};
}
把 ngClass 屬性綁定到 currentClasses,根據(jù)它來(lái)設(shè)置此元素的 CSS 類:
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>
你既可以在初始化時(shí)調(diào)用
setCurrentClassess(),也可以在所依賴的屬性變化時(shí)調(diào)用。
NgStyle
ngStyle 需要綁定到一個(gè) key:value 控制對(duì)象。 對(duì)象的每個(gè) key 是樣式名,它的 value 是能用于這個(gè)樣式的任何值。
currentStyles: {};
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>
NgModel
要使用 ngModel 需要導(dǎo)入 FormsModule 模塊。
示例:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular
/* Other imports */
@NgModule({
imports: [
BrowserModule,
FormsModule // <--- import into the NgModule
],
/* Other module metadata */
})
export class AppModule { }
更多關(guān)于
FormsModule和ngModel的內(nèi)容參見(jiàn)表單。
使用 ngModel 實(shí)現(xiàn)雙向數(shù)據(jù)綁定。
<input [(ngModel)]="currentHero.name">
該語(yǔ)句實(shí)際上隱藏了其實(shí)現(xiàn)細(xì)節(jié):
<input [ngModel]="currentHero.name"
(ngModelChange)="currentHero.name=$event">
如果需要做一些不同的處理,就不能使用 [(ngModel)] 語(yǔ)法,而應(yīng)寫(xiě)成擴(kuò)展的方式:
<input [ngModel]="currentHero.name"
(ngModelChange)="setUppercaseName($event)">
ngModel指令只能用在支持 ControlValueAccessor 的元素上。
內(nèi)置結(jié)構(gòu)型指令(Built-in structural directives)
結(jié)構(gòu)型指令負(fù)責(zé) HTML 布局。
常見(jiàn)的內(nèi)置結(jié)構(gòu)型指令:
-
NgIfconditionally add or remove an element from the DOM -
NgForrepeat a template for each item in a list -
NgSwitcha set of directives that switch among alternative views
NgIf
You can add or remove an element from the DOM by applying an NgIf directive to that element (called the host elment).
示例:
<hero-detail *ngIf="isActive"></hero-detail>
When the isActive expression returns a truthy value, NgIf adds the HeroDetailComponent to the DOM. When the expression is falsy, NgIf removes the HeroDetailComponent from the DOM, destroying that component and all of its sub-components.
別忘了
ngIf前面的星號(hào)(*)。
這和顯示/隱藏不是一回事。
我們也可以通過(guò)類綁定或樣式綁定來(lái)顯示或隱藏一個(gè)元素。但隱藏子樹(shù)和用 NgIf 排除子樹(shù)是截然不同的。
當(dāng)隱藏子樹(shù)時(shí),它仍然留在 DOM 中。當(dāng) NgIf 為 false 時(shí) Angular 從 DOM 中物理地移除了子樹(shù),它銷毀了子樹(shù)中的組件及其狀態(tài),也潛在釋放了可觀的資源。
防范空指針錯(cuò)誤
NgIf 指令通常會(huì)用來(lái)防范空指針錯(cuò)誤。 而顯示/隱藏的方式是無(wú)法防范的。
<div *ngIf="currentHero">Hello, {{currentHero.name}}</div>
<div *ngIf="nullHero">Hello, {{nullHero.name}}</div>
currentHero 的名字只有當(dāng)存在 currentHero 時(shí)才會(huì)顯示出來(lái)。 而 nullHero 永遠(yuǎn)不會(huì)顯示。
NgFor
NgFor 是一個(gè)重復(fù)器指令——顯示列表項(xiàng)的一種方式。你先定義了一個(gè) HTML 塊,它規(guī)定了單個(gè)條目應(yīng)該如何顯示。然后你告訴 Angular 把這個(gè)塊當(dāng)做模板,渲染列表中的每個(gè)條目。
NgFor is a repeater directive — a way to present a list of items. You define a block of HTML that defines how a single item should be displayed. You tell Angular to use that block as a template for rendering each item in the list.
例子1:
<div *ngFor="let hero of heroes">{{hero.name}}</div>
例子2:
<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>
別忘了
ngFor前面的星號(hào)(*)。
賦值給 *ngFor 的字符串不是模板表達(dá)式,它是一個(gè)微語(yǔ)法 —— 由 Angular 自己解釋的小型語(yǔ)言。
字符串 "let hero of heroes" 的含義:
取出 heroes 數(shù)組中的每個(gè)英雄,把它存入局部變量 hero 中,并在每次迭代時(shí)對(duì)模板 HTML 可用。
Take each hero in the heroes array, store it in the local hero looping variable, and make it available to the templated HTML for each iteration.
模板輸入變量 (Template input variables)
The let keyword before hero creates a template input variable called hero.
*ngFor 的索引 (index)
The index property of the NgFor directive context returns the zero-based index of the item in each iteration.
<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.name}}</div>
更多內(nèi)容參見(jiàn) NgFor API
*ngFor with trackBy
ngFor 指令有時(shí)候會(huì)性能較差,特別是在大型列表中。對(duì)一個(gè)條目的一丁點(diǎn)改動(dòng)、移除或添加,都會(huì)導(dǎo)致級(jí)聯(lián)的 DOM 操作。
有了 trackBy,則只有 id 發(fā)生改變才會(huì)觸發(fā)元素替換。
在組件中添加方法:
trackByHeroes(index: number, hero: Hero): number { return hero.id; }
使用 trackBy:
<div *ngFor="let hero of heroes; trackBy: trackByHeroes">
({{hero.id}}) {{hero.name}}
</div>
NgSwitch
NgSwitch can display one element from among several possible elements, based on a switch condition.
NgSwitch 由三個(gè)指令組成:
- 屬性型指令
NgSwitch - 結(jié)構(gòu)型指令
NgSwitchCase - 結(jié)構(gòu)型指令
NgSwitchDefault
示例:
<div [ngSwitch]="currentHero.emotion">
<happy-hero *ngSwitchCase="'happy'" [hero]="currentHero"></happy-hero>
<sad-hero *ngSwitchCase="'sad'" [hero]="currentHero"></sad-hero>
<confused-hero *ngSwitchCase="'confused'" [hero]="currentHero"></confused-hero>
<unknown-hero *ngSwitchDefault [hero]="currentHero"></unknown-hero>
</div>
NgSwitch 指令在增加或移除組件元素 (component elements) 時(shí)尤其有用。
模板引用變量 (Template reference variables) #var
模板引用變量通常是一個(gè)模版中的對(duì) DOM 元素的一個(gè)引用。
A template reference variable is often a reference to a DOM element within a template.
使用井號(hào) # (或 ref-)來(lái)聲明一個(gè)模板引用變量。The#phone declares a phone variable on an <input> element.
<input #phone placeholder="phone number">
或者寫(xiě)成
<input ref-phone placeholder="phone number">
你可以在模板中的任意位置引用該模板引用變量。
<input #phone placeholder="phone number">
<!-- lots of other elements -->
<!-- phone refers to the input element; pass its `value` to an event handler -->
<button (click)="callPhone(phone.value)">Call</button>
說(shuō)明:
phonerefers to the phone number<input>box. The phone button click handler passes the *input *value to the component'scallPhonemethod.
模板引用變量如何獲取自身的值?
通常,如果一個(gè)元素聲明了一個(gè)模板引用變量,那么 Angular 會(huì)將模板引用變量的值設(shè)置為該元素的值。
In most cases, Angular sets the reference variable's value to the element on which it was declared.
示例:
<form (ngSubmit)="onSubmit(heroForm)" #heroForm="ngForm">
<div class="form-group">
<label for="name">Name
<input class="form-control" name="name" required [(ngModel)]="hero.name">
</label>
</div>
<button type="submit" [disabled]="!heroForm.form.valid">Submit</button>
</form>
<div [hidden]="!heroForm.form.valid">
{{submitMessage}}
</div>
If Angular hadn't taken it over when you imported the FormsModule, it would be the HTMLFormElement. The heroForm
is actually a reference to an Angular NgForm directive with the ability to track the value and validity of every control in the form.
The native <form> element doesn't have a form property. But the NgForm directive does, which explains how you can disable the submit button if the heroForm.form.valid is invalid and pass the entire form control tree to the parent component's onSubmit method.
注意
模板引用變量 (template reference variable) (#phone) 與模板輸入變量 (template input variable) (*ngFor 中的 let phone) 并不相同。詳見(jiàn)結(jié)構(gòu)型指令。
輸入輸出屬性 @Input 和 @Output
綁定目標(biāo)與綁定源的區(qū)別:
- 綁定的目標(biāo)是在
=左側(cè)的部分, 源則是在=右側(cè)的部分。 - 綁定的目標(biāo)是綁定符:
[]、()或[()]中的屬性或事件名, 源則是引號(hào)" "中的部分或插值符號(hào){{}}中的部分。 - 源指令中的每個(gè)成員都會(huì)自動(dòng)在綁定中可用。 不需要特別做什么,就能在模板表達(dá)式或語(yǔ)句中訪問(wèn)指令的成員。
- 訪問(wèn)目標(biāo)指令中的成員則受到限制。 只能綁定到那些顯式標(biāo)記為輸入或輸出的屬性。
iconUrl 和 onSave 是綁定源
<img [src]="iconUrl"/>
<button (click)="onSave()">Save</button>
HeroDetailComponent.hero 是屬性綁定的目標(biāo)。 HeroDetailComponent.deleteRequest 是事件綁定的目標(biāo)。
<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
</hero-detail>
聲明輸入和輸出屬性
目標(biāo)屬性必須被顯式的標(biāo)記為輸入或輸出。
方法1:使用裝飾器 @Input() 和 @Output()。
@Input() hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();
方法2:通過(guò)元數(shù)據(jù)數(shù)組。
@Component({
inputs: ['hero'],
outputs: ['deleteRequest'],
})
兩種方法不可同時(shí)使用。
輸入還是輸出?
輸入屬性通常接收數(shù)據(jù)值。 輸出屬性暴露事件生產(chǎn)者。
Input properties usually receive data values. Output properties expose event producers.
輸入和輸出這兩個(gè)詞是從目標(biāo)指令的角度來(lái)說(shuō)的。
- 從
HeroDetailComponent角度來(lái)看,HeroDetailComponent.hero是個(gè)輸入屬性, 因?yàn)閿?shù)據(jù)流從模板綁定表達(dá)式流入那個(gè)屬性。 - 從
HeroDetailComponent角度來(lái)看,HeroDetailComponent.deleteRequest是個(gè)輸出屬性, 因?yàn)槭录哪莻€(gè)屬性流出,流向模板綁定語(yǔ)句中的處理器。
給輸入輸出屬性起別名
方法1:把別名傳進(jìn) @Input / @Output 裝飾器,就可以為屬性指定別名:
@Output('myClick') clicks = new EventEmitter<string>(); // @Output(alias) propertyName = ...
方法2:在 inputs 和 outputs 數(shù)組中為屬性指定別名。 語(yǔ)法(屬性名:別名)。
@Directive({
outputs: ['clicks:myClick'] // propertyName:alias
})
模板表達(dá)式操作符
管道操作符 |
管道是一個(gè)簡(jiǎn)單的函數(shù),它接受一個(gè)輸入值,并返回轉(zhuǎn)換結(jié)果。
<div>Title through uppercase pipe: {{title | uppercase}}</div>
管道操作符會(huì)把它左側(cè)的表達(dá)式結(jié)果傳給它右側(cè)的管道函數(shù)。
更多內(nèi)容見(jiàn)管道。
還可以通過(guò)多個(gè)管道串聯(lián)表達(dá)式:
<!-- Pipe chaining: convert title to uppercase, then to lowercase -->
<div>
Title through a pipe chain:
{{title | uppercase | lowercase}}
</div>
還能對(duì)管道使用參數(shù):
<!-- pipe with configuration argument => "February 25, 1970" -->
<div>Birthdate: {{currentHero?.birthdate | date:'longDate'}}</div>
json 管道對(duì)調(diào)試綁定特別有用:
<div>{{currentHero | json}}</div>
輸出結(jié)果:
{ "id": 0, "name": "Hercules", "emotion": "happy",
"birthdate": "1970-02-25T08:00:00.000Z",
"url": "http://www.imdb.com/title/tt0065832/",
"rate": 325 }
安全導(dǎo)航操作符 ( ?. ) 和空屬性路徑
Angular 的安全導(dǎo)航操作符 (?.) 用來(lái)保護(hù)出現(xiàn)在屬性路徑中 null 和 undefined 值。示例:
The current hero's name is {{currentHero?.name}}
說(shuō)明:當(dāng)
currentHero為空時(shí),保護(hù)視圖渲染器,讓它免于失敗。
顯示一個(gè)空 (null) 英雄的 name 示例:
The null hero's name is {{nullHero.name}}
:marked
JavaScript throws a null reference error, and so does Angular:
JavaScript 拋出了空引用錯(cuò)誤,Angular 也是如此:code-example(format="nocode").
TypeError: Cannot read property 'name' of null in [null].
當(dāng) currentHero 為空的時(shí)候,應(yīng)用崩潰了,整個(gè)視圖都不見(jiàn)了。
笨重的解決辦法1:
<!--No hero, div not displayed, no error -->
<div *ngIf="nullHero">The null hero's name is {{nullHero.name}}</div>
笨重的解決辦法2:
The null hero's name is {{nullHero && nullHero.name}}
正確的解決辦法:
<!-- No hero, no problem! -->
The null hero's name is {{nullHero?.name}}
總結(jié)
透徹理解模板語(yǔ)法對(duì)開(kāi)發(fā)至關(guān)重要。