###### 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], } ] ```