Angular 模組

模組 (NgModule)

過去我們將所有實作的 元件(Component) 都塞到 AppModule 內,但是實務上的專案可能會有幾十個甚至上百個元件,如果全部都放置在 AppModule 內,會造成專案難以維護也難以分工的問題,所以比較合理的做法就是將元件分拆到不同的 模組(NgModule) 內,再將模組聚合起來。

開啟 src\app\app.module.ts 可以看到目前所建立的元件。

app.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2.component';
import { Page3Component } from './page3.component';
import { Page404Component } from './page404/page404.component';

@NgModule({
declarations: [
AppComponent,
Page1Component,
Page2Component,
Page3Component,
Page404Component
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

我們可以把 模組 假想成 資料夾,把 元件 想成是 檔案,對應下來的專案結構就會變成如下圖所示:
img
檔案必須存在於某個資料夾之內,而資料夾裡面除了可以放置檔案外還可以再放置其他的資料夾,整個專案就像樹狀結構一樣可以不斷的建立分支。

建立新模組

透過 Angular-CLI 指令 ng g m [name] 來建立新模組,例如建立一個OperationModule,透過
ng g m operation

ggenerate 縮寫,mmodule 縮寫。
相關參數請參閱 GitHub

img
CLI 產生一個位於 src\app\operation\operation.module.ts 的模組,下一行出現警告,提醒我們這個模組沒有被註冊所以無法使用,如果某個元件要使用到這個模組就必須將它註冊到該元件所屬的模組內,這樣才能使用。

operation.module.ts
1
2
3
4
5
6
7
8
9
10
11
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
imports: [
CommonModule
],
declarations: []
})
export class OperationModule { }

我們在 OperationModule 模組位置(src\app\operation\)下面再建立3個元件 Opt1ComponentOpt2ComponentOpt3Component
img
神奇的事情發生了,元件都註冊到 OperationModule 而不是 AppModule,也就是說 CLI 在建立元件時會將元件註冊到最近的模組內。
img
開啟 src\app\operation\operation.module.ts 可以看到3個元件確實都被註冊到 OperationModule

operation.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Opt1Component } from './opt1/opt1.component';
import { Opt2Component } from './opt2/opt2.component';
import { Opt3Component } from './opt3/opt3.component';

@NgModule({
imports: [
CommonModule
],
declarations: [
Opt1Component,
Opt2Component,
Opt3Component
]
})
export class OperationModule { }

但是別忘了 OperationModule 目前還是居無定所,沒有被註冊到任何模組內。

情境A

假設我們希望 app.component.html 能夠加入 Opt1Component 的功能,正常流程先在 app-routing.module.ts 增加 Opt1Component 的路由規則, path 屬性設定為 opt1

app-routing.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2.component';
import { Page3Component } from './page3.component';
import { Page404Component } from './page404/page404.component';
import { Opt1Component } from './operation/opt1/opt1.component';

const routes: Routes = [
{ path: '', children: [] },
{ path: 'p1', component: Page1Component },
{ path: 'p2', component: Page2Component },
{ path: 'p3', component: Page3Component },
{ path: 'opt1', component: Opt1Component },
{ path: '404', component: Page404Component },
{ path: '**', redirectTo: '404' }
// { path: '**', redirectTo: ''}
// { path: '**', component: Page404Component }
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

如果 VS Code 有安裝 TypeScript Toolbox ,直接在路由規則輸入 Opt1Component 後會出現找不到名稱的警示,點選 Opt1Component 再行號旁會出現燈泡的圖示,點選後會出現 add import 的功能,它會自動幫我們在上面加入 import 宣告,TypeScript Toolbox 已經被包含在 Angular Extension Pack 內。
img

再將 app.component.html 樣板插入一個 Opt1Component 的連結。

app.component.html
1
2
3
4
5
6
7
<a routerLink="/p1">Page 1</a>
<button routerLink="/p2">Page 2</button>
<span routerLink="/p3">Page 3</span>
<a routerLink="/opt1">Option 1</a>
<hr>
<router-outlet></router-outlet>

執行後會出現 Angular 不認得 Opt1Component 的警告。
img
再來我們將 Opt1Component 所屬的模組(OperationModule) 給註冊到 AppModule 內,重新執行就正常了。

app.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { Page1Component } from './page1/page1.component';
import { Page2Component } from './page2.component';
import { Page3Component } from './page3.component';
import { Page404Component } from './page404/page404.component';
import { OperationModule } from './operation/operation.module';

@NgModule({
declarations: [
AppComponent,
Page1Component,
Page2Component,
Page3Component,
Page404Component
],
imports: [
BrowserModule,
AppRoutingModule,
OperationModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

img

情境B

假設 Page1Component 需要使用到 Opt1Component,所以正常方式是將 Opt1Component 的 tag (app-opt1) 加到 ‘src\app\page1\page1.component.html’ 內,執行看看結果,會發現 Angular 不認得 app-opt1 這個 tag。

page1.component.html
1
2
3
4
5
<p>
page1 works!
<app-opt1></app-opt1>
</p>

img

如果 VS Code 有安裝 Angular Language Service 在開發時期 VS Code 就會出現警告,Angular Language Service 已經被包含在 Angular Extension Pack 內。
img

雖然 OperationModule 已經註冊到 AppModule 內,但是透過 Component tag 方式來引用時仍然會出現錯誤。
現在我們需要使用到 NgModule 的另一個屬性 exports,開啟 src\app\operation\operation.module.tsOpt1Component 設定到 OperationModuleexports 內。

operation.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Opt1Component } from './opt1/opt1.component';
import { Opt2Component } from './opt2/opt2.component';
import { Opt3Component } from './opt3/opt3.component';

@NgModule({
imports: [
CommonModule
],
declarations: [
Opt1Component,
Opt2Component,
Opt3Component
],
exports: [
Opt1Component
]
})
export class OperationModule { }

重新再執行就正常了,切換到 Page 1 後下方出現 Page1ComponentOpt1Component 的樣板。
img

NgModuleimports 提供了匯入功能,讓我們將要使用的元件匯入到此模組內,而 exports 則提供匯出功能,讓別的模組可以知道此模組提供什麼元件,所以正常要 exports 的元件也一定要先匯入進來,因為是元件所以匯入是加到 declarations 屬性內。

再將 src\styles.scss 加入 P tag 的樣式,重新瀏覽網頁時可以看到 Opt1Component 的樣板確實被包覆在 Page1Component 樣板內。

styles.scss
1
2
3
4
5
6
/* You can add global styles to this file, and also import other style files */
P {
border: 1px dashed red;
margin: 8px;
}

img

開啟 .angular-cli.json 可以發現 styles 的集合宣告,預設包含了 styles.scss 檔,Angular CLI 在編譯時會將內含的所有檔案都一起編譯進去,所以不一定要將所有共用的樣式都塞到 styles.scss 內,可以分拆成多個檔案,甚至可以直接引用別人寫好的樣式。
img

[**first-app_2017-08-31_2.zip**](/uploads/first-app_2017-08-31_2.zip)