路由是 Angular 应用程序的核心,它加载与所请求路由相关联的组件,以及获取特定路由的相关数据。这容许咱们经过控制不一样的路由,获取不一样的数据,从而渲染不一样的页面。css
Routes
实际上是一个Route类的数组。html
而Route
的参数以下图所示,通常状况下,path
和component
是必选的两个参数。
好比:path:/a,component:A
则说明,当地址为/a
时,应该展现组件A
的内容。node
其他类的简介见下图:jquery
输入命令ng new router --routing
新建一个名叫router
的项目,其中--routing
命令参数表明在项目基础上添加一个路由配置文件app-routingcodule.ts
。typescript
能够看到路由配置文件已经生成,其初始内容是:npm
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
的错误:数组
解决办法:app
node_modules
目录npm install -g @angular/cli
在路由配置文件中为routes
赋值:dom
const routes: Routes = [{path: '', component: HomeComponent}, {path: 'books', component: BooksComponent}];
其中HomeComponent
和BooksComponent
分别对应咱们新增的两个组件。
另外须要注意path
参数中不须要输入/
!iphone
打开app.component.html
,输入一下代码:
<a [routerLink]="['/']">主页</a> <a [routerLink]="['/books']">书籍</a> <router-outlet></router-outlet>
启动项目就能够看到效果了:
要使用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]
;{path:/product/:id} => /product/1 => ActivatedRoute.params[id]
;{path:/product,component:ProductComponent,data:[{madeInChina:true}]} => ActivatedRoute.data[0][madeInChina]
;与注入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组件的模板文件中添加绑定信息:
最终呈现效果:
首先须要在路由配置文件中预约义参数的名称:
而后在<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
组件内容,当点击结束咨询
时文本域消失,页面左侧仍然显示原有内容。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绑定的类型参数;
新建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则可进入,不然被阻止。
需求:
当咨询窗口已被打开,而且已经输入了内容,此时忽然点击结束,会被阻止,相似博客编辑到一半忽然访问其余网站时会给出提示同样。
新建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]
为方便演示,先将路由配置文件中关于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
~