Angular4-學習筆記-4-模板語法

學習資料來自 Angular.cnAngular.io。

模板語法

在線例子

在 Angular 中,組件扮演著控制器或視圖模型的角色,模板則扮演視圖的角色。

模板中的 HTML

HTML 是 Angular 模板的語言。

<h1>Hello Angular</h1>

為防范腳本注入攻擊的風險,<script> 元素被禁用了(忽略了)。更多內容參見安全。

<html>、<body><base> 元素在此沒有使用的意義。

插值表達式 (Interpolation) {{...}}

插值表達式可以把計算后的字符串插入到 HTML 元素標簽內的文本或對標簽的屬性進行賦值。

一般來說,括號間的素材是一個模板表達式,Angular 先對它求值,再把它轉換成字符串。

<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}</p>

模板表達式 (Template expressions)

模板表達式產生一個值。Angular 執行這個表達式,并把它賦值給綁定目標的屬性,這個綁定目標可能是 HTML 元素、組件或指令。

模板表達式不支持的內容包括:

  • 賦值 (=, +=, -=, ...)
  • new 運算符
  • 使用 ;, 的鏈式表達式
  • 自增或自減操作符 (++--)
  • 不支持位運算 |&
  • 具有新的模板表達式運算符,比如 |?.
  • 不能引用全局命名空間中的任何東西。 不能引用 windowdocument。不能調用 console.logMath.max。

表達式上下文 (Expression context)

典型的表達式上下文是組件實例。
The expression context is typically the component instance.

下例中 titleisUnchanged 均為 AppComponent 的屬性。

{{title}}
<span [hidden]="isUnchanged">changed</span>

表達式的上下文可以包括組件之外的對象。 比如模板輸入變量 (let hero)和模板引用變量(#heroInput)就是備選的上下文對象之一。

<div *ngFor="let hero of heroes">{{hero.name}}</div>
<input #heroInput> {{heroInput.value}}

用術語來說,表達式上下文由模版變量,指令上下文對象(如果存在)和組件成員混合而成。如果所引用的名稱在上述命名空間中有沖突,那么將會按照模板變量,指令上下文和組件成員的順序優先選取。

上例展示了一個命名沖突。組件有一個 hero 屬性,*ngFor 定義了一個 hero 模版變量。{{hero.name}} 中的 hero 指的是模板變量,而非組件屬性。

模板表達式被局限于只能訪問來自表達式上下文中的成員。

表達式指南 (Expression guidelines)

模板表達式能成就或毀掉一個應用。應遵循原則:

1. 沒有可見的副作用

模板表達式除了目標屬性的值以外,不應該改變應用的任何狀態。這條規則是 Angular “單向數據流”策略的基礎。在一次單獨的渲染過程中,視圖應該總是穩定的。

2. 執行迅速

Angular 執行模板表達式比我們想象的頻繁。當計算代價較高時,應該考慮緩存那些從其它值計算得出的值。

TODO: 如何緩存?

3. 非常簡單

模板表達式應保持簡單,不要過于復雜。應在組件中實現應用和業務邏輯,使開發和測試變得更容易。

4. 冪等性

使用冪等的表達式沒有副作用,并且能提升 Angular 變更檢測的性能。
在 Angular 的術語中,冪等的表達式應該總是返回完全相同的東西,直到某個依賴值發生改變。

模板語句 (Template statements)

模板語句用來響應由綁定目標(如 HTML 元素、組件或指令)觸發的事件。
A template statement responds to an event raised by a binding target such as an element, component, or directive.

語法:(event)="statement"

<button (click)="deleteHero()">Delete hero</button>

模板語句有副作用。這是事件的關鍵所在,可以根據用戶的活動更新應用狀態。
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.

模板語句解析器 (template statement parser) 和模板表達式解析器 (template expression parser) 有所不同,特別之處在于它支持基本賦值 (=) 和表達式鏈 ( ;, )。

某些 JavaScript 語法仍然是不允許的:

  • new 運算符
  • 自增和自減運算符:++--
  • 操作并賦值,例如 +=-=
  • 位操作符 |&
  • 模板表達式運算符

語句上下文 (Statement context)

和表達式中一樣,語句只能引用語句上下文中——通常是正在綁定事件的那個組件實例。

語句上下文可以引用模板自身上下文中的屬性。下例把模板的 $event 對象、模板輸入變量(let hero)和模板引用變量(#heroForm)傳給了組件中的一個事件處理器方法。

<button (click)="onSave($event)">Save</button>
<button *ngFor="let hero of heroes" (click)="deleteHero(hero)">{{hero.name}}</button>
<form #heroForm (ngSubmit)="onSubmit(heroForm)"> ... </form>

模板語句不能引用全局命名空間的任何東西。比如不能引用 windowdocument,也不能調用 console.logMath.max。

語句指南 (Expression guidelines)

和表達式一樣,避免寫復雜的模板語句。常規是函數調用或者屬性賦值。

綁定語法:概覽

綁定的類型可以根據數據流的方向分成三類:

  • 從數據源到視圖(source-to-view)
  • 從視圖到數據源(view-to-source)
  • 雙向的從視圖到數據源再到視圖(view-to-source-to-view)。

單向 source-to-view

語法 綁定類型
{{expression}} 插值表達式
[target]="expression" / bind-target="expression" Property, Attribute 類樣式

單向 view-to-source

語法 綁定類型
(target)="statement" / on-target="statement" 事件

雙向

語法 綁定類型
[(target)]="expression" / bindon-target="expression" 雙向

綁定類型(插值表達式除外)有一個目標名,它位于等號左邊,它是 Property 的名字(注意它不是 Attribute 的名字)。

新的思維模型

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。
<!-- Bind button disabled state to `isUnchanged` property -->
<button [disabled]="isUnchanged">Save</button>

一旦開始數據綁定,就不再跟 HTML attribute 打交道了。這里不是設置 attribute,而是設置 DOM 元素、組件和指令的 property。

attribute 初始化 DOM property,然后它們的任務就完成了。property 的值可以改變,是當前值;attribute 的值不能改變,是初始值。

即使名字相同,HTML attribute 和 DOM property 也不是同一樣東西。

模板綁定是通過 property 和事件來工作的,而不是 attribute。在 Angular 的世界中,attribute 唯一的作用是用來初始化元素和指令的狀態。 當進行數據綁定時,只是在與元素和指令的 property 和事件打交道。

綁定目標 (Binding targets)

數據綁定的目標是 DOM 中的某些東西。

Property 綁定類型

目標 范例
Element property <img [src]="heroImageUrl">
Component property <hero-detail [hero]="currentHero"></hero-detail>
Directive property <div [ngClass]="{special: isSpecial}"></div>

事件綁定類型

目標 范例
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>

雙向綁定類型

目標 范例
Event and property <input [(ngModel)]="name">

Attribute 綁定類型

目標 范例
Attribute(例外情況) <button [attr.aria-label]="help">help</button>

CSS 類綁定類型

目標 范例
class property <div [class.special]="isSpecial">Special</div>

樣式綁定類型

目標 范例
style property <button [style.color]="isSpecial ? 'red' : 'green'">

屬性綁定 (Property binding) [property]

當要把視圖元素的屬性 (property) 設置為模板表達式時,就要寫模板的屬性 (property) 綁定。

最常用的屬性綁定是把元素屬性設置為組件屬性的值。

<img [src]="heroImageUrl">

上例說明:image 元素的 src 屬性會被綁定到組件的 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)

人們經常把屬性綁定描述成單向數據綁定,因為值的流動是單向的,從組件的數據屬性流動到目標元素的屬性。

注意:

  • 不能使用屬性綁定來從目標元素拉取值
  • 不能綁定到目標元素的屬性來讀取它。只能設置它。
  • 也不能使用屬性 綁定 來調用目標元素上的方法。

如果必須讀取目標元素上的屬性或調用它的某個方法, 參見 API 參考手冊中的 ViewChildContentChild。

綁定目標

兩種寫法:

<img [src]="heroImageUrl">

![](heroImageUrl)

bind- 前綴的寫法被稱為規范形式 (canonical form)。

元素屬性可能是最常見的綁定目標,但 Angular 會先去看這個名字是否是某個已知指令的屬性名。

<div [ngClass]="classes">[ngClass] binding to the classes property</div>

嚴格來說,Angular 正在匹配指令的輸入屬性的名字。 這個名字是指令的 inputs 數組中所列的名字,或者是帶有 @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.

如果名字沒有匹配上已知指令或元素的屬性,Angular 就會報告“未知指令”的錯誤。

消除副作用

一般建議是,只綁定數據屬性和那些只返回值而不做其它事情的方法。

返回恰當的類型

模板表達式應該返回目標屬性所需類型的值。

別忘了方括號

一次性字符串初始化

當滿足下列條件時,應該省略括號:

  • 目標屬性接受字符串值。
  • 字符串是個固定值,可以直接合并到模塊中。
  • 這個初始值永不改變。
<hero-detail prefix="You are my" [hero]="currentHero"></hero-detail>

屬性綁定還是插值表達式?

在多數情況下,插值表達式是更方便的備選項。實際上,在渲染視圖之前,Angular 把這些插值表達式翻譯成相應的屬性綁定。

<p>![]({{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>

當要渲染的數據類型是字符串時,基于可讀性考慮,建議使用插值表達式。其他情況則必須使用屬性綁定。

內容安全

不管是插值表達式還是屬性綁定,都不會允許帶有 script 標簽的 HTML 泄漏到瀏覽器中。
插值表達式處理 script 標簽與屬性綁定有所不同,但是二者都只渲染沒有危害的內容。

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 綁定

語法:[attr.attribute-name]

可以通過 attribute 綁定來直接設置 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',因為它不是已知的原生屬性)

正確的寫法:

<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 綁定的主要用例之一是設置 ARIA attribute

ARIA 指可訪問性,用于給殘障人士訪問互聯網提供便利。

<!-- create and set an aria attribute for assistive technology -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>

CSS 類綁定

語法:[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 指令來同時管理多個類名。

樣式綁定

語法:[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 指令來同時設置多個內聯樣式。

事件綁定 (Event binding) (event)

語法:(目標事件)="模版語句"
(target event)="template statement"

<button (click)="onSave()">Save</button>

目標事件

元素事件可能是更常見的目標,但 Angular 會先看這個名字是否能匹配上已知指令的事件屬性,如:

<!-- `myClick` is an event on the custom `ClickDirective` -->
<div (myClick)="clickMessage=$event" clickable>click with myClick</div>

如果這個名字沒能匹配到元素事件或已知指令的輸出屬性,Angular 就會報“未知指令”錯誤。

$event 和事件處理語句(event handling statements)

在事件綁定中,Angular 會為目標事件設置事件處理器。當事件發生時,這個處理器會執行模板語句。典型的模板語句通常涉及到響應事件執行動作的接收器,例如從 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.

綁定會通過名叫 $event 的事件對象傳遞關于此事件的信息(包括數據值)。

事件對象的形態取決于目標事件。
The shape of the event object is determined by the target event.

如果目標事件是原生 DOM 元素事件, $event 就是 DOM事件對象,它有像 targettarget.value 這樣的屬性。

<input [value]="currentHero.name"
       (input)="currentHero.name=$event.target.value" >

如果事件屬于指令,那么 $event 具體是什么由指令決定。

使用 EventEmitter 實現自定義事件

通常,指令使用 EventEmitter 來觸發自定義事件。指令創建一個 EventEmitter 實例,并且把它作為屬性暴露出來。指令調用 EventEmitter.emit(payload) 來觸發事件,可以傳入任何東西作為消息載荷。 父指令通過綁定到這個屬性來監聽事件,并通過 $event 對象來訪問載荷。

示例

假設 HeroDetailComponent 用于顯示英雄的信息,并響應用戶的動作。 雖然 HeroDetailComponent 包含刪除按鈕,但它自己并不知道該如何刪除這個英雄。 最好的做法是觸發事件來報告“刪除用戶”的請求。

src/app/hero-detail.component.ts (template)

template: `
<div>
  ![]({{heroImageUrl}})
  <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);
}

說明:組件定義了 deleteRequest 屬性,它是 EventEmitter 實例。 當用戶點擊刪除時,組件會調用 delete() 方法,讓 EventEmitter 發出一個 Hero 對象。

現在,假設有個宿主的父組件,它綁定了 HeroDetailComponentdeleteRequest 事件。

<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>

deleteRequest 事件觸發時,Angular 調用父組件的 deleteHero 方法, 在 $event 變量中傳入要刪除的英雄(來自 HeroDetail)。

模板語句有副作用

模板語句的副作用不僅沒問題,反而正是所期望的。

雙向數據綁定 (Two-way binding) [(...)]

語法:[(x)]

[(x)] 語法結合了屬性綁定的方括號 [x] 和事件綁定的圓括號 (x)。當一個元素擁有可以設置的屬性 x 和對應的事件 xChange 時,就可以使用 [(x)] 語法 。

示例 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>

說明:$event 變量包含了 SizerComponent.sizeChange 事件的荷載。 當用戶點擊按鈕時,Angular 將 $event 賦值給 AppComponent.fontSizePx

內置指令

內置屬性型指令(Built-in attribute directives)

屬性型指令會監聽和修改其它 HTML 元素或組件的行為、元素 Attribute、DOM Property。 它們通常會作為 HTML Attribute 的名稱而應用在元素上。

常見的內置屬性型指令:

  • NgClass 添加或移除一組CSS類
  • NgStyle 添加或移除一組CSS樣式
  • NgModel 雙向綁定到 HTML 表單元素

NgClass

示例:組件方法 setCurrentClasses 可以把組件的屬性 currentClasses 設置為一個對象,它將會根據三個其它組件的狀態為 truefalse 而添加或移除三個類。

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

ngStyle 需要綁定到一個 key:value 控制對象。 對象的每個 key 是樣式名,它的 value 是能用于這個樣式的任何值。

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 需要導入 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 { }

更多關于 FormsModulengModel 的內容參見表單。

使用 ngModel 實現雙向數據綁定。

<input [(ngModel)]="currentHero.name">

該語句實際上隱藏了其實現細節:

<input [ngModel]="currentHero.name"
       (ngModelChange)="currentHero.name=$event">

如果需要做一些不同的處理,就不能使用 [(ngModel)] 語法,而應寫成擴展的方式:

<input [ngModel]="currentHero.name"
       (ngModelChange)="setUppercaseName($event)">

ngModel 指令只能用在支持 ControlValueAccessor 的元素上。

內置結構型指令(Built-in structural directives)

結構型指令負責 HTML 布局。

常見的內置結構型指令:

  • NgIf conditionally add or remove an element from the DOM
  • NgFor repeat a template for each item in a list
  • NgSwitch a 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 前面的星號( * )。

這和顯示/隱藏不是一回事。

我們也可以通過類綁定或樣式綁定來顯示或隱藏一個元素。但隱藏子樹和用 NgIf 排除子樹是截然不同的。

當隱藏子樹時,它仍然留在 DOM 中。當 NgIffalse 時 Angular 從 DOM 中物理地移除了子樹,它銷毀了子樹中的組件及其狀態,也潛在釋放了可觀的資源。

防范空指針錯誤

NgIf 指令通常會用來防范空指針錯誤。 而顯示/隱藏的方式是無法防范的。

<div *ngIf="currentHero">Hello, {{currentHero.name}}</div>
<div *ngIf="nullHero">Hello, {{nullHero.name}}</div>

currentHero 的名字只有當存在 currentHero 時才會顯示出來。 而 nullHero 永遠不會顯示。

NgFor

NgFor 是一個重復器指令——顯示列表項的一種方式。你先定義了一個 HTML 塊,它規定了單個條目應該如何顯示。然后你告訴 Angular 把這個塊當做模板,渲染列表中的每個條目。
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 前面的星號( * )。

賦值給 *ngFor 的字符串不是模板表達式,它是一個微語法 —— 由 Angular 自己解釋的小型語言。

字符串 "let hero of heroes" 的含義:

取出 heroes 數組中的每個英雄,把它存入局部變量 hero 中,并在每次迭代時對模板 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>

更多內容參見 NgFor API

*ngFor with trackBy

ngFor 指令有時候會性能較差,特別是在大型列表中。對一個條目的一丁點改動、移除或添加,都會導致級聯的 DOM 操作。

有了 trackBy,則只有 id 發生改變才會觸發元素替換。

在組件中添加方法:

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 由三個指令組成:

  • 屬性型指令 NgSwitch
  • 結構型指令 NgSwitchCase
  • 結構型指令 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) 時尤其有用。

模板引用變量 (Template reference variables) #var

模板引用變量通常是一個模版中的對 DOM 元素的一個引用。
A template reference variable is often a reference to a DOM element within a template.

使用井號 # (或 ref-)來聲明一個模板引用變量。The#phone declares a phone variable on an <input> element.

<input #phone placeholder="phone number">
或者寫成
<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>

說明:phone refers to the phone number <input> box. The phone button click handler passes the *input *value to the component's callPhone method.

模板引用變量如何獲取自身的值?

通常,如果一個元素聲明了一個模板引用變量,那么 Angular 會將模板引用變量的值設置為該元素的值。
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) 并不相同。詳見結構型指令。

輸入輸出屬性 @Input@Output

綁定目標與綁定源的區別:

  • 綁定的目標是在 = 左側的部分, 則是在 = 右側的部分。
  • 綁定的目標是綁定符:[]()[()] 中的屬性或事件名, 源則是引號 " " 中的部分或插值符號 {{}} 中的部分。
  • 指令中的每個成員都會自動在綁定中可用。 不需要特別做什么,就能在模板表達式或語句中訪問指令的成員。
  • 訪問目標指令中的成員則受到限制。 只能綁定到那些顯式標記為輸入輸出的屬性。

iconUrlonSave 是綁定源

<img [src]="iconUrl"/>
<button (click)="onSave()">Save</button>

HeroDetailComponent.hero 是屬性綁定的目標。 HeroDetailComponent.deleteRequest 是事件綁定的目標。

<hero-detail [hero]="currentHero" (deleteRequest)="deleteHero($event)">
</hero-detail>

聲明輸入和輸出屬性

目標屬性必須被顯式的標記為輸入或輸出。

方法1:使用裝飾器 @Input()@Output()

@Input()  hero: Hero;
@Output() deleteRequest = new EventEmitter<Hero>();

方法2:通過元數據數組。

@Component({
  inputs: ['hero'],
  outputs: ['deleteRequest'],
})

兩種方法不可同時使用。

輸入還是輸出?

輸入屬性通常接收數據值。 輸出屬性暴露事件生產者。
Input properties usually receive data values. Output properties expose event producers.

輸入和輸出這兩個詞是從目標指令的角度來說的。

  • HeroDetailComponent 角度來看,HeroDetailComponent.hero 是個輸入屬性, 因為數據流從模板綁定表達式流入那個屬性。
  • HeroDetailComponent 角度來看,HeroDetailComponent.deleteRequest 是個輸出屬性, 因為事件從那個屬性流出,流向模板綁定語句中的處理器。

給輸入輸出屬性起別名

方法1:把別名傳進 @Input / @Output 裝飾器,就可以為屬性指定別名:

@Output('myClick') clicks = new EventEmitter<string>(); //  @Output(alias) propertyName = ...

方法2:在 inputsoutputs 數組中為屬性指定別名。 語法(屬性名:別名)。

@Directive({
  outputs: ['clicks:myClick']  // propertyName:alias
})

模板表達式操作符

管道操作符 |

管道是一個簡單的函數,它接受一個輸入值,并返回轉換結果。

<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>

輸出結果:

{ "id": 0, "name": "Hercules", "emotion": "happy",
  "birthdate": "1970-02-25T08:00:00.000Z",
  "url": "http://www.imdb.com/title/tt0065832/",
  "rate": 325 }

安全導航操作符 ( ?. ) 和空屬性路徑

Angular 的安全導航操作符 (?.) 用來保護出現在屬性路徑中 null 和 undefined 值。示例:

The current hero's name is {{currentHero?.name}}

說明:當 currentHero 為空時,保護視圖渲染器,讓它免于失敗。

顯示一個空 (null) 英雄的 name 示例:

The null hero's name is {{nullHero.name}}

:marked
  JavaScript throws a null reference error, and so does Angular:
JavaScript 拋出了空引用錯誤,Angular 也是如此:code-example(format="nocode").
  TypeError: Cannot read property 'name' of null in [null].

currentHero 為空的時候,應用崩潰了,整個視圖都不見了。

笨重的解決辦法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}}

總結

透徹理解模板語法對開發至關重要。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • 數據綁定為應用程序提供了一種簡單一致的機制,來管理與協調數據的顯示,以及數據值的變化。angular提供了多種數據...
    oWSQo閱讀 731評論 0 1
  • 本系列教程的主要內容來源于 egghead.io get-started-with-angular 視頻教程,但針...
    semlinker閱讀 10,813評論 1 21
  • 幾天前,我把電腦的F盤格式化,刪除了《絕命毒師》、《絕命律師》、《漢尼拔》、《別對我撒謊》這些在我成長階段留下快樂...
    鬼紋閱讀 222評論 0 0
  • S1無論多遠未來 讀來依然一字一句一篇都燦爛 小夏是一個每天都能樂得屁顛屁顛的女孩,用同學的話來說就是沒心沒肺活著...
    西洲玖堯閱讀 724評論 0 2