路由是 Angular 應用程序的核心,它加載與所請求路由相關聯的組件,以及獲取特定路由的相關數據。這允許我們通過控制不同的路由,獲取不同的數據,從而渲染不同的頁面。
Installing the router
yarn add @angular/router
# OR
npm i --save @angular/router
以上命令執行后,將會自動下載 @angular/router
模塊到 node_modules
文件夾中。正常的angular項目一般不需要進行此步,在構建想的時候已經將此包下載。
Base href
我們需要做的最后一件事,是將 <base>
標簽添加到我們的 index.html
文件中。路由需要根據這個來確定應用程序的根目錄。例如,當我們轉到 http://example.com/page1
時,如果我們沒有定義應用程序的基礎路徑,路由將無法知道我們的應用的托管地址是 http://example.com
還是 http://example.com/page1
。
這件事操作起來很簡單,只需打開項目中的 index.html
文件,添加相應的 <base>
標簽,具體如下:
1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>Mostlove</title> 6 <base href="/"> 7 8 <meta name="viewport" content="width=device-width, initial-scale=1"> 9 <link rel="icon" type="image/x-icon" href="favicon.ico"> 10 </head>
以上配置信息告訴 Angular 路由,應用程序的根目錄是 /
。如果沒有此處的配置Angular將無法進行路由功能。
1.路由相關配置
路由類設置
/*路由基本模型*/
/*導入RouterModule,Routes類型*/
import { RouterModule, Routes } from '@angular/router'; import { LoginComponent } from "./login/login.component"; /*定義路由const表示不可改變*/ const routers: Routes = [ /* path:字符串,表示默認登入, path為路徑 /login component:組件 component:組件 pathMatch:為字符串默認為前綴匹配 "prefix"; "full" 為完全匹配。 redirectTo:指向為路徑,既path outlet:字符串,路由目標,面對多個路由的情況 children:Routes 子路由相關 */ { path: '', component: LoginComponent }, // path:路徑 /detail/1 :id代表參數相關 { path: 'detail/:id', component: LoginComponent }, // 懶加載子模塊, 子模塊需要配置路由設置啟動子組件,如果這樣設置了路由,需要在子模塊中再定義路由 { path: 'other', loadChildren:"./demo/demo.module#demoModule" }, // 重定向,路徑為** 表示不能識別的路徑信息,重定向到相關路徑下 { path: '**', pathMatch: 'full', redirectTo: '' } ]; /*將路由設置導出,子模塊中的路由使用 forChild 而不是 forRoot*/ export const appRouter = RouterModule.forRoot(routers);
ngModule設置
@NgModule({ declarations: [ ...... ], imports: [ ...... appRouter ] })
組件模板設置
<router-outlet></router-outlet>
2.多路由處理
{ path: 'news', outlet: 'let1', component: NewsComponent
}, { path: 'news', outlet: 'let2', component: News2Cmponent
}, //模板中
<router-outlet name="let1"></router-outlet> <router-outlet name="let2"></router-outlet>
訪問 /news/
時同時加載 NewsComponent
和 News2Cmponent
兩個組件
3.路有鏈接以及組件中調用路由方法使用
<a routerLink="/detail/1" routerLinkActive="active">detail</a>
<a [routerLink]="['/detail', news.id]">{{news.title}}</a>
<a [routerLink]="[{ outlets: { let2: ['news'] } }]">Contact</a>
routerLinkActive="active"
即在本路由激活時添加樣式 .active
或者:
this.router.navigate(['/detail', this.news.id]) this.router.navigate([{ outlets: { let2: null }}]);
其中:navigateByUrl 方法指向完整的絕對路徑
4.路由守衛(適用於后台管理等需要登錄才能使用的模塊)
import { Injectable } from '@angular/core'; import { CanActivate } from '@angular/router'; @Injectable() export class AuthService implements CanActivate { canActivate() { // 這里判斷登錄狀態, 返回 true 或 false return true; } }
在路由配置中的設置
{ path: '', component: LoginComponent, canActivate:[LoginComponent] },
5.退出守衛(適合於編輯器修改后的保存提示等場景)
import { Injectable } from '@angular/core'; import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; // CanDeactivateComponent 是定義的接口,見下段代碼 import { CanDeactivateComponent } from './can-deactivate.omponent'; @Injectable() export class DeacService implements CanDeactivate<CanDeactivateComponent> { canDeactivate( canDeactivateComponent: CanDeactivateComponent, activatedRouteSnapshot: ActivatedRouteSnapshot, routerStateSnapshot: RouterStateSnapshot ) { // 目標路由和當前路由 console.log(activatedRouteSnapshot); console.log(routerStateSnapshot); // 判斷並返回 return canDeactivateComponent.canDeactivate ? canDeactivateComponent.canDeactivate() : true } }..
// 接口組件, 返回 true 或 false 如表單發生改變則調用對話框服務
export interface CanDeactivateComponent { canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean; }
路由配置
{
path: ..., canDeactivate: [DeacService], component: ... }
模塊中添加服務
providers: [ DeactivateGuardService ]
Using the router
要使用路由,我們需要在 AppModule
模塊中,導入 RouterModule
。具體如下:
1 mport { BrowserModule } from '@angular/platform-browser'; 2 import { NgModule } from '@angular/core'; 3 import { FormsModule } from '@angular/forms'; 4 import { HttpModule } from '@angular/http'; 5 import { RouterModule } from "@angular/router";
此時我們的路由還不能正常工作,因為我們還未配置應用程序路由的相關信息。RouterModule
對象為我們提供了兩個靜態的方法:forRoot()
和 forChild()
來配置路由信息。
RouterModule.forRoot()
RouterModule.forRoot() 方法用於在主模塊中定義主要的路由信息,通過調用該方法使得我們的主模塊可以訪問路由模塊中定義的所有指令。接下來我們來看一下如何使用 forRoot()
:
1 @NgModule({ 2 declarations: [ 3 AppComponent, 4 NavigationComponent, 5 SideComponent, 6 // DisplayComponent 7 ], 8 imports: [ 9 BrowserModule, 10 FormsModule, 11 HttpClientModule, 12 HttpModule, 13 RouterModule.forRoot(ROUTES), 14 // SideModule 15 ],
我們通過使用 const
定義路由的配置信息,然后把它作為參數調用 RouterModule.forRoot()
方法,而不是直接使用 RouterModule.forRoot([...])
這種方式,這樣做的好處是方便我們在需要的時候導出 ROUTES
到其它模塊中。
RouterModule.forChild()
RouterModule.forChild() 與 Router.forRoot() 方法類似,但它只能應用在特性模塊中。
友情提示:根模塊中使用 forRoot()
,子模塊中使用 forChild()
這個功能非常強大,因為我們不必在一個地方(我們的主模塊)定義所有路由信息。反之,我們可以在特性模塊中定義模塊特有的路由信息,並在必要的時候將它們導入我們主模塊。
1 import { NgModule } from '@angular/core'; 2 import { CommonModule } from '@angular/common'; 3 import { Routes, RouterModule } from '@angular/router'; 4 5 export const ROUTES: Routes = []; 6 7 @NgModule({ 8 imports: [ 9 CommonModule, 10 RouterModule.forChild(ROUTES) 11 ], 12 // ... 13 }) 14 export class ChildModule {}
通過以上示例,我們知道在主模塊和特性模塊中,路由配置對象的類型是一樣的,區別只是主模塊和特性模塊中需調用不同的方法,來配置模塊路由。接下來我們來介紹一下如何配置 ROUTES
對象。
Configuring a route
我們定義的所有路由都是作為 ROUTES
數組中的對象。首先,為我們的主頁定義一個路由:
1 import { Routes, RouterModule } from '@angular/router'; 2 3 import { HomeComponent } from './home/home.component'; 4 5 export const ROUTES: Routes = [ 6 { path: '', component: HomeComponent } 7 ];
示例中我們通過 path
屬性定義路由的匹配路徑,而 component
屬性用於定義路由匹配時需要加載的組件。
友情提示:我們使用 path: ''
來匹配空的路徑,例如:https://yourdomain.com
Displaying routes
配置完路由信息后,下一步是使用一個名為 router-outlet
的指令告訴 Angular 在哪里加載組件。當 Angular 路由匹配到響應路徑,並成功找到需要加載的組件時,它將動態創建對應的組件,並將其作為兄弟元素,插入到 router-outlet
元素中。
在我們 AppComponent
組件中,我們可以在任意位置插入 router-outlet
指令:我們現在已經建立了應用程序的主路由,我們可以進一步了解路由的其它配置選項。
Further configuration
到目前為止我們已經介紹的內容只是一個開始 ,接下來我們來看看其它一些選項和功能。
Dynamic routes
如果路由始終是靜態的,那沒有多大的用處。例如 path: ''
是加載我們 HomeComponent
組件的靜態路由。我們將介紹動態路由,基於動態路由我們可以根據不同的路由參數,渲染不同的頁面。
例如,如果我們想要在個人資料頁面根據不同的用戶名顯示不同的用戶信息,我們可以使用以下方式定義路由:
1 import { HomeComponent } from './home/home.component'; 2 import { ProfileComponent } from './profile/profile.component'; 3 4 export const ROUTES: Routes = [ 5 { path: '', component: HomeComponent }, 6 { path: '/profile/:username', component: ProfileComponent } 7 ];
這里的關鍵點是 :
,它告訴 Angular 路由,:username
是路由參數,而不是 URL 中實際的部分。
友情提示:如果沒有使用 :
,它將作為靜態路由,僅匹配 /profile/username
路徑
現在我們已經建立一個動態路由,此時最重要的事情就是如何獲取路由參數。要訪問當前路由的相關信息,我們需要先從 @angular/router
模塊中導入 ActivatedRoute
,然后在組件類的構造函數中注入該對象,最后通過訂閱該對象的 params
屬性,來獲取路由參數,具體示例如下:
1 import { Component, OnInit } from '@angular/core'; 2 import { ActivatedRoute } from '@angular/router'; 3 4 @Component({ 5 selector: 'profile-page', 6 template: ` 7 <div class="profile"> 8 <h3>{{ username }}</h3> 9 </div> 10 ` 11 }) 12 export class SettingsComponent implements OnInit { 13 username: string; 14 constructor(private route: ActivatedRoute) {} 15 ngOnInit() { 16 this.route.params.subscribe((params) => this.username = params.username); 17 } 18 }
介紹完動態路由,我們來探討一下如何創建 child routes
。
Child routes
實際上每個路由都支持子路由,假設在我們 /settings
設置頁面下有 /settings/profile
和 /settings/password
兩個頁面,分別表示個人資料頁和修改密碼頁。
我們可能希望我們的 / settings
頁面擁有自己的組件,然后在設置頁面組件中顯示 / settings/profile
和 / settings/password
頁面。我們可以這樣做:
1 import { SettingsComponent } from './settings/settings.component'; 2 import { ProfileSettingsComponent } from './settings/profile/profile.component'; 3 import { PasswordSettingsComponent } from './settings/password/password.component'; 4 5 export const ROUTES: Routes = [ 6 { 7 path: 'settings', 8 component: SettingsComponent, 9 children: [ 10 { path: 'profile', component: ProfileSettingsComponent }, 11 { path: 'password', component: PasswordSettingsComponent } 12 ] 13 } 14 ];
在這里,我們在 setttings
路由中定義了兩個子路由,它們將繼承父路由的路徑,因此修改密碼頁面的路由匹配地址是 /settings/password
,依此類推。
接下來,我們需要做的最后一件事是在我們的 SettingsComponent
組件中添加 router-outlet
指令,因為我們要在設置頁面中呈現子路由。如果我們沒有在 SettingsComponent
組件中添加 router-outlet
指令,盡管 /settings/password
匹配修改密碼頁面的路由地址,但修改密碼頁面將無法正常顯示。具體代碼如下:
1 import { Component } from '@angular/core'; 2 3 @Component({ 4 selector: 'settings-page', 5 template: ` 6 <div class="settings"> 7 <settings-header></settings-header> 8 <settings-sidebar></settings-sidebar> 9 <router-outlet></router-outlet> 10 </div> 11 ` 12 }) 13 export class SettingsComponent {}
Component-less routes
另一個很有用的路由功能是 component-less
路由。使用 component-less
路由允許我們將路由組合在一起,並讓它們共享路由配置信息和 outlet。
例如,我們可以定義 setttings
路由而不需要使用 SettingsComponent
組件:
1 import { ProfileSettingsComponent } from './settings/profile/profile.component'; 2 import { PasswordSettingsComponent } from './settings/password/password.component'; 3 4 export const ROUTES: Routes = [ 5 { 6 path: 'settings', 7 children: [ 8 { path: 'profile', component: ProfileSettingsComponent }, 9 { path: 'password', component: PasswordSettingsComponent } 10 ] 11 } 12 ];
此時, /settings/profile
和 /settings/password
路由定義的內容,將顯示在 AppComponent
組件的 router-outlet
元素中。
loadChildren
我們也可以告訴路由從另一個模塊中獲取子路由。這將我們談論的兩個想法聯系在一起 - 我們可以指定另一個模塊中定義的子路由,以及通過將這些子路由設置到特定的路徑下,來充分利用 component-less
路由的功能。
讓我們創建一個 SettingsModule
模塊,用來保存所有 setttings
相關的路由信息:
1 import { NgModule } from '@angular/core'; 2 import { CommonModule } from '@angular/common'; 3 import { Routes, RouterModule } from '@angular/router'; 4 5 export const ROUTES: Routes = [ 6 { 7 path: '', 8 component: SettingsComponent, 9 children: [ 10 { path: 'profile', component: ProfileSettingsComponent }, 11 { path: 'password', component: PasswordSettingsComponent } 12 ] 13 } 14 ]; 15 16 @NgModule({ 17 imports: [ 18 CommonModule, 19 RouterModule.forChild(ROUTES) 20 ], 21 }) 22 export class SettingsModule {}
需要注意的是,在 SettingsModule
模塊中我們使用 forChild()
方法,因為 SettingsModule
不是我們應用的主模塊。
另一個主要的區別是我們將 SettingsModule
模塊的主路徑設置為空路徑 ('')。因為如果我們路徑設置為 /settings
,它將匹配 /settings/settings
,很明顯這不是我們想要的結果。通過指定一個空的路徑,它就會匹配 /settings
路徑,這就是我們想要的結果。
那么 /settings
路由信息,需要在哪里配置?答案是在 AppModule
中。這時我們就需要用到 loadChildren
屬性,具體如下:
export const ROUTES: Routes = [ { path: 'settings', loadChildren: './settings/settings.module#SettingsModule' } ];
需要注意的是,我們沒有將 SettingsModule
導入到我們的 AppModule
中,而是通過 loadChildren
屬性,告訴 Angular 路由依據 loadChildren
屬性配置的路徑去加載 SettingsModule
模塊。這就是模塊懶加載功能的具體應用,當用戶訪問 /settings/**
路徑的時候,才會加載對應的 SettingsModule
模塊,這減少了應用啟動時加載資源的大小。
另外我們傳遞一個字符串作為 loadChildren
的屬性值,該字符串由三部分組成:
-
需要導入模塊的相對路徑
-
#
分隔符 -
導出模塊類的名稱
了解完路由的一些高級選項和功能,接下來我們來介紹路由指令。
Router Directives
除了 router-outlet
指令,路由模塊中還提供了一些其它指令。讓我們來看看它們如何與我們之前介紹的內容結合使用。
routerLink
為了讓我們鏈接到已設置的路由,我們需要使用 routerLink
指令,具體示例如下:
1 <nav> 2 <a routerLink="/">Home</a> 3 <a routerLink="/settings/password">Change password</a> 4 <a routerLink="/settings/profile">Profile Settings</a> 5 </nav>
當我們點擊以上的任意鏈接時,頁面不會被重新加載。反之,我們的路徑將在 URL 地址欄中顯示,隨后進行后續視圖更新,以匹配 routerLink
中設置的值。
友情提示:我們也可以將 routerLink
的屬性值,改成數組形式,以便我們傳遞特定的路由信息
如果我們想要鏈接到動態的路由地址,且該地址有一個 username
的路由變量,則我們可以按照以下方式配置 routerLink
對應的屬性值:
<a [routerLink]="['/profile', username]"> Go to {{ username }}'s profile. </a>
routerLinkActive
在實際開發中,我們需要讓用戶知道哪個路由處於激活狀態,通常情況下我們通過向激活的鏈接添加一個 class 來實現該功能。為了解決上述問題,Angular 路由模塊為我們提供了 routerLinkActive
指令,該指令的使用示例如下:
<nav> <a routerLink="/settings" routerLinkActive="active">Home</a> <a routerLink="/settings/password" routerLinkActive="active">Change password</a> <a routerLink="/settings/profile" routerLinkActive="active">Profile Settings</a> </nav>
通過使用 routerLinkActive
指令,當 a
元素對應的路由處於激活狀態時,active
類將會自動添加到 a
元素上。
最后,我們來簡單介紹一下 Router API。
Router API
我們可以通過路由還提供的 API 實現與 routerLink
相同的功能。要使用 Router API,我們需要在組件類中注入 Router
對象,具體如下:
1 import { Component } from '@angular/core'; 2 import { Router } from '@angular/router'; 3 4 @Component({ 5 selector: 'app-root', 6 template: ` 7 <div class="app"> 8 <h3>Our app</h3> 9 <router-outlet></router-outlet> 10 </div> 11 ` 12 }) 13 export class AppComponent { 14 constructor(private router: Router) {} 15 }
組件類中注入的 router
對象中有一個 navigate()
方法,該方法支持的參數類型與 routerLink
指令一樣,當調用該方法后,頁面將會自動跳轉到對應的路由地址。具體使用示例如下:
1 import { Component, OnInit } from '@angular/core'; 2 import { Router } from '@angular/router'; 3 4 @Component({ 5 selector: 'app-root', 6 template: ` 7 <div class="app"> 8 <h3>Our app</h3> 9 <router-outlet></router-outlet> 10 </div> 11 ` 12 }) 13 export class AppComponent implements OnInit { 14 constructor(private router: Router) {} 15 ngOnInit() { 16 setTimeout(() => { 17 this.router.navigate(['/settings']); 18 }, 5000); 19 } 20 }
若以上代碼成功運行,用戶界面將在 5 秒后被重定向到 /settings
頁面。這個方法非常有用,例如當檢測到用戶尚未登錄時,自動重定向到登錄頁面。
另一個使用示例是演示頁面跳轉時如何傳遞數據,具體如下:
1 import { Component, OnInit } from '@angular/core'; 2 import { Router } from '@angular/router'; 3 4 @Component({ 5 selector: 'app-root', 6 template: ` 7 <div class="app"> 8 <h3>Users</h3> 9 <div *ngFor="let user of users"> 10 <user-component 11 [user]="user" 12 (select)="handleSelect($event)"> 13 </user-component> 14 </div> 15 <router-outlet></router-outlet> 16 </div> 17 ` 18 }) 19 export class AppComponent implements OnInit { 20 users: Username[] = [ 21 { name: 'toddmotto', id: 0 }, 22 { name: 'travisbarker', id: 1 }, 23 { name: 'tomdelonge', id: 2 } 24 ]; 25 26 constructor(private router: Router) {} 27 28 handleSelect(event) { 29 this.router.navigate(['/profile', event.name]); 30 } 31 }
Angular 路由的功能非常強大,既可以使用指令方式也可以使用命令式 API,希望本文可以幫助你盡快入門,若要進一步了解路由詳細信息,請訪問 - Angular Router 官文文檔。
我有話說
除了使用 navigate()
方法外還有沒有其它方法可以實現頁面導航?
Angular Router API 為我們提供了 navigate()
和 navigateByUrl()
方法來實現頁面導航。那為什么會有兩個不同的方法呢?
使用 router.navigateByUrl()
方法與直接改變地址欄上的 URL 地址一樣,我們使用了一個新的 URL 地址。然而 router.navigate()
方法基於一系列輸入參數,產生一個新的 URL 地址。為了更好的區分它們之間的差異,我們來看個例子,假設當前的 URL 地址是:
/inbox/11/message/22(popup:compose)
當我們調用 router.navigateByUrl('/inbox/33/message/44')
方法后,此時的 URL 地址將變成 /inbox/33/message/44
。但如果我們是調用 router.navigate('/inbox/33/message/44')
方法,當前的 URL 地址將變成 /inbox/33/message/44(popup:compose)
。