Angular 應用管理着用戶之所見和所爲,並經過 Component 類的實例(組件)和麪向用戶的模板來與用戶交互。html
從使用模型-視圖-控制器 (MVC) 或模型-視圖-視圖模型 (MVVM) 的經驗中,不少開發人員都熟悉了組件和模板這兩個概念。 在 Angular 中,組件扮演着控制器或視圖模型的角色,模板則扮演視圖的角色。express
來看看寫視圖的模板都須要什麼。本章將覆蓋模板語法中的下列基本元素編程
HTML 是 Angular 模板的語言。快速起步應用的模板是純 HTML 的:json
<h1>Hello Angular</h1>
幾乎全部的 HTML 語法都是有效的模板語法。但值得注意的例外是<script>
元素,它被禁用了,以阻止腳本注入攻擊的風險。(實際上,<script>
只是被忽略了。)bootstrap
有些合法的 HTML 被用在模板中是沒有意義的。<html>
、<body>
和<base>
元素這個舞臺上中並無扮演有用的角色。基本上全部其它的元素都被同樣使用。api
能夠經過組件和指令來擴展模板中的 HTML 詞彙。它們看上去就是新元素和屬性。接下來將學習如何經過數據綁定來動態獲取/設置 DOM(文檔對象模型)的值。數組
數據綁定的第一種形式 —— 插值表達式 —— 展現了模板的 HTML 能夠有多豐富。瀏覽器
在之前的 Angular 教程中,咱們遇到過由雙花括號括起來的插值表達式,{{
和}}
。緩存
<p>My current hero is {{currentHero.firstName}}</p>
插值表達式能夠把計算後的字符串插入到 HTML 元素標籤內的文本或對標籤的屬性進行賦值。安全
<h3> {{title}} <img src="{{heroImageUrl}}" style="height:30px"> </h3>
在括號之間的「素材」,一般是組件屬性的名字。Angular 會用組件中相應屬性的字符串值,替換這個名字。 上例中,Angular 計算title
和heroImageUrl
屬性的值,並把它們填在空白處。 首先顯示粗體的應用標題,而後顯示英雄的圖片。
通常來講,括號間的素材是一個模板表達式,Angular 先對它求值,再把它轉換成字符串。 下列插值表達式經過把括號中的兩個數字相加說明
<!-- "The sum of 1 + 1 is 2" --> <p>The sum of 1 + 1 is {{1 + 1}}</p>
這個表達式能夠調用宿主組件的方法,就像下面用的getVal()
:
<!-- "The sum of 1 + 1 is not 4" --> <p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}</p>
Angular 對全部雙花括號中的表達式求值,把求值的結果轉換成字符串,並把它們跟相鄰的字符串字面量鏈接起來。最後,把這個組合出來的插值結果賦給元素或指令的屬性。
表面上看,咱們在元素標籤之間插入告終果和對標籤的屬性進行了賦值。 這樣思考起來很方便,而且這個誤解不多給咱們帶來麻煩。 但嚴格來說,這是不對的。插值表達式是一個特殊的語法,Angular 把它轉換成了屬性綁定,後面將會解釋這一點。
講解屬性綁定以前,先深刻了解一下模板表達式和模板語句。
模板表達式產生一個值。 Angular 執行這個表達式,並把它賦值給綁定目標的屬性,這個綁定目標多是 HTML 元素、組件或指令。
當咱們寫{{1 + 1}}
時,是往插值表達式的括號中放進了一個模板表達式。 在屬性綁定中會再次看到模板表達式,它出如今=
右側的引號中,看起來像這樣:[property]="expression"
。
編寫模板表達式所用的語言看起來很像 JavaScript。 不少 JavaScript 表達式也是合法的模板表達式,但不是所有。
JavaScript 中那些具備或可能引起反作用的表達式是被禁止的,包括:
賦值 (=
, +=
, -=
, ...)
new
運算符
使用;
或,
的鏈式表達式
自增或自減操做符 (++
和--
)
和 JavaScript語 法的其它顯著不一樣包括:
不支持位運算|
和&
具備新的模板表達式運算符,好比|
和?.
表達式上下文
也許更讓人吃驚的是,模板表達式不能引用全局命名空間中的任何東西。 不能引用window
或document
。不能調用console.log
或Math.max
。 它們被侷限於只能訪問來自表達式上下文中的成員。
典型的表達式上下文就是這個組件實例,它是各類綁定值的來源。
當看到包裹在雙花括號中的 title ({{title}}
) 時,咱們就知道title
是這個數據綁定組件中的一個屬性。 當看到[disabled]="isUnchanged"
中的 isUnchanged 時,咱們就知道正在引用該組件的isUnchanged
屬性。
一般,組件自己就是表達式的上下文,這種狀況下,模板表達式會引用那個組件。
表達式的上下文能夠包括組件以外的對象。 模板引用變量就是備選的上下文對象之一。
模板表達式能成就或毀掉一個應用。請遵循下列指南:
超出上面指南外的狀況應該只出如今那些你確信本身已經完全理解的特定場景中。
模板表達式除了目標屬性的值之外,不該該改變應用的任何狀態。
這條規則是 Angular 「單向數據流」策略的基礎。 永遠不用擔憂讀取組件值可能改變另外的顯示值。 在一次單獨的渲染過程當中,視圖應該老是穩定的。
Angular 執行模板表達式比咱們想象的頻繁。 它們可能在每一次按鍵或鼠標移動後被調用。 表達式應該快速結束,不然用戶就會感到拖沓,特別是在較慢的設備上。 當計算代價較高時,應該考慮緩存那些從其它值計算得出的值。
雖然能夠寫出至關複雜的模板表達式,但不要那麼去寫。
常規是屬性名或方法調用。偶爾的邏輯取反 (!
) 也還湊合。 其它狀況下,應在組件中實現應用和業務邏輯,使開發和測試變得更容易。
最好使用冪等的表達式,由於它沒有反作用,而且能提高 Angular 變動檢測的性能。
在 Angular 的術語中,冪等的表達式應該老是返回徹底相同的東西,直到某個依賴值發生改變。
在單獨的一次事件循環中,被依賴的值不該該改變。 若是冪等的表達式返回一個字符串或數字,連續調用它兩次,也應該返回相同的字符串或數字。 若是冪等的表達式返回一個對象(包括Date
或Array
),連續調用它兩次,也應該返回同一個對象的引用。
模板語句用來響應由綁定目標(如 HTML 元素、組件或指令)觸發的事件。
模板語句將在事件綁定一節看到,它出如今=
號右側的引號中,就像這樣:(event)="statement"
。
模板語句有反作用。 這正是用戶輸入更新應用狀態的方式。 不然,響應事件就沒有什麼意義了。
響應事件是 Angular 中「單向數據流」的另外一面。 在一次事件循環中,能夠隨意改變任何地方的任何東西。
和模板表達式同樣,模板語句使用的語言也像 JavaScript。 模板語句解析器和模板表達式解析器有所不一樣,特別之處在於它支持基本賦值 (=
) 和表達式鏈 (;
和,
)。
然而,某些 JavaScript 語法仍然是不容許的:
new
運算符
自增和自減運算符:++
和--
操做並賦值,例如+=
和-=
位操做符|
和&
和表達式中同樣,語句只能引用語句上下文中 —— 一般是正在綁定事件的那個組件實例。
模板語句沒法引用全局命名空間的任何東西。它們不能引用window
或者document
, 不能調用console.log
或者Math.max
。
(click)="onSave()"
中的 onSave 就是數據綁定組件實例中的方法。
語句上下文能夠包含組件以外的對象。 模板引用對象就是備選上下文對象之一。 在事件綁定語句中,常常會看到被保留的$event
符號,它表明觸發事件的「消息」或「有效載荷」。
和表達式同樣,避免寫複雜的模板語句。 常規是函數調用或者屬性賦值。
如今,對模板表達式和語句有了一點感受了吧。 除插值表達式外,還有各類各樣的數據綁定語法,是學習它們是時候了。
數據綁定是一種機制,用來協調用戶所見和應用數據。 雖然咱們能往 HTML 推送值或者從 HTML 拉取值, 但若是把這些雜事交給數據綁定框架處理, 應用會更容易編寫、閱讀和維護。 只要簡單地在綁定源和目標 HTML 元素之間聲明綁定,框架就會完成這項工做。
Angular 提供了各類各樣的數據綁定,本章將逐一討論。 首先,從高層視角來看看 Angular 數據綁定和它的語法。
根據數據流的方向,能夠把全部綁定歸爲三類。 每一類都有它獨特的語法:
數據方向 |
語法 |
綁定類型 |
---|---|---|
單向 從數據源 到視圖目標 |
{{expression}} [target] = "expression" bind-target = "expression"
|
插值表達式 Property Attribute 類 樣式 |
單向 從視圖目標 到數據源 |
(target) = "statement" on-target = "statement"
|
事件 |
雙向 |
[(target)] = "expression" bindon-target = "expression"
|
雙向 |
譯註:因爲 HTML attribute 和 DOM property 在中文中都被翻譯成了「屬性」,沒法區分, 而接下來的部分重點是對它們進行比較。
咱們沒法改變歷史,所以,在本章的翻譯中,保留了它們的英文形式,不加翻譯,以避免混淆。 本章中,若是提到「屬性」的地方,必定是指 property,由於在 Angular 中,實際上不多涉及 attribute。
但在其它章節中,爲簡單起見,凡是能經過上下文明顯區分開的,就仍統一譯爲「屬性」, 區分不明顯的,會加註英文。
除了插值表達式以外的綁定類型,在等號左邊是目標名, 不管是包在括號中 ([]
、()
) 仍是用前綴形式 (bind-
、on-
、bindon-
) 。
什麼是「目標」?在回答這個問題以前,咱們必須先挑戰下自我,嘗試用另外一種方式來審視模板中的 HTML。
數據綁定的威力和容許用自定義標記擴展 HTML 詞彙的能力,容易誤導咱們把模板 HTML 當成 HTML+。
也對,它是 HTML+。 但它也跟咱們熟悉的 HTML 有着顯著的不一樣。 咱們須要一種新的思惟模型。
在正常的 HTML 開發過程當中,咱們使用 HTML 元素建立視覺結構, 經過把字符串常量設置到元素的 attribute 來修改那些元素。
<div class="special">Mental Model</div> <img src="images/hero.png"> <button disabled>Save</button>
在 Angular 模板中,咱們仍使用一樣的方式來建立結構和初始化 attribute 值。
而後,用封裝了 HTML 的組件建立新元素,並把它們看成原生 HTML 元素在模板中使用。
<!-- Normal HTML --> <div class="special">Mental Model</div> <!-- Wow! A new element! --> <hero-detail></hero-detail>
這就是HTML+。
如今開始學習數據綁定。咱們碰到的第一種數據綁定看起來是這樣的:
<!-- Bind button disabled state to `isUnchanged` property --> <button [disabled]="isUnchanged">Save</button>
過會兒再認識那個怪異的方括號記法。直覺告訴咱們,咱們正在綁定按鈕的disabled
attribute。 並把它設置爲組件的isUnchanged
屬性的當前值。
但咱們的直覺是錯的!平常的 HTML 思惟模式在誤導咱們。 實際上,一旦開始數據綁定,就再也不跟 HTML attribute 打交道了。 這裏不是設置 attribute,而是設置 DOM 元素、組件和指令的 property。
要想理解 Angular 綁定如何工做,重點是搞清 HTML attribute 和 DOM property 之間的區別。
attribute 是由 HTML 定義的。property 是由 DOM (Document Object Model) 定義的。
少許 HTML attribute 和 property 之間有着 1:1 的映射,如id
。
有些 HTML attribute 沒有對應的 property,如colspan
。
有些 DOM property 沒有對應的 attribute,如textContent
。
大量 HTML attribute看起來映射到了property…… 但卻不像咱們想的那樣!
最後一類尤爲讓人困惑…… 除非咱們能理解這個廣泛原則:
attribute 初始化 DOM property,而後它們的任務就完成了。property 的值能夠改變;attribute 的值不能改變。
例如,當瀏覽器渲染<input type="text" value="Bob">
時,它將建立相應 DOM 節點, 其value
property 被初始化爲 「Bob」。
當用戶在輸入框中輸入 「Sally」 時,DOM 元素的value
property 變成了 「Sally」。 可是這個 HTML value
attribute 保持不變。若是咱們讀取 input 元素的 attribute,就會發現確實沒變: input.getAttribute('value') // 返回 "Bob"
。
HTML attribute value
指定了初始值;DOM value
property 是當前值。
disabled
attribute 是另外一個古怪的例子。按鈕的disabled
property 是false
,由於默認狀況下按鈕是可用的。 當咱們添加disabled
attribute 時,只要它出現了按鈕的disabled
property 就初始化爲true
,因而按鈕就被禁用了。
添加或刪除disabled
attribute會禁用或啓用這個按鈕。但 attribute 的值可有可無,這就是咱們爲何無法經過 <button disabled="false">仍被禁用</button>
這種寫法來啓用按鈕。
設置按鈕的disabled
property(如,經過 Angular 綁定)能夠禁用或啓用這個按鈕。 這就是 property 的價值。
就算名字相同,HTML attribute 和 DOM property 也不是同同樣東西。
這句話很重要,得再強調一次:
模板綁定是經過 property 和事件來工做的,而不是 attribute。
在 Angular 的世界中,attribute 惟一的做用是用來初始化元素和指令的狀態。 當進行數據綁定時,只是在與元素和指令的 property 和事件打交道,而 attribute 就徹底靠邊站了。
把這個思惟模型緊緊的印在腦子裏,接下來,學習什麼是綁定目標。
數據綁定的目標是 DOM 中的某些東西。 這個目標多是(元素 | 組件 | 指令的)property、(元素 | 組件 | 指令的)事件,或(極少數狀況下) attribute 名。 下面是的彙總表:
綁定類型 |
目標 |
範例 |
---|---|---|
Property |
元素的 property 組件的 property 指令的 property |
<img [src] = "heroImageUrl"> <hero-detail [hero]="currentHero"></hero-detail> <div [ngClass] = "{selected: isSelected}"></div>
|
事件 |
元素的事件 組件的事件 指令的事件 |
<button (click) = "onSave()">Save</button> <hero-detail (deleteRequest)="deleteHero()"></hero-detail> <div (myClick)="clicked=$event">click me</div>
|
雙向 |
事件與 property |
<input [(ngModel)]="heroName">
|
Attribute | attribute(例外狀況) |
<button [attr.aria-label]="help">help</button>
|
CSS 類 |
|
<div [class.special]="isSpecial">Special</div>
|
樣式 |
|
<button [style.color] = "isSpecial ? 'red' : 'green'">
|
讓咱們從結構性雲層中走出來,看看每種綁定類型的具體狀況。
當要把視圖元素的屬性 (property) 設置爲模板表達式時,就要寫模板的屬性 (property) 綁定。
最經常使用的屬性綁定是把元素屬性設置爲組件屬性的值。 下面這個例子中,image 元素的src
屬性會被綁定到組件的heroImageUrl
屬性上:
<img [src]="heroImageUrl">
另外一個例子是當組件說它isUnchanged
(未改變)時禁用按鈕:
<button [disabled]="isUnchanged">Cancel is disabled</button>
另外一個例子是設置指令的屬性:
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
還有另外一個例子是設置自定義組件的模型屬性(這是父子組件之間通信的重要途徑):
<hero-detail [hero]="currentHero"></hero-detail>
人們常常把屬性綁定描述成單向數據綁定,由於值的流動是單向的,從組件的數據屬性流動到目標元素的屬性。
不能使用屬性綁定來從目標元素拉取值,也不能綁定到目標元素的屬性來讀取它。只能設置它。
也不能使用屬性 綁定 來調用目標元素上的方法。
若是這個元素觸發了事件,能夠經過事件綁定來監聽它們。
若是必須讀取目標元素上的屬性或調用它的某個方法,得用另外一種技術。 參見 API 參考手冊中的 ViewChild 和 ContentChild。
包裹在方括號中的元素屬性名標記着目標屬性。下列代碼中的目標屬性是 image 元素的src
屬性。
<img [src]="heroImageUrl">
有些人喜歡用bind-
前綴的可選形式,並稱之爲規範形式:
<img bind-src="heroImageUrl">
目標的名字老是 property 的名字。即便它看起來和別的名字同樣。 看到src
時,可能會把它當作 attribute。不!它不是!它是 image 元素的 property 名。
元素屬性多是最多見的綁定目標,但 Angular 會先去看這個名字是不是某個已知指令的屬性名,就像下面的例子中同樣:
<div [ngClass]="classes">[ngClass] binding to the classes property</div>
嚴格來講,Angular 正在匹配指令的輸入屬性的名字。 這個名字是指令的inputs
數組中所列的名字,或者是帶有@Input()
裝飾器的屬性。 這些輸入屬性被映射爲指令本身的屬性。
若是名字沒有匹配上已知指令或元素的屬性,Angular 就會報告「未知指令」的錯誤。
模板表達式應該返回目標屬性所需類型的值。 若是目標屬性想要個字符串,就返回字符串。 若是目標屬性想要個數字,就返回數字。 若是目標屬性想要個對象,就返回對象。
HeroDetail
組件的hero
屬性想要一個Hero
對象,那就在屬性綁定中精確地給它一個Hero
對象:
<hero-detail [hero]="currentHero"></hero-detail>
方括號告訴 Angular 要計算模板表達式。 若是忘了加方括號,Angular 會把這個表達式當作字符串常量看待,並用該字符串來初始化目標屬性。 它不會計算這個字符串。
不要出現這樣的失誤:
<!-- ERROR: HeroDetailComponent.hero expects a Hero object, not the string "currentHero" --> <hero-detail hero="currentHero"></hero-detail>
當下列條件知足時,應該省略括號:
目標屬性接受字符串值。
字符串是個固定值,能夠直接合併到模塊中。
這個初始值永不改變。
咱們常常這樣在標準 HTML 中用這種方式初始化 attribute,這種方式也能夠用在初始化指令和組件的屬性。 下面這個例子把HeroDetailComponent
的prefix
屬性初始化爲固定的字符串,而不是模板表達式。Angular 設置它,而後忘記它。
<hero-detail prefix="You are my" [hero]="currentHero"></hero-detail>
做爲對比,[hero]
綁定是組件的currentHero
屬性的活綁定,它會一直隨着更新。
咱們一般得在插值表達式和屬性綁定之間作出選擇。 下列這幾對綁定作的事情徹底相同:
<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p> <p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p> <p><span>"{{title}}" is the <i>interpolated</i> title.</span></p> <p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
在多數狀況下,插值表達式是更方便的備選項。 實際上,在渲染視圖以前,Angular 把這些插值表達式翻譯成相應的屬性綁定。
當要渲染的數據類型是字符串時,沒有技術上的理由證實哪一種形式更好。 咱們傾向於可讀性,因此傾向於插值表達式。 建議創建代碼風格規則,選擇一種形式, 這樣,既遵循了規則,又能讓手頭的任務作起來更天然。
但數據類型不是字符串時,就必須使用屬性綁定了。
假設下面的惡毒內容
evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
幸運的是,Angular 數據綁定對危險 HTML 有防備。 在顯示它們以前,它對內容先進行消毒。 無論是插值表達式仍是屬性綁定,都不會容許帶有 script 標籤的 HTML 泄漏到瀏覽器中。
<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>
插值表達式處理 script 標籤與屬性綁定有所不一樣,可是兩者都只渲染沒有危害的內容。
模板語法爲那些不太適合使用屬性綁定的場景提供了專門的單向數據綁定形式。
能夠經過attribute 綁定來直接設置 attribute 的值。
這是「綁定到目標屬性 (property)」這條規則中惟一的例外。這是惟一的能建立和設置 attribute 的綁定形式。
本章中,通篇都在說經過屬性綁定來設置元素的屬性老是好於用字符串設置 attribute。爲何 Angular 還提供了 attribute 綁定呢?
由於當元素沒有屬性可綁的時候,就必須使用 attribute 綁定。
考慮 ARIA, SVG 和 table 中的 colspan/rowspan 等 attribute。 它們是純粹的 attribute,沒有對應的屬性可供綁定。
若是想寫出相似下面這樣的東西,現狀會令咱們痛苦:
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
會獲得這個錯誤:
Template parse errors: Can't bind to 'colspan' since it isn't a known native property 模板解析錯誤:不能綁定到 'colspan',由於它不是已知的原生屬性
正如提示中所說,<td>
元素沒有colspan
屬性。 可是插值表達式和屬性綁定只能設置屬性,不能設置 attribute。
咱們須要 attribute 綁定來建立和綁定到這樣的 attribute。
attribute 綁定的語法與屬性綁定相似。 但方括號中的部分不是元素的屬性名,而是由attr
前綴,一個點 (.
) 和 attribute 的名字組成。 能夠經過值爲字符串的表達式來設置 attribute 的值。
這裏把[attr.colspan]
綁定到一個計算值:
<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>
這裏是表格渲染出來的樣子:
One-Two | |
Five | Six |
attribute 綁定的主要用例之一是設置 ARIA attribute(譯註:ARIA指可訪問性,用於給殘障人士訪問互聯網提供便利), 就像這個例子中同樣:
<!-- create and set an aria attribute for assistive technology --> <button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
藉助 CSS 類綁定,能夠從元素的class
attribute 上添加和移除 CSS 類名。
CSS 類綁定綁定的語法與屬性綁定相似。 但方括號中的部分不是元素的屬性名,而是由class
前綴,一個點 (.
)和 CSS 類的名字組成, 其中後兩部分是可選的。形如:[class.class-name]
。
下列例子示範瞭如何經過 CSS 類綁定來添加和移除應用的 "special" 類。不用綁定直接設置 attribute 時是這樣的:
<!-- standard class attribute setting --> <div class="bad curly special">Bad curly special</div>
能夠把它改寫爲綁定到所需 CSS 類名的綁定;這是一個或者全有或者全無的替換型綁定。 (譯註:即當 badCurly 有值時 class 這個 attribute 設置的內容會被徹底覆蓋)
<!-- reset/override all class names with a binding --> <div class="bad curly special" [class]="badCurly">Bad curly</div>
最後,能夠綁定到特定的類名。 當模板表達式的求值結果是真值時,Angular 會添加這個類,反之則移除它。
<!-- 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指令 來同時管理多個類名。
經過樣式綁定,能夠設置內聯樣式。
樣式綁定的語法與屬性綁定相似。 但方括號中的部分不是元素的屬性名,而由style
前綴,一個點 (.
)和 CSS 樣式的屬性名組成。 形如:[style.style-property]
。
<button [style.color] = "isSpecial ? 'red': 'green'">Red</button> <button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
有些樣式綁定中的樣式帶有單位。在這裏,以根據條件用 「em」 和 「%」 來設置字體大小的單位。
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button> <button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
雖然這是設置單同樣式的好辦法,但咱們一般更喜歡使用 NgStyle指令 來同時設置多個內聯樣式。
前面遇到的綁定的數據流都是單向的:從組件到元素。
用戶不會只盯着屏幕看。它們會在輸入框中輸入文本。它們會從列表中選取條目。 它們會點擊按鈕。這類用戶動做可能致使反向的數據流:從元素到組件。
知道用戶動做的惟一方式是監聽某些事件,如按鍵、鼠標移動、點擊和觸摸屏幕。 能夠經過 Angular 事件綁定來聲明對哪些用戶動做感興趣。
事件綁定語法由等號左側帶圓括號的目標事件和右側引號中的模板語句組成。 下面事件綁定監聽按鈕的點擊事件。每當點擊發生時,都會調用組件的onSave()
方法。
<button (click)="onSave()">Save</button>
圓括號中的名稱 —— 好比(click)
—— 標記出目標事件。在下面例子中,目標是按鈕的 click 事件。
<button (click)="onSave()">Save</button>
有些人更喜歡帶on-
前綴的備選形式,稱之爲規範形式:
<button on-click="onSave()">On Save</button>
元素事件多是更常見的目標,但 Angular 會先看這個名字是否能匹配上已知指令的事件屬性,就像下面這個例子:
<!-- `myClick` is an event on the custom `ClickDirective` --> <div (myClick)="clickMessage=$event">click with myClick</div>
更多關於該myClick
指令的解釋,見給輸入/輸出屬性起別名。
若是這個名字沒能匹配到元素事件或已知指令的輸出屬性,Angular 就會報「未知指令」錯誤。
在事件綁定中,Angular 會爲目標事件設置事件處理器。
當事件發生時,這個處理器會執行模板語句。 典型的模板語句一般涉及到響應事件執行動做的接收器,例如從 HTML 控件中取得值,並存入模型。
綁定會經過名叫$event
的事件對象傳遞關於此事件的信息(包括數據值)。
事件對象的形態取決於目標事件。若是目標事件是原生 DOM 元素事件, $event
就是 DOM事件對象,它有像target
和target.value
這樣的屬性。
考慮這個範例:
<input [value]="currentHero.firstName" (input)="currentHero.firstName=$event.target.value" >
上面的代碼在把輸入框的value
屬性綁定到firstName
屬性。 要監聽對值的修改,代碼綁定到輸入框的input
事件。 當用戶形成更改時,input
事件被觸發,並在包含了 DOM 事件對象 ($event
) 的上下文中執行這條語句。
要更新firstName
屬性,就要經過路徑$event.target.value
來獲取更改後的值。
若是事件屬於指令(回想一下,組件是指令的一種),那麼$event
具體是什麼由指令決定。
一般,指令使用 Angular EventEmitter 來觸發自定義事件。 指令建立一個EventEmitter
實例,而且把它做爲屬性暴露出來。 指令調用EventEmitter.emit(payload)
來觸發事件,能夠傳入任何東西做爲消息載荷。 父指令經過綁定到這個屬性來監聽事件,並經過$event
對象來訪問載荷。
假設HeroDetailComponent
用於顯示英雄的信息,並響應用戶的動做。 雖然HeroDetailComponent
包含刪除按鈕,但它本身並不知道該如何刪除這個英雄。 最好的作法是觸發事件來報告「刪除用戶」的請求。
下面的代碼節選自HeroDetailComponent
:
template: ` <div> <img src="{{heroImageUrl}}"> <span [style.text-decoration]="lineThrough"> {{prefix}} {{hero?.fullName}} </span> <button (click)="delete()">Delete</button> </div>`
// This component make a request but it can't actually delete a hero. deleteRequest = new EventEmitter<Hero>(); delete() { this.deleteRequest.emit(this.hero); }
組件定義了deleteRequest
屬性,它是EventEmitter
實例。 當用戶點擊刪除時,組件會調用delete()
方法,讓EventEmitter
發出一個Hero
對象。
如今,假設有個宿主的父組件,它綁定了HeroDetailComponent
的deleteRequest
事件。
<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>
當deleteRequest
事件觸發時,Angular 調用父組件的deleteHero
方法, 在$event
變量中傳入要刪除的英雄(來自HeroDetail
)。
deleteHero
方法有反作用:它刪除了一個英雄。 模板語句的反作用不只沒問題,反而正是所指望的。
刪除這個英雄會更新模型,還可能觸發其它修改,包括向遠端服務器的查詢和保存。 這些變動經過系統進行擴散,並最終顯示到當前以及其它視圖中。
咱們常常須要顯示數據屬性,並在用戶做出更改時更新該屬性。
在元素層面上,既要設置元素屬性,又要監聽元素事件變化。
Angular 爲此提供一種特殊的雙向數據綁定語法:[(x)]
。 [(x)]
語法結合了屬性綁定的方括號[x]
和事件綁定的圓括號(x)
。
想象盒子裏的香蕉來記住方括號套圓括號。
當一個元素擁有能夠設置的屬性x
和對應的事件xChange
時,解釋[(x)]
語法就容易多了。 下面的SizerComponent
符合這個模式。它有size
屬性和伴隨的sizeChange
事件:
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);
}
}
size
的初始值是一個輸入值,來自屬性綁定。(譯註:注意size
前面的@Input
) 點擊按鈕,在最小/最大值範圍限制內增長或者減小size
。 而後用調整後的size
觸發sizeChange
事件。
下面的例子中,AppComponent.fontSize
被雙向綁定到SizerComponent
:
<my-sizer [(size)]="fontSizePx"></my-sizer> <div [style.font-size.px]="fontSizePx">Resizable Text</div>
SizerComponent.size
初始值是AppComponent.fontSizePx
。 點擊按鈕時,經過雙向綁定更新AppComponent.fontSizePx
。 被修改的AppComponent.fontSizePx
經過樣式綁定,改變文本的顯示大小。 試一下在線例子。
雙向綁定語法其實是屬性綁定和事件綁定的語法糖。 Angular將SizerComponent
的綁定分解成這樣:
<my-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></my-sizer>
$event
變量包含了SizerComponent.sizeChange
事件的荷載。 當用戶點擊按鈕時,Angular 將$event
賦值給AppComponent.fontSizePx
。
很清楚,比起單獨綁定屬性和事件,雙向數據綁定語法顯得很是方便。
咱們但願能在像<input>
和<select>
這樣的 HTML 元素上使用雙向數據綁定。 惋惜,原生 HTML 元素不遵循x
值和xChange
事件的模式。
幸運的是,Angular 以 NgModel 指令爲橋樑,容許在表單元素上使用雙向數據綁定。
當開發數據輸入表單時,咱們常常但願能顯示數據屬性,並在用戶作出變動時更新該屬性。
使用NgModel
指令進行雙向數據綁定讓它變得更加容易。請看下例:
<input [(ngModel)]="currentHero.firstName">
在使用ngModel
作雙向數據綁定以前,得先導入FormsModule
, 把它加入 Angular 模塊的imports
列表。 學習關於FormsModule
和ngModel
的更多知識,參見表單。
下面展現瞭如何導入FormsModule
,讓[(ngModel)]
變得可用:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
[(ngModel)]
內幕回顧一下firstName
的綁定,值得注意的是,能夠經過分別綁定<input>
元素的value
屬性和`input事件來實現一樣的效果。
<input [value]="currentHero.firstName" (input)="currentHero.firstName=$event.target.value" >
這樣很笨拙。誰能記住哪一個元素屬性用於設置,哪一個用於發出用戶更改? 如何從輸入框中提取出當前顯示的文本,以便更新數據屬性? 誰想每次都去查一遍?
ngModel
指令經過它本身的ngModel
輸入屬性和ngModelChange
輸出屬性隱藏了這些繁瑣的細節。
<input [ngModel]="currentHero.firstName" (ngModelChange)="currentHero.firstName=$event">
ngModel
數據屬性設置元素的 value 屬性,ngModelChange
事件屬性監聽元素 value 的變化。
每種元素的特色各不相同,因此NgModel
指令只能在一些特定表單元素上使用,例如輸入文本框,由於它們支持 ControlValueAccessor。
除非寫一個合適的值訪問器,不然不能把[(ngModel)]
用在自定義組件上。 但值訪問器技術超出了本章的範圍。 對於不能控制其 API 的 Angular 組件或者 Web 組件,可能須要爲其添加 value accessor。
可是對於咱們能控制的 Angular 組件來講,這麼作就徹底沒有必要了。 由於能夠指定值和事件屬性名字來進行基本的 Angular 雙向綁定語法,徹底不用NgModel
。
獨立的ngModel
綁定相比直接綁定元素的原生屬性是個改進,但還能作得更好。
咱們不該該說起數據屬性兩次。Angular 應該能捕捉組件的數據屬性,並用一條聲明來設置它——依靠[(ngModel)]
,能夠這麼作:
<input [(ngModel)]="currentHero.firstName">
[(ngModel)]
就是咱們所需的一切嗎?有沒有什麼理由須要回退到它的展開形式?
[(ngModel)]
語法只能設置一個數據綁定屬性。 若是須要作更多或不一樣的事情,就得本身用它的展開形式。
來作點淘氣的事吧,好比強制讓輸入值變成大寫形式:
<input [ngModel]="currentHero.firstName" (ngModelChange)="setUpperCaseFirstName($event)">
下面是實際操做中的全部變體形式,包括這個大寫版本:
上一版本的 Angular 中包含了超過 70 個內置指令。 社區貢獻了更多,這還沒算爲內部應用而建立的無數私有指令。
在新版的 Angular 中不須要那麼多指令。 使用更強大、更富有表現力的 Angular 綁定系統,其實能夠達到一樣的效果。 若是能用簡單的綁定達到目的,爲何還要建立指令來處理點擊事件呢?
<button (click)="onSave()">Save</button>
咱們仍然能夠從簡化複雜任務的指令中獲益。 Angular 發佈時仍然帶有內置指令,只是沒那麼多了。 咱們仍會寫本身的指令,只是沒那麼多了。
下面來看一下那些最經常使用的內置指令。
咱們常常用動態添加或刪除 CSS 類的方式來控制元素如何顯示。 經過綁定到NgClass
,能夠同時添加或移除多個類。
CSS 類綁定 是添加或刪除單個類的最佳途徑。
<!-- toggle the "special" class on/off with a property --> <div [class.special]="isSpecial">The class binding is special</div>
當想要同時添加或移除多個 CSS 類時,NgClass
指令多是更好的選擇。
綁定到一個 key:value 形式的控制對象,是應用NgClass
的好方式。這個對象中的每一個 key 都是一個 CSS 類名,若是它的 value 是true
,這個類就會被加上,不然就會被移除。
下面的組件方法setClasses
管理了三個 CSS 類的狀態:
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
,根據它來設置此元素的CSS類:
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special</div>
你既能夠在初始化時調用setCurrentClassess()
,也能夠在所依賴的屬性變化時調用。
咱們能夠根據組件的狀態動態設置內聯樣式。 NgStyle
綁定能夠同時設置多個內聯樣式。
樣式綁定是設置單同樣式值的簡單方式。
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" > This div is x-large. </div>
若是要同時設置多個內聯樣式,NgStyle
指令多是更好的選擇。
NgStyle
須要綁定到一個 key:value 控制對象。 對象的每一個 key 是樣式名,它的 value 是能用於這個樣式的任何值。
來看看組件的setCurrentStyles
方法,它會根據另外三個屬性的狀態把組件的currentStyles
屬性設置爲一個定義了三個樣式的對象:
currentStyles: {}; setCurrentStyles() { this.currentStyles = { // CSS styles: set per current state of component properties 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '24px' : '12px' }; }
把NgStyle
屬性綁定到currentStyles
,以據此設置此元素的樣式:
<div [ngStyle]="currentStyles"> This div is initially italic, normal weight, and extra large (24px). </div>
你既能夠在初始化時調用setCurrentStyles()
,也能夠在所依賴的屬性變化時調用。
經過綁定NgIf
指令到真值表達式,能夠把元素子樹(元素及其子元素)添加到 DOM 上。
<div *ngIf="currentHero">Hello, {{currentHero.firstName}}</div>
別忘了ngIf
前面的星號(*
)。 更多信息,見 * 與 <template>。
綁定到假值表達式將從 DOM 中移除元素子樹。
<!-- because of the ngIf guard `nullHero.firstName` never has a chance to fail --> <div *ngIf="nullHero">Hello, {{nullHero.firstName}}</div> <!-- Hero Detail is not in the DOM because isActive is false--> <hero-detail *ngIf="isActive"></hero-detail>
咱們能夠經過類綁定或樣式綁定來顯示和隱藏元素子樹(元素及其子元素)。
<!-- isSpecial is true --> <div [class.hidden]="!isSpecial">Show with class</div> <div [class.hidden]="isSpecial">Hide with class</div> <!-- HeroDetail is in the DOM but hidden --> <hero-detail [class.hidden]="isSpecial"></hero-detail> <div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div> <div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>
隱藏子樹和用NgIf
排除子樹是大相徑庭的。
當隱藏子樹時,它仍然留在 DOM 中。 子樹中的組件及其狀態仍然保留着。 即便對於不可見屬性,Angular 也會繼續檢查變動。 子樹可能佔用至關可觀的內存和運算資源。
當NgIf
爲false
時,Angular 從 DOM 中物理地移除了這個元素子樹。 它銷燬了子樹中的組件及其狀態,也潛在釋放了可觀的資源,最終讓用戶體驗到更好的性能。
顯示 / 隱藏技術用在小型元素樹上可能還不錯。 但在隱藏大樹時咱們得當心;NgIf
多是更安全的選擇。但要記住:永遠得先測量,再下結論。
當須要從一組可能的元素樹中根據條件顯示一個時,咱們就把它綁定到NgSwitch
。 Angular 將只把選中的元素樹放進 DOM 中。
下面是例子:
<span [ngSwitch]="toeChoice"> <span *ngSwitchCase="'Eenie'">Eenie</span> <span *ngSwitchCase="'Meanie'">Meanie</span> <span *ngSwitchCase="'Miney'">Miney</span> <span *ngSwitchCase="'Moe'">Moe</span> <span *ngSwitchDefault>other</span> </span>
咱們把做爲父指令的NgSwitch
綁定到能返回開關值的表達式。 本例中,這個值是字符串,但它也能夠是任何類型的值。
這個例子中,父指令NgSwitch
控制一組<span>
子元素。 每一個<span>
或者掛在匹配值表達式上,或者被標記爲默認狀況。
任什麼時候候,這些 span 中最多隻有一個會出如今 DOM 中。
若是這個 span 的匹配值等於開關值,Angular 就把這個<span>
添加到 DOM 中。 若是沒有任何 span 匹配上,Angular 就把默認的 span 添加到 DOM 中。 Angular 會移除並銷燬全部其它的 span。
能夠用任何其它元素代替本例中的 span。 那個元素能夠是帶有巨大子樹的<div>
。 只有匹配的<div>
和它的子樹會顯示在 DOM 中,其它的則會被移除。
這裏有三個相互合做的指令:
ngSwitch
:綁定到返回開關值的表達式
ngSwitchCase
:綁定到返回匹配值的表達式
ngSwitchDefault
:用於標記出默認元素的 attribute
不要在ngSwitch
的前面加星號 (*
),而應該用屬性綁定。
要把星號 (*
) 放在ngSwitchCase
和ngSwitchDefault
的前面。 要了解更多信息,見 * 與 <template>。
NgFor
是一個重複器指令 —— 自定義數據顯示的一種方式。
咱們的目標是展現一個由多個條目組成的列表。首先定義了一個 HTML 塊,它規定了單個條目應該如何顯示。 再告訴 Angular 把這個塊當作模板,渲染列表中的每一個條目。
下例中,NgFor
應用在一個簡單的<div>
上:
<div *ngFor="let hero of heroes">{{hero.fullName}}</div>
也能夠把NgFor
應用在一個組件元素上,就下例這樣:
<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>
不要忘了ngFor
前面的星號 (*
)。 更多信息,見 * 與 <template>
賦值給*ngFor
的文本是用於指導重複器如何工做的指令。
賦值給*ngFor
的字符串不是模板表達式。 它是一個微語法 —— 由 Angular 本身解釋的小型語言。在這個例子中,字符串"let hero of heroes"
的含義是:
取出
heroes
數組中的每一個英雄,把它存入局部變量hero
中,並在每次迭代時對模板 HTML 可用
Angular 把這個指令翻譯成一組元素和綁定。
在前面的兩個例子中,ngFor
指令在heroes
數組上進行迭代(它是由父組件的heroes
屬性返回的), 以其所在的元素爲模板「衝壓」出不少實例。 Angular 爲數組中的每一個英雄建立了此模板的一個全新實例。
hero
前面的let
關鍵字建立了名叫hero
的模板輸入變量。
模板輸入變量和模板引用變量不是一回事!
在模板中使用這個變量來訪問英雄的屬性,就像在插值表達式中所作的那樣。 也能夠把這個變量傳給組件元素上的綁定,就像對hero-detail
所作的那樣。
ngFor
指令支持可選的index
,它在迭代過程當中會從 0 增加到「數組的長度」。 能夠經過模板輸入變量來捕獲這個 index,並在模板中使用。
下例把 index 捕獲到名叫i
的變量中,使用它「衝壓出」像 "1 - Hercules Son of Zeus" 這樣的條目。
<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.fullName}}</div>
要學習更多的相似 index 的值,例如last
、even
和odd
,請參閱 NgFor API 參考。
ngFor
指令有時候會性能較差,特別是在大型列表中。 對一個條目的一丁點改動、移除或添加,都會致使級聯的 DOM 操做。
例如,咱們能夠經過從新從服務器查詢來刷新英雄列表。 刷新後的列表可能包含不少(若是不是所有的話)之前顯示過的英雄。
咱們知道這一點,是由於每一個英雄的id
沒有變化。 但在 Angular 看來,它只是一個由新的對象引用構成的新列表, 它沒有選擇,只能清理舊列表、捨棄那些 DOM 元素,而且用新的 DOM 元素來重建一個新列表。
若是給它一個追蹤函數,Angular 就能夠避免這種折騰。 追蹤函數告訴 Angular:咱們知道兩個具備相同hero.id
的對象實際上是同一個英雄。 下面就是這樣一個函數:
trackByHeroes(index: number, hero: Hero) { return hero.id; }
如今,把NgForTrackBy
指令設置爲那個追蹤函數。
<div *ngFor="let hero of heroes; trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>
追蹤函數不會阻止全部 DOM 更改。 若是同一個英雄的屬性變化了,Angular 就可能不得不更新DOM元素。 可是若是這個屬性沒有變化 —— 並且大多數時候它們不會變化 —— Angular 就能留下這些 DOM 元素。列表界面就會更加平滑,提供更好的響應。
這裏是關於NgForTrackBy
效果的插圖。
當審視NgFor
、NgIf
和NgSwitch
這些內置指令時,咱們使用了一種古怪的語法:出如今指令名稱前面的星號 (*
)。
*
是一種語法糖,它讓那些須要藉助模板來修改 HTML 佈局的指令更易於讀寫。 NgFor
、NgIf
和NgSwitch
都會添加或移除元素子樹,這些元素子樹被包裹在<template>
標籤中。
咱們沒有看到<template>
標籤,那是由於這種*
前綴語法讓咱們忽略了這個標籤, 而把注意力直接聚焦在所要包含、排除或重複的那些 HTML 元素上。
這一節,將深刻研究一下,看看 Angular 是怎樣扒掉這個*
,把這段 HTML 展開到<template>
標籤中的。
*ngIf
咱們能夠像 Angular 同樣,本身把*
前綴語法展開成 template 語法,這裏是*ngIf
的一些代碼:
<hero-detail *ngIf="currentHero" [hero]="currentHero"></hero-detail>
currentHero
被引用了兩次,第一次是做爲NgIf
的真 / 假條件,第二次把實際的 hero 值傳給了HeroDetailComponent
。
展開的第一步是把ngIf
(沒有*
前綴)和它的內容傳給表達式,再賦值給template
指令。
<hero-detail template="ngIf:currentHero" [hero]="currentHero"></hero-detail>
下一步,也是最後一步,是把 HTML 包裹進<template>
標籤和[ngIf]
屬性綁定中:
<template [ngIf]="currentHero"> <hero-detail [hero]="currentHero"></hero-detail> </template>
注意,[hero]="currengHero"
綁定留在了模板中的子元素<hero-detail>
上。
不要誤寫爲ngIf="currentHero"
! 這種語法會把一個字符串"currentHero"賦值給ngIf
。 在 JavaScript 中,非空的字符串是真值,因此ngIf
總會是true
,而 Angular 將永遠顯示hero-detail
…… 即便根本沒有currentHero
!
*ngSwitch
相似的轉換也適用於*ngSwitch
。咱們能夠本身解開這個語法糖。 下例中,首先是*ngSwitchCase
和*ngSwitchDefault
,而後再解出<template>
標籤:
<span [ngSwitch]="toeChoice"> <!-- with *NgSwitch --> <span *ngSwitchCase="'Eenie'">Eenie</span> <span *ngSwitchCase="'Meanie'">Meanie</span> <span *ngSwitchCase="'Miney'">Miney</span> <span *ngSwitchCase="'Moe'">Moe</span> <span *ngSwitchDefault>other</span> <!-- with <template> --> <template [ngSwitchCase]="'Eenie'"><span>Eenie</span></template> <template [ngSwitchCase]="'Meanie'"><span>Meanie</span></template> <template [ngSwitchCase]="'Miney'"><span>Miney</span></template> <template [ngSwitchCase]="'Moe'"><span>Moe</span></template> <template ngSwitchDefault><span>other</span></template> </span>
*ngSwitchWhen
和*ngSwitchDefault
用和*ngIf
徹底相同的方式展開,把它們之前的元素包裹在<template>
標籤中。
如今,應該明白爲何ngSwitch
自己不能用星號 (*) 前綴了吧? 它沒有定義內容,它的工做是控制一組模板。
上面這種狀況下,它管理兩組NgSwitchCase
和NgSwitchDefault
指令,一次是 (*) 前綴的版本,一次是展開模板後的版本。 咱們也期待它顯示所選模板的值兩次。這正是在這個例子中看到的:
*ngFor
*ngFor
也經歷相似的轉換。從一個*ngFor
的例子開始:
<hero-detail *ngFor="let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>
這裏是在把ngFor
傳進template
指令後的同一個例子:
<hero-detail template="ngFor let hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>
下面,它被進一步擴展成了包裹着原始<hero-detail>
元素的<template>
標籤:
<template ngFor let-hero [ngForOf]="heroes" [ngForTrackBy]="trackByHeroes"> <hero-detail [hero]="hero"></hero-detail> </template>
NgFor
的代碼相對NgIf
更復雜一點,由於重複器有更多活動部分須要配置。 這種狀況下,咱們就得記住添加NgForOf
指令和NgForTrackBy
指令,並對它們賦值。 使用*ngFor
語法比直接寫這些展開後的 HTML 自己要簡單多了。
模板引用變量是模板中對 DOM 元素或指令的引用。
它能在原生 DOM 元素中使用,也能用於 Angular 組件 —— 實際上,它能夠和任何自定義 Web 組件協同工做。
能夠在同一元素、兄弟元素或任何子元素中引用模板引用變量。
不要在同一個模版中屢次定義相同變量名,不然運行時的值將會不可預測。
這裏是關於建立和使用模板引用變量的另外兩個例子:
<!-- phone refers to the input element; pass its `value` to an event handler --> <input #phone placeholder="phone number"> <button (click)="callPhone(phone.value)">Call</button> <!-- fax refers to the input element; pass its `value` to an event handler --> <input ref-fax placeholder="fax number"> <button (click)="callFax(fax.value)">Fax</button>
"phone" 的井號 (#
) 前綴表示定義了一個phone
變量。
有些人不喜歡使用#
字符,而是使用它的規範形式:ref-
前綴。 例如,既能用#phone
,也能用ref-phone
來定義phone
變量。
Angular 把這種變量的值設置爲它所在的那個元素。 在這個input
元素上定義了這些變量。 把那些input
元素對象傳給 button 元素,在事件綁定中,它們做爲參數傳給了call
方法。
讓咱們看看最後一個例子:表單,使用模板引用變量的典範。
正如在表單一章中所見過的,表單的 HTML 能夠作得至關複雜。 下面是簡化過的範例 —— 雖然仍算不上多簡單。
<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm"> <div class="form-group"> <label for="name">Name</label> <input class="form-control" name="name" required [(ngModel)]="currentHero.firstName"> </div> <button type="submit" [disabled]="!theForm.form.valid">Submit</button> </form>
模板引用變量theForm
在這個例子中出現了三次,中間隔着一大段 HTML。
<form (ngSubmit)="onSubmit(theForm)" #theForm="ngForm"> <button type="submit" [disabled]="!theForm.form.valid">Submit</button> </form>
theForm
變量的值是什麼?
若是 Angular 沒有接管它,那它多是個HTMLFormElement。 實際上它是個ngForm
,對 Angular 內置指令NgForm
的引用。 它包裝了原生的HTMLFormElement
並賦予它更多超能力,好比跟蹤用戶輸入的有效性。
這解釋了該如何經過檢查theForm.form.valid
來禁用提交按鈕,以及如何把一個信息量略大的對象傳給父組件的onSubmit
方法。(譯註:onSubmit
方法可能會出發事件,被父組件監聽,參見下面的輸入和輸出屬性
和父組件監聽子組件的事件。)
迄今爲止,咱們主要聚焦在綁定聲明的右側,學習如何在模板表達式和模板語句中綁定到組件成員。 當成員出如今這個位置上,則稱之爲數據綁定的源。
本節則專一於綁定到的目標,它位於綁定聲明中的左側。 這些指令的屬性必須被聲明成輸入或輸出。
記住:全部組件皆爲指令。
咱們要重點突出下綁定目標和綁定源的區別。
綁定的目標是在=
左側的部分, 源則是在=
右側的部分。
綁定的目標是綁定符:[]
、()
或[()]
中的屬性或事件名, 源則是引號 (" "
) 中的部分或插值符號 ({{}}
) 中的部分。
源指令中的每一個成員都會自動在綁定中可用。 不須要特別作什麼,就能在模板表達式或語句中訪問指令的成員。
訪問目標指令中的成員則受到限制。 只能綁定到那些顯式標記爲輸入或輸出的屬性。
在下面的例子中,iconUrl
和onSave
是組件的成員,它們在=
右側引號語法中被引用了。
<img [src]="iconUrl"/> <button (click)="onSave()">Save</button>
它們既不是組件的輸入也不是輸出。它們是綁定的數據源。
如今,看看HeroDetailComponent
,它是綁定的目標。
<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)"> </hero-detail>
HeroDetailComponent.hero
和HeroDetailComponent.deleteRequest
都在綁定聲明的左側。 HeroDetailComponent.hero
在方括號中,它是屬性綁定的目標。 HeroDetailComponent.deleteRequest
在圓括號中,它是事件綁定的目標。
目標屬性必須被顯式的標記爲輸入或輸出。
當咱們深刻HeroDetailComponent
內部時,就會看到這些屬性被裝飾器標記成了輸入和輸出屬性。
@Input() hero: Hero; @Output() deleteRequest = new EventEmitter<Hero>();
另外,還能夠在指令元數據的inputs
或outputs
數組中標記出這些成員。好比這個例子:
@Component({ inputs: ['hero'], outputs: ['deleteRequest'], })
既能夠經過裝飾器,也能夠經過元數據數組來指定輸入/輸出屬性。但別同時用!
輸入屬性一般接收數據值。 輸出屬性暴露事件生產者,如EventEmitter
對象。
輸入和輸出這兩個詞是從目標指令的角度來講的。
從HeroDetailComponent
角度來看,HeroDetailComponent.hero
是個輸入屬性, 由於數據流從模板綁定表達式流入那個屬性。
從HeroDetailComponent
角度來看,HeroDetailComponent.deleteRequest
是個輸出屬性, 由於事件從那個屬性流出,流向模板綁定語句中的處理器。
給輸入/輸出屬性起別名
有時須要讓輸入/輸出屬性的公開名字不一樣於內部名字。
這是使用 attribute 指令時的常見狀況。 指令的使用者指望綁定到指令名。例如,在<div>
上用myClick
選擇器應用指令時, 但願綁定的事件屬性也叫myClick
。
<div (myClick)="clickMessage=$event">click with myClick</div>
然而,在指令類中,直接用指令名做爲本身的屬性名一般都不是好的選擇。 指令名不多能描述這個屬性是幹嗎的。 myClick
這個指令名對於用來發出 click 消息的屬性就算不上一個好名字。
幸運的是,可使用約定俗成的公開名字,同時在內部使用不一樣的名字。 在上面例子中,其實是把myClick
這個別名指向了指令本身的clicks
屬性。
把別名傳進@Input/@Output裝飾器,就能夠爲屬性指定別名,就像這樣:
@Output('myClick') clicks = new EventEmitter<string>(); // @Output(alias) propertyName = ...
也可在inputs
和outputs
數組中爲屬性指定別名。 能夠寫一個冒號 (:
) 分隔的字符串,左側是指令中的屬性名,右側則是公開的別名。
@Directive({ outputs: ['clicks:myClick'] // propertyName:alias })
模板表達式語言使用了 JavaScript 語法的子集,並補充了幾個用於特定場景的特殊操做符。 下面介紹其中的兩個:管道和安全導航操做符。
在綁定以前,表達式的結果可能須要一些轉換。例如,可能但願把數字顯示成金額、強制文本變成大寫,或者過濾列表以及進行排序。
Angular 管道對像這樣的小型轉換來講是個明智的選擇。 管道是一個簡單的函數,它接受一個輸入值,並返回轉換結果。 它們很容易用於模板表達式中,只要使用管道操做符 (|
) 就好了。
<div>Title through uppercase pipe: {{title | uppercase}}</div>
管道操做符會把它左側的表達式結果傳給它右側的管道函數。
還能夠經過多個管道串聯表達式:
<!-- Pipe chaining: convert title to uppercase, then to lowercase --> <div> Title through a pipe chain: {{title | uppercase | lowercase}} </div>
還能對它們使用參數:
<!-- pipe with configuration argument => "February 25, 1970" --> <div>Birthdate: {{currentHero?.birthdate | date:'longDate'}}</div>
json
管道對調試綁定特別有用:
<div>{{currentHero | json}}</div>
它生成的輸出是相似於這樣的:
{ "firstName": "Hercules", "lastName": "Son of Zeus", "birthdate": "1970-02-25T08:00:00.000Z", "url": "http://www.imdb.com/title/tt0065832/", "rate": 325, "id": 1 }
Angular 的安全導航操做符 (?.
) 是一種流暢而便利的方式,用來保護出如今屬性路徑中 null 和 undefined 值。 下例中,當currentHero
爲空時,保護視圖渲染器,讓它免於失敗。
The current hero's name is {{currentHero?.firstName}}
咱們來詳細闡述一下這個問題和解決方案:
若是下列數據綁定中title
屬性爲空,會發生什麼?
The title is {{title}}
這個視圖仍然被渲染出來,可是顯示的值是空;只能看到 「The title is」,它後面卻沒有任何東西。 這是合理的行爲。至少應用沒有崩潰。
假設模板表達式涉及屬性路徑,在下例中,顯示一個空 (null) 英雄的firstName
。
The null hero's name is {{nullHero.firstName}}
JavaScript 拋出了空引用錯誤,Angular 也是如此:
TypeError: Cannot read property 'firstName' of null in [null].
暈,整個視圖都不見了。
若是確信hero
屬性永遠不可能爲空,能夠聲稱這是合理的行爲。 若是它必須不能爲空,但它仍然是空值,其實是製造了一個編程錯誤,它應該被捕獲和修復。 這種狀況應該拋出異常。
另外一方面,屬性路徑中的空值可能會時常發生,特別是當咱們知道數據最終會出現。
當等待數據的時候,視圖渲染器不該該抱怨,而應該把這個空屬性路徑顯示爲空白,就像上面title
屬性那樣。
不幸的是,當currentHero
爲空的時候,應用崩潰了。
能夠經過寫NgIf代碼來解決這個問題。
<!--No hero, div not displayed, no error --> <div *ngIf="nullHero">The null hero's name is {{nullHero.firstName}}</div>
或者能夠嘗試經過&&
來把屬性路徑的各部分串起來,讓它在遇到第一個空值的時候,就返回空。
The null hero's name is {{nullHero && nullHero.firstName}}
這些方法都有價值,可是會顯得笨重,特別是當這個屬性路徑很是長的時候。 想象一下在一個很長的屬性路徑(如a.b.c.d
)中對空值提供保護。
Angular 安全導航操做符 (?.
) 是在屬性路徑中保護空值的更加流暢、便利的方式。 表達式會在它遇到第一個空值的時候跳出。 顯示是空的,但應用正常工做,而沒有發生錯誤。
<!-- No hero, no problem! --> The null hero's name is {{nullHero?.firstName}}
在像a?.b?.c?.d
這樣的長屬性路徑中,它工做得很完美。