在過去的文章我們已經學到—Angular2的應用程式是由一個一個的component所堆積組合起來的,這麼做的好處不用多說就是關注點分離,讓你專心在目前的功能上,也不用擔心影響到其他的component,但是當你的程式架構越來越龐大時,管理這些數以百計的components就變成一門學問了,這時候我們就可以透過模組化的機制,更有組織的管理你的所有components,這也就是我們今天要談的主題—Module。
開始使用Module
其實在我們早就已經有使用過Module了,在使用Angular CLI建立一個Angular2專案時,就會在src/app下產生一隻為app.module.ts個的檔案,這裡面放著我們Angular2專案預設的入口,也就是第一個Module—AppModule,首先讓我們看看裡面的程式:
從程式中可以看到我們使用Angular CLI建立的components都已經在這裡的declarations: []宣告了,如果你還記得的話,在之前講到Service時,我們也曾手動將TodoListService加入AppModules的providers: []中,另外還有BrowserModule、FormsModule這些宣告,則是Angular2中內建可用的modules,少了這些modules,我們就沒有像是ngIf、nfFor和ngModel這類的好東西可用哩。
看到這些內建的modules也給了我們一些啟發,我們可以藉由將程式封裝成一組一組的modules,就可以把它分享出去給別人使用囉!這在團隊中有共用的一些元件、程式時非常有用,我們可以把共用的程式抽出來封裝到modules裡面,然後上傳到private git中,再使用npm指定git路徑安裝,就能在不同專案間共享程式碼啦!甚至可以把你的心血公開上傳到npm,讓全世界一起使用!聽起來就很熱血阿!!
至於Angular2專案怎麼知道AppModule是我們應用程式的入口呢?我們可以打開angular-cli.json看到apps[0].main的設定,裡面設定了main.ts,這隻程式在Angular CLI編譯打包程式時,會作為主要的程式進入點,而在main.ts中的程式如下:
我們可以看到platformBrowserDynamic().bootstrapModule(AppModule);就說明了AppModule就是我們啟動時要執行的module啦!因此若是有需要,我們也可以在這裡換成其他的module,很有彈性吧!
那麼Angular2的Module到底可以用來幹嘛呢?根據官方文件的說法,Angular Moduleg是一隻由帶有相關metadata物件的@NgModule裝飾的function,這些metadata物件告訴我們:
- 哪些components、directives和pipes屬於這個module
- module下哪些類別是可以公開給外部使用的
- 在這個module下,我們需要匯入哪些其他的module給我們的components、directives和pipes使用
- 提供了哪些services是應用程式中所有的組件都可以使用的
接下來我們將示範把之前寫好的TodoApp封裝到一個module裡面,同時說明如何在@NgModule裡面宣告上述的4個部分,在完成後你將會發現我們的程式及目錄結構變得更加清楚!
建立一個Module
要建立一個module非常簡單,透過Angular CLI只要一個指令就搞定
ng g m todo-app
接著你會看到src/app下面多了一個todo-app的資料夾以及todo-app.module.ts
搬移相關檔案
接著我們把跟TodoApp相關的程式都移進這個資料夾
搬移後變成:
把所有相關的程式都放在一起,看起來是不是舒適很多呢?接著我們建立一個屬於TodoAppModule的component看看,透過指令
ng g c todo-app/todo-app
我們在用Angular CLI建立程式時,可以輸入相對路徑,程式會建立在你指定的路徑上
同時,如果你指定的路徑已經包含了module的話,也會自動幫你把相關宣告放在module定義中,打開todo-app.module.ts看看
可以看到Angular CLI在建立component時也幫我們加到自己建立modules中的declarations: []囉,接著我們把AddFormComponent和TodoItems也加入todo-app.module.ts的declarations中,順便把TodoDonePipe也加入,並把AppModule裡面同樣的宣告移除掉
declarations: [
TodoAppComponent,
AddFormComponent,
TodoItemsComponent,
TodoDonePipe
]
到目前我們已經達到了前面說明的哪些components、directives和pipes屬於這個module的部分,也就是說,components、directives和pipes在module中,都必須放到declarations: []中宣告,如果一開始就規劃好module的話,這些Angular CLI都會幫我們做好,如果是要搬移的話,還是要手動作囉。
將TodoListService加入TodoAppModule
由於TodoListService只需要給TodoAppModule使用,因此我們將TodoAppService加入TodoAppModule的providers: []裡面,預設建立的TodoAppModule可能沒有providers: [],但我們可以自己在todo-app.module.ts中加入
providers: [TodoListService]
這裡的目標是宣告module中提供了哪些services是應用程式中所有的組件都可以使用的。
將TodoAppModule加入AppModule
接著我們可以把TodoAppModule加入AppModule的imports: []中,告訴AppModule在這個module下,我們需要匯入哪些其他的module給我們的components、directives和pipes使用。
app.module.ts內容變成
imports: [
BrowserModule,
FormsModule,
HttpModule,
TodoAppModule
],
另外,在AddForm中我們使用到了ngModel,這個指令放在FormsModules中;因此我們必須在TodoAppModule中匯入FormsModule,而ngIf和ngFor在CommonModule中,但在建立module時預設已經加入囉
imports: [
CommonModule,
FormsModule
]
你可能發現我們import了CommonModule和FormsModule,但TodoListService用到的HttpModule卻不需要在這裡匯入,是因為Http是一個Service,而Service在AppModule中import之後就可以在所有應用程式下面的組件使用(可以回去翻第三點),而且只會產生一次,以增進效能。
ngIf和ngFor在CommonModule中,但AppModule並沒有import CommonModule卻可以使用,原因我們稍後說明。
讓AddFormComponent和TodoItemsComponent可供外部使用
如果這時候我們執行程式起來,會發現程式無法正常執行,而打開F12時看到這樣的訊息
告訴你沒有app-add-form這個component,這是因為我們雖然把AddFormComponent加入TodoAppModule中了,但還需要宣告module下哪些類別是可以公開給外部使用的,我們必須把要提供給外部使用的類別加入exports: []中,因此我們把AddFormComponent和TodoItemsComponent放入這裡面:
exports: [
AddFormComponent,
TodoItemsComponent
]
再執行看看,就大功告成啦!我們已經成功的把TodoList相關的元件封裝到一個TodoAppModule中囉。
在AppModule中不用import CommonModule的原因是因為BrowserModule把CommonModule匯入並重新exports了,因此其實在AppModules中等於已經加入CommonModule了
最後調整一下,把AddFormComponent和TodoItemsComponent都export出來,會變成兩個component可以獨立在不同地方使用,我們不希望發生這樣的事情(e.x.按下AddFormComponent卻看不到任何變化),因此我們把這兩個component加到之前建立的TodoAppComponent的View(todo-app.component.html)中:
<app-add-form></app-add-form>
<app-todo-items></app-todo-items>
之後只把TodoAppComponent進行export就好囉,TodoAppModule中的exports: []改為:
exports: [
TodoAppComponent
]
如此一來在TodoAppModule外就不能單獨使用AddFormComponent或TodoItemsComponent囉!這麼一來封裝性就更加強大,程式的職責也更加清楚啦!!
單元回顧
今天我們學到了使用@NgModule來將程式模組化,@NgModule參數為一個metadata物件,內容如下:
- declarations: []:哪些components、directives和pipes屬於這個module
- exports:[]:module下哪些類別是可以公開給外部使用的
- imports:[]:在這個module下,我們需要匯入哪些其他的module給我們的components、directives和pipes使用
- providers:[]:提供了哪些services是應用程式中所有的組件都可以使用的
透過將程式模組化,可以讓程式架構更加明確,搭配Angular CLI會自動幫你將模組化的內容收納在對應的資料夾下,見少無謂的重工,增強生產力;善用module,我們可以架構出更加清楚好維護的程式碼,也能夠更容易的將程式碼分享出去,實在是送人自用兩相宜的好工具阿!!
今日程式碼:https://github.com/wellwind/Angular2DotblogsDemo/tree/ModuleDemo