組件控制屏幕上被稱(chēng)為視圖的一小片區(qū)域。比如,教程中的下列視圖都是由一個(gè)個(gè)組件所定義和控制的:
你在類(lèi)中定義組件的應(yīng)用邏輯,為視圖提供支持。 組件通過(guò)一些由屬性和方法組成的 API 與視圖交互。
比如,HeroListComponent 中有一個(gè) 名為 heroes
的屬性,它儲(chǔ)存著一個(gè)數(shù)組的英雄數(shù)據(jù)。 HeroListComponent 還有一個(gè) selectHero()
方法,當(dāng)用戶(hù)從列表中選擇一個(gè)英雄時(shí),它會(huì)設(shè)置 selectedHero
屬性的值。 該組件會(huì)從服務(wù)獲取英雄列表,它是一個(gè) TypeScript 的構(gòu)造器參數(shù)型屬性。本服務(wù)通過(guò)依賴(lài)注入系統(tǒng)提供給該組件。
export class HeroListComponent implements OnInit {
heroes: Hero[];
selectedHero: Hero;
constructor(private service: HeroService) { }
ngOnInit() {
this.heroes = this.service.getHeroes();
}
selectHero(hero: Hero) { this.selectedHero = hero; }
}
當(dāng)用戶(hù)在應(yīng)用中穿行時(shí),Angular 就會(huì)創(chuàng)建、更新、銷(xiāo)毀一些組件。 你的應(yīng)用可以通過(guò)一些可選的生命周期鉤子(比如 ngOnInit()
)來(lái)在每個(gè)特定的時(shí)機(jī)采取行動(dòng)。
@Component 裝飾器會(huì)指出緊隨其后的那個(gè)類(lèi)是個(gè)組件類(lèi),并為其指定元數(shù)據(jù)。 在下面的范例代碼中,你可以看到 HeroListComponent 只是一個(gè)普通類(lèi),完全沒(méi)有 Angular 特有的標(biāo)記或語(yǔ)法。 直到給它加上了 @Component 裝飾器,它才變成了組件。
組件的元數(shù)據(jù)告訴 Angular 到哪里獲取它需要的主要構(gòu)造塊,以創(chuàng)建和展示這個(gè)組件及其視圖。 具體來(lái)說(shuō),它把一個(gè)模板(無(wú)論是直接內(nèi)聯(lián)在代碼中還是引用的外部文件)和該組件關(guān)聯(lián)起來(lái)。 該組件及其模板,共同描述了一個(gè)視圖。
除了包含或指向模板之外,@Component 的元數(shù)據(jù)還會(huì)配置要如何在 HTML 中引用該組件,以及該組件需要哪些服務(wù)等等。
下面的例子中就是 HeroListComponent 的基礎(chǔ)元數(shù)據(jù):
@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
providers: [ HeroService ]
})
export class HeroListComponent implements OnInit {
/* . . . */
}
這個(gè)例子展示了一些最常用的 @Component 配置選項(xiàng):
你要通過(guò)組件的配套模板來(lái)定義其視圖。模板就是一種 HTML,它會(huì)告訴 Angular 如何渲染該組件。
視圖通常會(huì)分層次進(jìn)行組織,讓你能以 UI 分區(qū)或頁(yè)面為單位進(jìn)行修改、顯示或隱藏。 與組件直接關(guān)聯(lián)的模板會(huì)定義該組件的宿主視圖。該組件還可以定義一個(gè)帶層次結(jié)構(gòu)的視圖,它包含一些內(nèi)嵌的視圖作為其它組件的宿主。
帶層次結(jié)構(gòu)的視圖可以包含同一模塊(NgModule)中組件的視圖,也可以(而且經(jīng)常會(huì))包含其它模塊中定義的組件的視圖。
模板很像標(biāo)準(zhǔn)的 HTML,但是它還包含 Angular 的模板語(yǔ)法,這些模板語(yǔ)法可以根據(jù)你的應(yīng)用邏輯、應(yīng)用狀態(tài)和 DOM 數(shù)據(jù)來(lái)修改這些 HTML。 你的模板可以使用數(shù)據(jù)綁定來(lái)協(xié)調(diào)應(yīng)用和 DOM 中的數(shù)據(jù),使用管道在顯示出來(lái)之前對(duì)其進(jìn)行轉(zhuǎn)換,使用指令來(lái)把程序邏輯應(yīng)用到要顯示的內(nèi)容上。
比如,下面是本教程中 HeroListComponent 的模板:
<h2>Hero List</h2>
<p><i>Pick a hero from the list</i></p>
<ul>
<li *ngFor="let hero of heroes" (click)="selectHero(hero)">
{{hero.name}}
</li>
</ul>
<app-hero-detail *ngIf="selectedHero" [hero]="selectedHero"></app-hero-detail>
這個(gè)模板使用了典型的 HTML 元素,比如 <h2>
和 <p>
,還包括一些 Angular 的模板語(yǔ)法元素,如 *ngFor
,{{hero.name}}
,click
、[hero]
和 <app-hero-detail>
。這些模板語(yǔ)法元素告訴 Angular 該如何根據(jù)程序邏輯和數(shù)據(jù)在屏幕上渲染 HTML。
*ngFor
指令告訴 Angular 在一個(gè)列表上進(jìn)行迭代。{{hero.name}}
、(click)
和 [hero]
把程序數(shù)據(jù)綁定到及綁定回 DOM,以響應(yīng)用戶(hù)的輸入。更多內(nèi)容參見(jiàn)稍后的數(shù)據(jù)綁定部分。<app-hero-detail>
標(biāo)簽是一個(gè)代表新組件 HeroDetailComponent 的元素。 HeroDetailComponent(代碼略)定義了 HeroListComponent 的英雄詳情子視圖。 注意觀(guān)察像這樣的自定義組件是如何與原生 HTML 元素?zé)o縫的混合在一起的。如果沒(méi)有框架,你就要自己負(fù)責(zé)把數(shù)據(jù)值推送到 HTML 控件中,并把來(lái)自用戶(hù)的響應(yīng)轉(zhuǎn)換成動(dòng)作和對(duì)值的更新。 手動(dòng)寫(xiě)這種數(shù)據(jù)推拉邏輯會(huì)很枯燥、容易出錯(cuò),難以閱讀 —— 有前端 JavaScript 開(kāi)發(fā)經(jīng)驗(yàn)的程序員一定深有體會(huì)。
Angular 支持雙向數(shù)據(jù)綁定,這是一種對(duì)模板中的各個(gè)部件與組件中的各個(gè)部件進(jìn)行協(xié)調(diào)的機(jī)制。 往模板 HTML 中添加綁定標(biāo)記可以告訴 Angular 該如何連接它們。
下圖顯示了數(shù)據(jù)綁定標(biāo)記的四種形式。每種形式都有一個(gè)方向 —— 從組件到 DOM、從 DOM 到組件或雙向。
這個(gè)來(lái)自 HeroListComponent 模板中的例子展示了其中的三種形式:
<li>{{hero.name}}</li>
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
<li (click)="selectHero(hero)"></li>
雙向數(shù)據(jù)綁定(主要用于模板驅(qū)動(dòng)表單中),它會(huì)把屬性綁定和事件綁定組合成一種單獨(dú)的寫(xiě)法。下面這個(gè)來(lái)自 HeroDetailComponent 模板中的例子通過(guò) ngModel 指令使用了雙向數(shù)據(jù)綁定:
<input [(ngModel)]="hero.name">
在雙向綁定中,數(shù)據(jù)屬性值通過(guò)屬性綁定從組件流到輸入框。用戶(hù)的修改通過(guò)事件綁定流回組件,把屬性值設(shè)置為最新的值。
Angular 在每個(gè) JavaScript 事件循環(huán)中處理所有的數(shù)據(jù)綁定,它會(huì)從組件樹(shù)的根部開(kāi)始,遞歸處理全部子組件。
數(shù)據(jù)綁定在模板及其組件之間的通訊中扮演了非常重要的角色,它對(duì)于父組件和子組件之間的通訊也同樣重要。
Angular 的管道可以讓你在模板中聲明顯示值的轉(zhuǎn)換邏輯。 帶有 @Pipe 裝飾器的類(lèi)中會(huì)定義一個(gè)轉(zhuǎn)換函數(shù),用來(lái)把輸入值轉(zhuǎn)換成供視圖顯示用的輸出值。
Angular 自帶了很多管道,比如 date
管道和 currency
管道,完整的列表參見(jiàn) Pipes API 列表。你也可以自己定義一些新管道。
要在 HTML 模板中指定值的轉(zhuǎn)換方式,請(qǐng)使用 管道操作符 (|
)。
{{interpolated_value | pipe_name}}
你可以把管道串聯(lián)起來(lái),把一個(gè)管道函數(shù)的輸出送給另一個(gè)管道函數(shù)進(jìn)行轉(zhuǎn)換。 管道還能接收一些參數(shù),來(lái)控制它該如何進(jìn)行轉(zhuǎn)換。比如,你可以把要使用的日期格式傳給 date
管道:
<!-- Default format: output 'Jun 15, 2015'-->
<p>Today is {{today | date}}</p>
<!-- fullDate format: output 'Monday, June 15, 2015'-->
<p>The date is {{today | date:'fullDate'}}</p>
<!-- shortTime format: output '9:43 AM'-->
<p>The time is {{today | date:'shortTime'}}</p>
Angular 的模板是動(dòng)態(tài)的。當(dāng) Angular 渲染它們的時(shí)候,會(huì)根據(jù)指令給出的指示對(duì) DOM 進(jìn)行轉(zhuǎn)換。 指令就是一個(gè)帶有 @Directive()
裝飾器的類(lèi)。
組件從技術(shù)角度上說(shuō)就是一個(gè)指令,但是由于組件對(duì) Angular 應(yīng)用來(lái)說(shuō)非常獨(dú)特、非常重要,因此 Angular 專(zhuān)門(mén)定義了 @Component()
裝飾器,它使用一些面向模板的特性擴(kuò)展了 @Directive()
裝飾器。
除組件外,還有兩種指令:結(jié)構(gòu)型指令和屬性型指令。 Angular 本身定義了一系列這兩種類(lèi)型的指令,你也可以使用 @Directive()
裝飾器來(lái)定義自己的指令。
像組件一樣,指令的元數(shù)據(jù)把它所裝飾的指令類(lèi)和一個(gè) selector
關(guān)聯(lián)起來(lái),selector
用來(lái)把該指令插入到 HTML 中。 在模板中,指令通常作為屬性出現(xiàn)在元素標(biāo)簽上,可能僅僅作為名字出現(xiàn),也可能作為賦值目標(biāo)或綁定目標(biāo)出現(xiàn)。
1. 結(jié)構(gòu)型指令
結(jié)構(gòu)型指令通過(guò)添加、移除或替換 DOM 元素來(lái)修改布局。 這個(gè)范例模板使用了兩個(gè)內(nèi)置的結(jié)構(gòu)型指令來(lái)為要渲染的視圖添加程序邏輯:
<li *ngFor="let hero of heroes"></li>
<app-hero-detail *ngIf="selectedHero"></app-hero-detail>
*ngFor
是一個(gè)迭代器,它要求 Angular 為 heroes 列表中的每個(gè)英雄渲染出一個(gè) <li>
。*ngIf
是個(gè)條件語(yǔ)句,只有當(dāng)選中的英雄存在時(shí),它才會(huì)包含 HeroDetail 組件。2. 屬性型指令
屬性型指令會(huì)修改現(xiàn)有元素的外觀(guān)或行為。 在模板中,它們看起來(lái)就像普通的 HTML 屬性一樣,因此得名“屬性型指令”。
ngModel 指令就是屬性型指令的一個(gè)例子,它實(shí)現(xiàn)了雙向數(shù)據(jù)綁定。 ngModel 修改現(xiàn)有元素(一般是 <input>
)的行為:設(shè)置其顯示屬性值,并響應(yīng) change 事件。
<input [(ngModel)]="hero.name">
注:
- Angular 還有很多預(yù)定義指令,有些修改布局結(jié)構(gòu)(比如ngSwitch
),有些修改 DOM 元素和組件的樣子(比如ngStyle
和ngClass
)。
- 參考 [Angular9 結(jié)構(gòu)型指令]() 和 [Angular9 屬性型指令]() 以了解 Angular 兩種指令類(lèi)型。
更多建議: