###### tags: `Guide` `NgModule` `Router` `Angular`
# 🌝Angular基礎教學02 #模組與路由
## 內容大綱
**主要內容如下**
1. 如何新增專案
2. 模組基本介紹
3. 如何設定路由
## Angular CLI
![](https://i.imgur.com/jseJ82B.png)
### Angular CLI介紹
Angular CLI,是Angular2發展的指令列工具,可以快速產生開發程式時需要的檔案範本,需透過npm安裝。
### Angular CLI使用
#### *new project.* 新增專案
- 新增一個新的Angular專案
```ng
ng new [project name]
```
#### *new component.* 新增元件
- 新增一個新的component
```ng
ng generate component [component name]
ng g c [component name]
```
#### *new module.* 新增模組
- 新增一個新的module
```ng
ng generate module [module name]
ng g m [module name]
```
---
## NgModule
[Angaulr文件-NgModule](https://angular.tw/guide/architecture-modules)
[API參考手冊-NgModule](https://angular.tw/api/core/NgModule)
### NgModule介紹
#### *@NgModule.* 裝飾器
- 宣告的類別掛上`@NgModule`裝飾器,就表示此類別為一個模組。在掛有`@NgModule`類別中,我們就可以來組織管理該模組自己的`Component`、`Directives`、`Pipe`。
### NgModule使用
#### *import.* 引入模組
- 要使用模組提供的功能,必須放在`import`內。
```typescript
import { FormsModule } from '@angular/forms';
@NgModule({
import: [FormsModule]
})
```
> 我們常使用的`ngModel`就是由FormsModule所提供的功能
#### *exports.* 提供外部引用
- 外部引用此模組時可使用的功能,必須放在`export`內。
==first.module.ts==
```typescript=
import { FormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [],
imports: [
CommonModule,
FormsModule
],
exports: [
FormsModule
]
})
export class FirstModule { }
```
==app.module.ts==
```typescript=
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { FirstModule } from './first/first.module';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FirstModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
```
> BrowserModule針對瀏覽器常用的程式,但一般在我們使用Angular CLI新增專案時就會幫我們新增在`AppModule`
*declarations.* 宣告樣板
- 放與樣板(顯示)有關的程式,它們可以使用`import`的模組所提供的功能,但必須注意這些與樣板(顯示)相關的類別,==只能存在一個模組==中。
```typescript
@NgModule({
declarations: [
AppComponent,
SearchPipe,
CustomdropdownDirective
]
})
```
:::info
💡**樣板**
在這裡的樣板是指與顯示相關的程式,會改變顯示的也包含在裡面,分辨方法就是看程式的裝飾器是否為下面這些。
- @Component
- @Pipe
- @Directive
:::
#### *providers.* 注入
- 決定哪些`service`允許被注入
```typescript
@NgModule({
providers: [AppService]
})
```
:::success
📝**小補充**
Angular 6 之後替 @Service 加上了 providedIn 設定,讓服務不一定非要放在 providers: [] 之中。
:::
#### *bootstrap.* 自動啟動
- 放在裡面的程式會在此模組被使用到時自動被啟動
```typeacript
@NgModule({
bootstrap: [AppComponent]
})
```
---
## Router
[Angular文件-RouterModule](https://angular.tw/api/router/RouterModule)
[Angaulr文件-in-app-navigation-routing-to-views](https://angular.tw/guide/router)
### Router介紹
Router會把瀏覽器的URL解釋成改變檢視的操作,所以我們可以透過Router處理頁面之間切換的問題。
新增專案時可以加上`--routing`,新專案中就會自動產生一個`Routing Module`。
```ng
ng new App --routing
```
若是想在新增`module`時,讓`Angular CLI`幫忙產生`Routing Module`,可以加上`--routing`,`Angular CLI`就會自動產生`Routing Module`了哦!
```ng
ng g m App --routing
```
### Router使用
#### *step1.* 新增module
- `routing module`是一種`module`,因為router屬於常用模組,所以通常都會將她獨立寫成一個module,並為了方便辨認加上routing,所以才會定義路由的模組看到的都是routing module。
#### *step2.* 匯入`RouterModule`和`Routes`
- 路由是由`RouterModule`所提供的功能,所以要使用路由功能必須引入`RouterModule`。
```typescript
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; // CLI imports router
const routes: Routes = []; // sets up routes constant where you define your routes
// configures NgModule imports and exports
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class FirstRoutingModule { }
```
#### *step3.* 在`Routes`中定義你的路由
- 定義`path`決定URL的路徑。
- 定義`component`決定該路徑所使用的元件。
```typescript
const routes: Routes = [
{ path: '', redirectTo: 'first-component', pathMatch: 'full' }, // redirect to `first-component`
{ path: 'first-component', component: FirstComponent },
{ path: 'second-component', component: SecondComponent },
];
```
> `{ path: '', redirectTo: 'first-component', pathMatch: 'full' }`,若路由為空則導到first-component
#### *step4.* 將路由新增到元件中
- `<router-outlet>`會通知Angular可以用訪問的路由來更新檢視。
==app.component.html==
```htmlmixed
<h1>Angular Router App</h1>
<!-- This nav gives you links to click, which tells the router which route to use (defined in the routes constant in AppRoutingModule) -->
<nav>
<ul>
<li><a routerLink="/first-component" routerLinkActive="active">First Component</a></li>
<li><a routerLink="/second-component" routerLinkActive="active">Second Component</a></li>
</ul>
</nav>
<!-- The routed views render in the <router-outlet>-->
<router-outlet></router-outlet>
```
### Router補充
#### *1.* 萬用路由
- 若使用者輸入的URL導不到符合的路由,就會發生錯誤,若想避免此問題,可以設定萬用路由`{path: '**', component: [YourComponent]`。
```typescript
const routes: Routes = [
{ path: 'first-component', component: FirstComponent },
{ path: 'second-component', component: SecondComponent },
{ path: '', redirectTo: '/first-component', pathMatch: 'full' }, // redirect to `first-component`
{ path: '**', component: FirstComponent },
{ path: '**', component: PageNotFoundComponent }, // Wildcard route for a 404 page
];
```
> 路由的順序很重要,因為路由是採取==先到先取==的方式,所以像是萬用路由是被定義在第一個,設定在萬用路由後的是根本沒有用的。
#### *2.* 延遲載入
[Angular文件-lazy-loading](https://angular.tw/guide/router#lazy-loading)
[Angular文件-lazy-loading-ngmodules](https://angular.tw/guide/lazy-loading-ngmodules)
- 可以使程式第一次載入時,不把延遲載入的模組內程式載入,達到減少第一次載入時的載入時間(此用法開發者會依照使用需求而使用,並不是必要功能)。
- 用`loadchildren`取代`component`就可以延遲載入模組
==app-routing.module.ts==
```typescript=
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FirstComponent } from './first/first/first.component';
const routes: Routes = [
// { path: '', redirectTo: 'first-component', pathMatch: 'full' }, // redirect to `first-component`
// { path: 'first-component', component: FirstComponent },
{ path: 'firstModule', loadChildren: () => import('./first/first.module').then(m => m.FirstModule) },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
```
- 在延遲載入的模組的路由模組,載入`component`。
==first-routing.module.ts==
```typescript=
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { FirstComponent } from './first/first.component';
const routes: Routes = [
{ path: '', redirectTo: 'first-component', pathMatch: 'full' }, // redirect to `first-component`
{ path: 'first-component', component: FirstComponent },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class FirstRoutingModule { }
```
:::info
💡**forRoot vs forChild**
RouterModule 可能會被多次匯入:每個延遲載入的發佈套件都會匯入一次。 但由於路由器要和全域性共享的資源 - location 打交道,所以不能同時啟用一個以上的 Router 服務。
這就是需要兩種方式來建立本模組的原因:RouterModule.forRoot 和 RouterModule.forChild。
- forRoot 建立一個包含所有指令、指定的路由和 Router 服務本身的模組。
- forChild 會建立一個包含所有指令、指定的路由,但不含 Router 服務的模組。
:::
- 可以透過Devtool(F12)>Network來確定是否有確實的延遲載入
==localhost:4200== *first-module尚未被匯入*
![](https://i.imgur.com/UIsgXng.png)
==localhost:4200/firstModule== *first-module匯入*
![](https://i.imgur.com/wncnaDN.png)
#### *3.* 巢狀路由
[Angular文件-Nesting routes
](https://angular.tw/guide/router#nesting-routes)
- 若是建立的路由需要下一層的路由`ex: /first-component/child-a`,但又不透過`lazy-loading`來達成這樣的需求,可以使用`children`來設定路由,而這樣的巢狀路由稱為子路由。
- `children`的型別也是Routes,所以子路由就跟設定路由一樣是要設定`path`跟`component`的。
```typescript
const routes: Routes = [
{ path: 'first-component',
component: FirstComponent, // this is the component with the <router-outlet> in the template
children: [
{
path: 'child-a', // child route path
component: ChildAComponent // child route component that the router renders
},
{
path: 'child-b',
component: ChildBComponent // another child route component that the router renders
}
]
}
]
```
#### *4.* 防止未經授權的訪問 #路由守衛
[Angular文件-preventing-unauthorized-access](https://angular.tw/guide/router#preventing-unauthorized-access)
- 當專案需要設計權限功能(希望使用者在未符合特定條件時不可進到此路由)的時候,可以依自己的需求來選擇設定Angular提供的以下路由守衛。
- `CanActivate`
- `CanActivateChild`
- `CanDeactivate`
- `Resolve`
- `CanLoad`
- 可以透過Angular CLI 來新增路由守衛,接下來就可以撰寫相關的邏輯在路由守衛(ex: CanActicate)內,最後只要在路由掛上路由守衛就可以了。
==終端機==
```npm
ng generate guard [guard name]
// or
ng g g [guard name]
```
==FirstGuard==
```typescript
export class FirstGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
// your logic goes here
}
}
```
==FirstRoutingModule==
```typescript
const routes: Routes = [
{
path: '/first',
component: FirstComponent,
canActivate: [FirstGuard],
}
]
```