Angular是一個框架,用來編寫html的應用程式,可以用javascript或typescript去編寫。
下圖是官網上所繪製的Angular架構圖:
首先先看最中間那一塊,是由template、metadata、component所構成的,這三個是一個component必備的元素。
所謂的元件可以看之前創建hero那篇文章,我們可以用下面指令創一個元件
ng generate component selectSystem
創完一個元件後,可以看見下面這些檔案
Templates
裡面的component.html檔就是template,它看起來像是一個html的檔案,可以在裡面用資料綁定與事件綁定與controller裡的物件做繫結。
下面是一個template的範例:
<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>
可以注意到上面有些地方與一般的html不相同,例如像是*ngFor、{{hero.name}}、(click)="selectHero(hero)"
等…
這就是架構圖畫面上用來連繫Component以及Template的兩個箭頭,property binding以及event binding。
例如click事件繫結是(click)=’functionName()’,物件繫結可以用{{data}})。
透過這樣的繫結可以讓template將使用者操作的事件傳給component,component也可以將資料的更動即時的反饋到template所顯示的資料上。
下圖是非常清楚的binding類型列圖
<li>{{hero.name}}</li> <app-hero-detail [hero]="selectedHero"></app-hero-detail> <li (click)="selectHero(hero)"></li>
其中{{hero.name}}
為值繫結,可以綁定component裡的值。
[hero]="selectedHero"
為property binding,可以將某個元件裡的變數塞進一個HTML元素的屬性裡。。
(click)="selectHero(hero)"
event binding可以呼叫component裡的function
<input [(ngModel)]="hero.name">
這個則是雙向數據綁定,在雙向綁定中,與屬性綁定一樣,數據屬性值將從組件輸入到輸入框中。用戶的更改也會返回到組件,將屬性重置為最新值,就像事件綁定一樣。
數據綁定在模板及其組件之間的通信中起著重要的作用。
數據綁定對於父組件和子組件之間的通信也很重要。
Component
而component.ts檔則是component內容,裡面會有一些屬性或方法來供template呼叫,下面是一個Component的簡單範例:
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; } }
當用戶在應用程序中切換畫面時,Angular會創建,更新和銷毀組件。你的應用程序可以通過可選參加這個生命週期的每個時刻動作的lifecycle hooks,像ngOnInit()。
Metadata
metadata則是在component.ts裡由@Component開頭的區塊來宣告,裡面會定義這個Component在要如何在別的元件的template裡被引用,templateUrl則是定義自己這個元件要顯示的html模版位置及styleUrls是css檔案位置。
例如下面這個metadata宣告
@Component({
selector: ‘app-heroes’,
templateUrl: ‘./heroes.component.html’,
styleUrls: [‘./heroes.component.css’]
})
就可以用下面的方式來顯示這個元件
<app-heroes></app-heroes>
Modules
接著來介紹左上方的區塊
Angular應用程序是模組化的,Angular稱自己為NgModules的模組化系統。NgModules是一個很大的議題,在後面會有另一篇文章專門介紹ngModules。
一個小的application至少會有一個模組,稱為root module。雖然有些小的專案可能就只有一個模組,但大多大的專案都會有多個模組,稱為feature modules,一個模組內會是相同工作範疇的一組元件,它們有許多工作流程或功能上緊密相關,彼此協同運作。
當我們執行了這樣的指令來創建一個新的專案時
ng new my-app
可以看到src/app資料夾內有app.module.ts這個檔案,這個檔案就是angular預設的根模組。
其內容如下:
import { NgModule } from ‘@angular/core’;
import { BrowserModule } from ‘@angular/platform-browser’;
@NgModule({
imports: [ BrowserModule ],
providers: [ Logger ],
declarations: [ AppComponent ],
exports: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
一個NgModule,無論是root module還是feature modules,該class裡一定會有@ngModule的宣告區域,在angular裡會有許多@開頭的宣告,這樣的宣告稱為decorator,可以在元件或模組裡設定許多metadata。關於ngModule更多說明可以看@NgModule的說明。關於decorators更多的說明則可以看這邊,這兩項之後都會有專門的文章來介紹。
NgModule是一個用來描述這個模組裡有那些component的metadata,是一個decorator function。
在ngModule裡最重要的屬性有下面這五點:
- declarations:屬於這個模組的成員。Angular有三種成員:components, directives,以及pipes。
- exports:要將declarations宣告的那一些成員公開。讓其他模組引用此模組時,可以存取該成員的public function。
- imports:需引用的模組,所有在這個模組內的元件要引用的模組都要在此宣告
- providers:要引用的Service需在此宣告
- bootstrap:這個屬性只有根模組需要設定,在此設定在一開始要顯示的application view
註:上面的範例中,因為有設定bootstrap代表其為一個根組件,而根組件是不需要exports出去的,因為不會有任何其他modules需要用到它。
而要啟動整個應用程序可以在main.ts中加上這段
import { enableProdMode } from ‘@angular/core’;
import { platformBrowserDynamic } from ‘@angular/platform-browser-dynamic’;
import { AppModule } from ‘./app/app.module’;
import { environment } from ‘./environments/environment’;
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);//用這個指令來啟動root modules
在js裡我們透過export一個class來供其他modules使用。並在其他js檔案利用import來將這個class引入。
export class AppModule { }
import { NgModule } from '@angular/core'; import { AppComponent } from './app.component';
NgModules與JavaScript modules是完全不同的兩樣東西,我們可以同時透過ngModule與JS Modules來交互使用以達成我們所需要達成的目的。
Angular libraries
angular內建了許多可供應用的模組,我們稱為library module。
所有的library module都以@angular為前綴開頭,可以使用npm來安裝管理它們。
例如要使用Component功能要引用@angular/core的Component
import { Component } from '@angular/core';
或是使用BrowserModule
import { BrowserModule } from '@angular/platform-browser';
Directives
在架構圖中的template右邊,可以看到有一個directive指向template。directive是透過Angular使用內建或自訂directive用來自己定義html元素,並簡化dom操作的功能。可以讓template裡去使用。
一個directive會由@Directive來宣告。其實directive與component的本質是相同的,只是component是一個有template的directive,而directive沒有。我們可以視component為directive的擴展,擴展了template的功能。不過因為component在angular是非常重要的,和directive有不同的意義,因此會將component以及directive在架構上分開來。
directives分為架構型的和屬性型的:
下面是兩個內建架構型的directive範例
<li *ngFor="let hero of heroes"></li> <app-hero-detail *ngIf="selectedHero"></app-hero-detail>
*ngFor
會讓angular重覆寫許多的 <li>將heroes裡的資料跑過一圈
*ngIf
則會讓angular只有在selectedHero的值為true時才會顯示app-hero-detail元件
而屬性型的directive則例如像是做雙向繫結的ngModel,下面的片段程式會將hero.name的值塞入input的value屬性內,並且監聽使用者修改input的值的事件將修改傳回至hero.name
<input [(ngModel)]="hero.name">
Services
架構圖的左下區是很多的Service注入至Component裡。幾乎所有功能都可以是service,但是它應該是目的明確且狹義的功能。
例如:記錄服務、數據服務、消息服務、稅計算器、應用程序配置等…
以下是一個範例,在這個例子中可以看到我們可以利用getHeroes()和service取得Hero列表,而Service則負責與Backend溝通由API取得資料並且回傳給component
export class HeroService { private heroes: Hero[] = []; constructor( private backend: BackendService, private logger: Logger) { } getHeroes() { this.backend.getAll(Hero).then( (heroes: Hero[]) => { this.logger.log(`Fetched ${heroes.length} heroes.`); this.heroes.push(...heroes); // fill cache }); return this.heroes; } }
Dependency injection
依賴注入是一種設計模式,可參考這篇文章了解:理解 Dependency Injection 實作原理
在Angular中,大多數依賴是服務,Angular的元件使用依賴注入為自己提供他們需要的服務,對於元件來說,服務必須是完全依賴的,component會在元件內使用service所提供的方法來取得自己所需的資料。
HeroService依賴注入的過程看起來像這樣:
injector會有一個所有Service的集合。如果所需要的Service不在這個集合中,那麼injector將創建一個Service並加進Service集合裡。當component所需要的Service都已經取得後,只要在constructor設定該服務在此元件內的名稱,就可以在元件裡自由的使用服務了。
下面是一個依賴注入的範例
constructor(private service: HeroService) { }
例如上面的程式碼就會讓該component裡的程式碼可以使用service.getHeroes()來取得英雄列表。
在上圖中,上面要被injector選擇的那些service是怎麼來的呢?我們可在根模組或者自己所在的模組裡去提供所有需用到的服務,這樣在這個模組內的所有元件都可以使用這個服務,如下:
src / app / app.module.ts
providers: [ BackendService, HeroService, Logger ],
或者也可以在該元件的metadata裡用providers來設定這個元件要使用這個服務,如下:
@Component({ selector: 'app-hero-list', templateUrl: './hero-list.component.html', providers: [ HeroService ] })