路由簡介
路由是 Angular 應用程序的核心,它加載與所請求路由相關聯的組件,以及獲取特定路由的相關數據。這允許我們通過控制不同的路由,獲取不同的數據,從而渲染不同的頁面。
相關的類

Routes
Routes其實是一個Route類的數組。

而Route的參數如下圖所示,一般情況下,path和component是必選的兩個參數。
比如:path:/a,component:A則說明,當地址為/a時,應該展示組件A的內容。

其余類的簡介見下圖:

應用
新建項目
輸入命令ng new router --routing新建一個名叫router的項目,其中--routing命令參數代表在項目基礎上添加一個路由配置文件app-routingcodule.ts。

可以看到路由配置文件已經生成,其初始內容是:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

新增組件
在項目根路徑下運行命令ng g component home和ng g component books來新增home和books兩個組件,此時可能會出現too many symbolic links encountered的錯誤:

解決辦法:
- 刪除根目錄下的
node_modules目錄 - 更新cli命令行工具
npm install -g @angular/cli - 重新安裝:npm install
配置路由
在路由配置文件中為routes賦值:
const routes: Routes = [{path: '', component: HomeComponent}, {path: 'books', component: BooksComponent}];
其中HomeComponent和BooksComponent分別對應我們新增的兩個組件。
另外需要注意path參數中不需要輸入/!
定義模板
打開app.component.html,輸入一下代碼:
<a [routerLink]="['/']">主頁</a>
<a [routerLink]="['/books']">書籍</a>
<router-outlet></router-outlet>
啟動項目就可以看到效果了:

使用Router
要使用Router對象,首先需要在控制器文件中定義它,一般是在構造函數constructor中傳遞相關參數:
constructor(private router: Router) {
}
這樣就可以在控制器中使用router來跳轉路由了,比如寫個方法toBookDetails:
toBookDetails() {
router.navigate(['/books']);
}
如何調用呢,在模板文件中通過定義一個按鈕並用(click)來綁定即可:
<input type="button" value="書籍" (click)="toBookDetails()">
效果如下:

通配符
以上我們都假設輸入的路徑都是存在的,但是假如用戶不小心輸入了一個不存在的路徑,我們有義務給予一些善意的提醒,如何實現呢?這就要使用通配符了,其用法如下:
首先創建一個新增模塊err404模塊,用於輸入不存在的路徑時展示,命令如下:
ng g component err404;
然后在路由配置文件中添加一條路由信息:
const routes: Routes = [
{path: '', component: HomeComponent},
{path: 'books', component: BooksComponent},
{path: '**', component: Err404Component}
];
程序會根據用戶定義的路由順序依次匹配,當所有明確指定的路由均不滿足匹配條件時,才會進入Err404Component組件,由於是依次匹配,所以將其放入最后。
最后,輸入http://localhost:4200/test,結果如下:

參數傳遞
參數傳遞的幾種方式:
- 普通方式傳遞數據:
/product?id=1&name=iphone => ActivatedRoute.queryParams[id]; - rest方式傳遞數據:
{path:/product/:id} => /product/1 => ActivatedRoute.params[id]; - 路由配置傳遞數據:
{path:/product,component:ProductComponent,data:[{madeInChina:true}]} => ActivatedRoute.data[0][madeInChina];
注入ActivatedRoute
與注入Router對象一樣,將其置於組件控制器的構造函數中,此處以BooksComponent為例並為其增加一個bookname的屬性,讓其在初始化時直接顯示屬性bookname的值:
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Params} from '@angular/router';
@Component({
selector: 'app-books',
templateUrl: './books.component.html',
styleUrls: ['./books.component.css']
})
export class BooksComponent implements OnInit {
private bookname: string;
constructor(private activatedRout: ActivatedRoute) {
}
ngOnInit() {
// 普通方式傳參的快照獲取方式
this.bookname = this.activatedRout.snapshot.queryParams['bookname'];
}
}
其中snapshot表示快照方式,還可以使用訂閱subscribe方式,下面再說。
普通方式傳參
在模板文件app.component.html中為/books所在的<a>標簽中添加屬性[queryParams]:
<a [routerLink]="['/books']" [queryParams]="{bookname:'《活着》'}">書籍</a>
此時點擊顯示的路由信息則為:
為了更明確起見,在books組件的模板文件中添加綁定信息:

最終呈現效果:

rest方式傳參
首先需要在路由配置文件中預定義參數的名稱:

然后在<a>中直接傳遞值:
<a [routerLink]="['/books','《活着》']">書籍</a>
此時點擊鏈接時所呈現的路徑變為:

最后,通過params方法來獲取參數的值:
// rest方式傳參的快照獲取方式
this.bookname = this.activatedRout.snapshot.params['bookname'];
這樣就可以呈現同上面一樣的效果的內容了。那么,如何通過方法來傳遞rest形式的參數呢?其實同<a>的格式一樣:
toBookDetails() {
this.router.navigate(['/books', '《簡愛》']);
}
此時我傳入的參數是《簡愛》,在鏈接主頁和鏈接書籍或者在鏈接主頁和按鈕書籍之間切換點擊時,內容的顯示是沒有問題的,但是在鏈接書籍和按鈕書籍之間切換點擊時,底下展示的內容不發生變化,這時就要用到參數訂閱的方式來實時呈現了:
// rest方式傳參的訂閱獲取方式
this.activatedRout.params.subscribe((params: Params) => this.bookname = params['bookname']);
路由配置傳參
這種傳參是靜態的,假設此時需要添加一個參數isChineseVersion來表示是否是中文版,其配置形式如下:

此時在book組件控制器中新增一個boolean類型參數isChineseVersion,並在初始化方法ngOnInit中為其賦值:
this.isChineseVersion = this.activatedRout.snapshot.data[0]['isChineseVersion'];,最后在模板文件中綁定即可:
<p>
is Chinese Version: {{isChineseVersion}}
</p>
重定向路由
在用戶訪問一個特定地址時,將其重定向到另一個指定的地址。
此處先將所有指向HomeComponent的根路徑全部改為/home,包括路由配置文件和<a>。
此時雖然指定了當路徑為/home時才指向HomeComponent組件,但如果我希望訪問根路徑時直接展示HomeComponent的內容是怎么辦,重定向路由可以幫助我們實現,語法如下:
const routes: Routes = [
{path: '', redirectTo: '/home', pathMatch: 'full'},
{path: 'home', component: HomeComponent},
{path: 'books/:bookname', component: BooksComponent, data: [{isChineseVersion: true}]},
{path: '**', component: Err404Component}
];
需要注意的是,pathMatch屬性的值,當為full時,表示只有當路徑為根路徑時才指向HomeComponent組件,它還有另一個值:prefix,意思是匹配前綴,試了之后發現沒有用,比如
{path: 'hh', redirectTo: '/home', pathMatch: 'prefix'},當訪問的路徑為http://localhost:4200/h或者http://localhost:4200/hhh時都不會呈現主頁內容而是直接跳到Err404的內容,此處存疑。
子路由
子路由定義的格式如下:

上圖中子路由的意思是,當訪問/home/時,展示HomeComponent和XxxComponent的內容,當訪問/home/yyy時,展示HomeComponent和YyyComponent的內容。
明確需求
在書籍模塊的模板下方,增加兩個鏈接,《簡愛》的中文簡介和英文簡介,點擊時分別顯示對應語言的簡介,默認顯示中文簡介。
實現
- 生成組件
introduction_cn=>ng g component introduction_cn和
introduction_en=>ng g component introduction_en - 分別在對應中英文模板中添加顯示的內容,代碼略;
- 在路由配置文件中定義子路由:
{
path: 'books/:bookname', component: BooksComponent, children: [
{path: '', component: IntroductionCnComponent},
{path: 'en', component: IntroductionEnComponent}
]
}
- 在書籍模板中定義鏈接和定位顯示位置:
<p>
books works!
</p>
<p>
query book is name of {{bookname}}
</p>
<a [routerLink]="['./']">中文簡介</a>
<a [routerLink]="['./en']">英文簡介</a>
<router-outlet></router-outlet>
效果

多個路由
以上的例子中都是一個路由來操控頁面的顯示內容,但是實際上頁面總是有多個模塊,如何來分配各自區域的顯示控制呢?這時就要用到多路由了,比如在上面的例子中,在主界面上只定義了一個<router-outlet></router-outlet>,當我們定義多個路由時就需要加以區分,實現方式很簡單,在<router-outlet>上加一個name屬性並賦值即可,比如:<router-outlet name = "a"></router-outlet>,此時a就是此路由定位器的標識,當不定義name屬性時,其名稱默認為primary。
下面通過實際例子做個簡單演示:
- 需求
在原有基礎上添加兩個鏈接開始咨詢和結束咨詢,當點擊開始咨詢時頁面右側顯示文本域,同時左側顯示HomeComponent組件內容,當點擊結束咨詢時文本域消失,頁面左側仍然顯示原有內容。 - 為原有組件添加樣式,使其寬度變為70%,並且靠頁面左邊,此例中涉及的組件有
HomeComponent/BooksComponent,以主頁組件為例演示:
使用div元素將原有內容包裹,並定義class值為home
<div class="home">
<p>
home works!
</p>
</div>
定義樣式:
.home {
background-color: yellowgreen;
height: 750px;
width: 70%;
float: left;
box-sizing: border-box;
}
同理定義BooksComponent模板及樣式文件。
- 新增咨詢組件
ng g component advise - 定義模板
<textarea placeholder="請輸入內容" class="advise"></textarea>
- 定義樣式
.advise {
background-color: green;
height: 750px;
width: 30%;
float: left;
box-sizing: border-box;
}
- 新增路由
advise
在路由配置文件中新增路由並制定定位器:

-
新增
開始咨詢鏈接並指定定位器
指定定位器仍然通過routerLink,格式:<a [routerLink]="[{outlets:{primary:'home', advise:'advise'}}]">開始咨詢</a>
表示點擊鏈接時,沒有名字(前面說了,它的默認名字叫primary)的定位器處顯示路徑為/home即組件是HomeComponent的內容,而名叫advise定位器處顯示路徑為/advise即組件是AdviseComponent的內容。
同理,定義結束咨詢的:<a [routerLink]="[{outlets:{advise:null}}]">書籍</a> -
添加名叫
advise的定位器

-
效果

路由守衛
路由守衛一般涉及三個類:CanActivate,CanDeactivate,Resolve.
CanActivate:決定是否有權限進入某路由;
CanDeactivate:決定是否可以離開某路由;
Resolve:初始化某個組件中的被Resolve綁定的類型參數;
CanActivate用法
新建ts文件:PermissionGuard.ts
import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot} from '@angular/router';
import {Observable} from 'rxjs/Observable';
import * as _ from 'lodash';
export class PermissionGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> | Promise<boolean> {
const rand = _.random(0, 5);
console.log(rand);
return rand % 2 === 0;
}
}
在路由配置文件中增加canActivate參數:
const routes: Routes = [
{path: '', redirectTo: '/home', pathMatch: 'full'},
{path: 'home', component: HomeComponent},
{path: 'advise', component: AdviseComponent, outlet: 'advise'},
/*{path: 'books/:bookname', component: BooksComponent, data: [{isChineseVersion: true}]},*/
{
path: 'books/:bookname',
component: BooksComponent,
children: [
{path: '', component: IntroductionCnComponent},
{path: 'en', component: IntroductionEnComponent}
],
canActivate: [PermissionGuard]
},
{path: '**', component: Err404Component}
];
在模塊配置文件app.moudle.ts中的providers參數中增加實現了CanActivate接口的值:
providers: [PermissionGuard]
目的是實例化PermissionGuard。
此時,再次點擊鏈接書籍時會先判斷生成的隨機數除2時的余數,余0則可進入,否則被阻止。
CanDeactivate用法
需求:
當咨詢窗口已被打開,並且已經輸入了內容,此時突然點擊結束,會被阻止,類似博客編輯到一半突然訪問其他網站時會給出提示一樣。
新建ts文件LeaveGuard.ts:
import {CanDeactivate} from '@angular/router';
import {AdviseComponent} from '../advise/advise.component';
import {Observable} from 'rxjs/Observable';
export class LeaveGuard implements CanDeactivate<AdviseComponent> {
/**
* 下面這段代碼是自帶實現,暫時注掉不用它。
* canDeactivate(component: AdviseComponent,
* currentRoute: ActivatedRouteSnapshot,
* currentState: RouterStateSnapshot,
* nextState?: RouterStateSnapshot)
* : boolean | Observable<boolean> | Promise<boolean> {
* throw new Error("Method not implemented.");
* }
* @param advise
* @returns {boolean}
*/
canDeactivate(advise: AdviseComponent): boolean | Observable<boolean> | Promise<boolean> {
return advise.isEmpty();
}
}
可以看到CanDeactivate不同於CanActivate,使用時需要輸入泛型類,用以綁定數據。代碼中使用了isEmpty(),它是咨詢組件AdviseComponent中的一個方法,表示當輸入的咨詢內容為空時返回真,代碼如下:
import {Component, OnInit} from '@angular/core';
import * as $ from 'jquery';
@Component({
selector: 'app-advise',
templateUrl: './advise.component.html',
styleUrls: ['./advise.component.css']
})
export class AdviseComponent implements OnInit {
constructor() {
}
ngOnInit() {
}
isEmpty(): boolean {
const adviseVal = $('textarea').val();
console.log(adviseVal);
return adviseVal === '';
}
}
在路由配置文件中為組件AdviseComponent添加canDeactivate屬性並賦值:
{
path: 'advise',
component: AdviseComponent,
outlet: 'advise',
canDeactivate: [LeaveGuard]
}
在模塊文件app.module.ts參數providers新增LeaveGuard用以實例化:
providers: [PermissionGuard, LeaveGuard]
Resolve
為方便演示,先將路由配置文件中關於BookComponent的canActivate參數注掉,防止干擾。
在books.componnet.ts中新建類Book,將屬性bookname替換名為book類型為Book的成員變量,添加對對此成員變量的訂閱方法:
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
@Component({
selector: 'app-books',
templateUrl: './books.component.html',
styleUrls: ['./books.component.css']
})
export class BooksComponent implements OnInit {
public book: Book;
private isChineseVersion: boolean;
constructor(private activatedRout: ActivatedRoute) {
}
ngOnInit() {
// 普通方式傳參的快照獲取方式
// this.bookname = this.activatedRout.snapshot.queryParams['bookname'];
// rest方式傳參的快照獲取方式
// this.bookname = this.activatedRout.snapshot.params['bookname'];
// rest方式傳參的訂閱獲取方式
// this.activatedRout.params.subscribe((params: Params) => this.book.bookname = params['bookname']);
this.activatedRout.data.subscribe((data: { book: Book }) => this.book = data.book);
// 路由配置方式傳參的獲取方式
// this.isChineseVersion = this.activatedRout.snapshot.data[0]['isChineseVersion'];
}
}
export class Book {
public bookname: string;
public author?: string;
public private?: number;
constructor(bookname: string) {
this.bookname = bookname;
}
}
修改books.component.html中query book is name of {{bookname}} => query book is name of {{book?.bookname}}
新建BookResolve.ts:
import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
import {Book} from '../books/books.component';
import {Observable} from 'rxjs/Observable';
import {Injectable} from '@angular/core';
@Injectable()
export class BookResolve implements Resolve<Book> {
constructor(private router: Router) {
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Book | Observable<Book> | Promise<Book> {
const bookname = route.params['bookname'];
if (bookname === '《活着》' || bookname === '《簡愛》') {
console.log('當前輸入書名為:' + bookname);
return new Book('《簡愛》');
} else {
this.router.navigate(['/home']);
return null;
}
}
}
在路由控制文件中為組件BooksComponent添加resolve參數並賦值:resolve: {book: BookResolve}
在模塊文件app.module.ts參數providers新增BookResolve用以實例化:
providers: [PermissionGuard, LeaveGuard, BookResolve]
總結

源碼
http://pan.baidu.com/s/1bpNdgFt
使用前先運行ci.bat~
