路由簡介
路由是 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
~