官网链接: angular官网 路由与导航
最好是跟着官网写一遍代码,然后来看这个总结,会比较清晰
如何实现一个简单的路由界面
1. 添加 <base>
标签
需要在index.htmlde <head>
的标签下添加一个 <base>
元素,规定页面中所有相对链接的基准url
// index.html
<base href="/">
2. 在module中导入路由库
// app.module.ts
import { RouterModule, Routes } from '@angular/router';
3. 路由配置
path为url路径,component为地址对应的组件,
然后将 appRoutes
传给 RouterModule.forRoot()
方法
(关于forRoot这个方法,具体会在服务这一块内容中介绍)
// app.module.ts
const appRoutes: Routes = [
{ path: 'hero', component: HeroComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(appRoutes);
]
})
4. 路由出口
在该标签下显示对应url的视图
可以简单可以理解为当路由发生变化时,会对该标签所在部分进行 jQuery.html()
类似的页面替换
// app.component.html
<router-outlet></router-outlet>
5. 路由链接
在 a
标签上添加routerLink指令,表示对应的url地址,
点击 a
标签,浏览器的url地址就会发生改变
// app.component.ts
templare: `
<a routerLink="/hero">Hero</a>
<router-outlet></router-outlet>
`
6. 查看页面
简单的路由就这么完成了,点击 a
标签下的hero,就会在 <router-outlet>
标签显示对应 component
页面
路由解读
1. 路由原理
官网解读,路由器使用了最新H5的history.pushState进行导航
pushState无刷新改变url
2. 增加 <base href="/">
在index.html的 <head>
标签下增加 <base href="/">
href属性规定页面中所有相对链接的基准url
例如将 href="/abc"
,路由都会变成此类地址 http://ip/abc/...
3. 路由跳转
3.1 a
标签routerLink
属性
routerLink
:表示点击后对应生效的路由规则
routerLinkActive
:表示路由生效后,向该元素添加css类
<a routerLink="/heroes" routerLinkActive="active">
当链接地址带参数的时候(例如链接到'hero/:id'
这个地址的时候)
// 注: 必须将[]中括号内的内容用""双引号括起来,不然会报错
routerLink="['life', 1]"
routerLinkActiveOptions
: 表示当路由完全匹配时,向该元素添加css类
例如下例,只有路径为/admin
的情况下,Dashboard
才会被加上active
的属性
<h3>ADMIN</h3>
<nav>
<a routerLink="./" routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }">Dashboard</a>
<router-outlet></router-outlet>
path: 'admin',
component: AdminComponent,
children: [
{
path: '',
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
]
}
]
3.2 navigate
跳转
除了在a
标签中添加routerLink
属性外,还可以通过navigate
这个方法进行跳转
this.router.navigate(['/heroes']);
如果需要在url地址中添加相关页面的信息时,可以通过
this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]);
(此时的url地址为/heroes;id=11;foo=foo
,之后可以通过下文中ActivatedRoute
来获取id
和foo
的取值了)
relativeTo
:跳转相对路径
constructor(
private route: ActivatedRoute
) {}
this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });
解释:
'../'
:返回上一层路径(当前路由为/a/b,则表示返回/a,当省略或者为./
则表示相对于当前级别)
{id: crisisId, foo: 'foo'}
:在url中添加相关信息
relativeTo
:当使用导航的相对路径时(../
)这个时,必须添加relativeTo
这个属性,表示为当前的ActivatedRoute
4. 路由配置
如何配置路由文件,除了简单的 path
路径 对应 component
,还有些其他的简单配置
a. 通配符
**
:匹配所有路由规则,当url地址不在配置的路由中时,就会显示该页面
{ path: '**', component: '...' }
注意
:路由规则是按路由配置中的顺序从上往下进行url地址匹配,所以该规则不能写在其他规则前面,不然其他的url路径都会被他给匹配上,所有页面就都会显示为通配符对应的component页面了
b. 重定向
redirectTo
:将path对应的url地址,重定向到redirectTo
的地址,对应显示该地址对应的component
如果该地址不存在的话,就会显示通配符对应的component
该重定向,还可以带路由参数,包括查询参数等
例如:
{ path: 'heroes', redirectTo: '/superheroes' },
{ path: 'hero/:id', redirectTo: '/superhero/:id' },
{ path: 'superheroes', component: HeroListComponent, data: { animation: 'heroes'} },
{ path: 'superhero/:id', component: HeroDetailComponent , data: {animation: 'hero'}}
注:
- 路由器只会处理一次重定向,所以不要将相同的路由重复重定向,例如
''->heroes
,而heroes->superheroes
,防止出现无限循环的重定向 - 如果重定向的路由,是有
<a>
标签跳转而来,注意同步修改下<a routerLink="xxx" routerLinkActive="active">
;如果修改的话,会导致路由激活时,active
属性消失 - 同时还需要修改
this.router.navigate(['/superheroes', { id: heroId, foo: 'foo'}]);
,这里跳转链接的值,如果此时还是hero
的话,就只会跳转到/superheroes
这个对应的重定向路由,后面的参数就没有了
patchMatch
:重定向必须添加的条件
,不然页面会报错
该字段有两个值:
-- full:表示url地址要全都匹配上,例子中的path: ''
,就表示当url为空时,会匹配该规则
-- prefix:表示只要url地址开头匹配,就会走到这个路由规则中来
{ path: '', redirectTo: '/heroes', pathMatch: 'full' }
注意
:当path: ''
时,pathMatch
只能选择为'full'
,不然其他的路由规则都会跑进该条规则下面,导致其他路由规则失效
c. 路由参数
当匹配到hero/12
等路径的时候,就会显示HeroDetailComponent
这个界面
{ path: 'hero/:id', component: HeroDetailComponent }
d. 子路由
children
属性,定义子路由
例如下例,当url地址为/test
时,将会显示Test1Component
、Test2Component
、Test3Component
,这三个组件;
且Test2Component
该组件将显示在Test1Component
该组件定义的router-outlet
该标签下,而Test2Component
则显示在Test2Component
该组件定义的router-outlet
标签下;
最终页面将会显示:
<p>组件1</p>
<p>组件2</p>
<p>组件3</p>
当url地址为/test/1
时,则对应显示
<p>组件1</p>
<p>组件2</p>
<p>组件4</p>
const TestRouter: Routes = [
{
path: 'test',
component: Test1Component,
children: [
{
path: '',
component: Test2Component,
children: [
{
path: ':id',
component: Test4Component
},
{
path: '',
component: Test3Component
}
]
}
]
}
];
// Test1Component
<p>组件1</p>
<router-outlet></router-outlet>
// Test2Component
<p>组件2</p>
<router-outlet></router-outlet>
// Test3Component
<p>组件3</p>
// Test4Component
<p>组件4</p>
e. 设置第二路由
当一个页面一个路由不足以展示其内容时,需要使用路径来显示其他内容,这个时候就用到个第二路由
url链接中显示为http://.../..(test:sec-url)
添加方法如下:
e.1:在原来的路由出口,先添加一个出口,需要用name
重新命名
<router-outlet></router-outlet>
<router-outlet name="test"></router-outlet>
e.2:添加路由
此时定义outlet
属性,表示该路由对应的是test
出口
{
path: 'sec-url',
component: SecComponent,
outlet: 'test'
},
e.3:添加a
链接
表示这个链接的router-outlet
的出口名为test
,然后对应的url路径为sec-url
,则表示显示该组件SecComponent
<a [routerLink]="[{ outlets: { test: ['sec-url'] } }]">Contact</a>
e.4 清除第二路由
将test
对应的组件设置为null,就可以清除该出口,并从URL中移除第二路由
(由于主路由的改变不会改变第二路由,除非router-outlet
对应的给替换了)
this.router.navigate([{ outlets: { test: null }}]);
5. 配置路由模块
a. 单独配置路由模块
``
相当于将路由模块作为一个特性模块,导出一个AppRoutingModule
,引入到主的模块文件中
当路由比较复杂的时候,建议这么使用,这样代码会比较的清晰
// 导出路由模块
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const appRoutes: Routes = [...]; //
@NgModule({
imports: [
RouterModule.forRoot(appRoutes)
],
exports: [
RouterModule
]
})
export class AppRoutingModule {}
// 导入路由模块
....
@NgModule({
import: [
AppRoutingModule
]
})
b. forRoot
与forChild
的区别(即如何在子模块中单独配置路由文件)
教程中说明: 只在根模块 AppRoutingModule
中调用 RouterModule.forRoot
(如果在AppModule
中注册应用的顶级路由,那就在 AppModule
中调用)。 在其它模块中,你就必须调用RouterModule.forChild
方法来注册附属路由。
个人理解:即根模块中使用forRoot()
,子模块中使用forChild()
,
在 AppRoutingModule
注册Router
服务,即可在应用的任何地方都能使用路由服务
注意
:在根模块appModule
中import
数组中,路由规则以后面引入的规则为准,所以需要注意引入根模块路由和特性模块的先后顺序。
c. enableTracing
参数
添加打印信息,让路由器将其所有的内部事件都记录到控制台中
@NgModule({
imports: [
RouterModule.forRoot(
appRoutes,
{ enableTracing: true } // <-- debugging purposes only
)
],
6. 获取路由参数
6.1 ActivatedRoute
服务接收路由参数
方法如下:
// 从路由器router包中导入 Router、ActivatedRoute 和 Params类
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap } from 'rxjs/operators';
// 把组件所需的服务注入进来,自动定义同名的私有变量,并把它们存进去
constructor(
private route: ActivatedRoute,
private router: Router,
private service: HeroService
) {}
//用 ActivatedRoute服务来接收路由的参数,从参数中取得该英雄的 id,并接收此英雄用于显示
ngOnInit() {
this.hero$ = this.route.paramMap.pipe(
switchMap((params: ParamMap) =>
this.service.getHero(params.get('id')))
);
}
(switchMap
这个的话,之后服务模块中再学习吧)
ParamMap API
成员 | 说明 |
---|---|
has(name) | name这个参数名是否在参数列表中 |
get(name) | name这个参数名对应的参数值,没有就返回null |
getAll(name) | name这个参数名对应的值,这个返回的是数组,否则返回空数组 |
keys | 所有参数名组成的字符串数组 |
备注:
a. children
只会出现当前路由下已激活的子路由,而不是所有的子路由
6.2 Snapshot
(快照)
使用方法:
ngOnInit() {
let id = this.route.snapshot.paramMap.get('id');
this.hero$ = this.service.getHero(id);
}
6.3 两者的区别
ActivatedRoute
:接收的是Observable
对象,表示在该组件的生存期间,只要路由发生变化了,组件也会发生对应的变化;例如上文中的例子,当路由中的id发生变化时,页面会立马渲染对应id的hero;
Snapshot
:例如上文中的例子,在同一个组件页面中时,当id发生变化的时候,不会影响页面的显示;显示哪个hero,仅由初次进入该component
时的id
所决定
7. 路由守卫
当跳转到某个url
地址时,先判断下是否具有权限或者其他要求,不然不能跳转,这个时候就用到了路由守卫。
守卫接口 | 含义 |
---|---|
CanActivate | 处理导航到某路由的情况 |
CanActivateChild | 处理导航到某子路由的情况 |
CanDeactivate | 处理当前路由离开的情况 |
Resolve | 路由激活前获取路由数据 |
CanLoad | 处理异步到某特性模块的情况 |
a. CanActivate、CanActiveteChild
使用方法:
a.1 定义守卫
判断有权限进入该路由(CanActivate)/子路由(CanActiveteChild)
参数
ActivatedRouteSnapshot
:代表即将被激活的路由
RouterStateSnapshot
:代表即将路由的状态(RouterStateSnapshot.url保存着这个守卫通过的话,将要进入的地址)
// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
// 此处用函数判断是否进行跳转
// return true; 表示可以跳转
// return false; 表示不能跳转
}
}
a.2 添加到路由文件中
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
children: [
{
path: '',
canActivateChild: [AuthGuard],
children: [...]
}
]
}
];
b. CanDeactivate
表示是否可离开当前的路由
b.1 创建路由
ng generate guard can-deactivate
b.2 定义守卫
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { CanDeactivate,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '@angular/router';
import { CrisisDetailComponent } from './crisis-center/crisis-detail/crisis-detail.component';
@Injectable({
providedIn: 'root',
})
export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent> {
canDeactivate(
component: CrisisDetailComponent,
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | boolean {
if(...){
return true;
}else{
return false;
}
}
}
b.3 添加守卫
{
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard]
}
c. Resolve 预先获取组件数据
在路由渲染前,获取数据
c.1 新增一个获取数据的服务
如果获取到数据,则将数据返回,显示对应的路由
不然则跳转到其他路由
import { Injectable } from '@angular/core';
import {
Router, Resolve,
RouterStateSnapshot,
ActivatedRouteSnapshot
} from '@angular/router';
import { Observable, of, EMPTY } from 'rxjs';
import { mergeMap, take } from 'rxjs/operators';
import { CrisisService } from './crisis.service';
import { Crisis } from './crisis';
@Injectable({
providedIn: 'root',
})
export class CrisisDetailResolverService implements Resolve<Crisis> {
constructor(private cs: CrisisService, private router: Router) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Crisis> | Observable<never> {
let id = route.paramMap.get('id');
return this.cs.getCrisis(id).pipe(
take(1),
mergeMap(crisis => {
if (crisis) {
return of(crisis);
} else { // id not found
this.router.navigate(['/crisis-center']);
return EMPTY;
}
})
);
}
}
c.2 将resolve增加到路由中
{
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard],
resolve: {
crisis: CrisisDetailResolverService
}
}
c.3 组件从ActivatedRoute.data中获取数据
this.route.data
.subscribe((data: { crisis: Crisis }) => {
this.editName = data.crisis.name;
this.crisis = data.crisis;
});
}
d. 查询路由参数(NavigationExtras
对象)
即会在url中添加?及相关的查询参数
d.1 在跳转路由中添加
const navigationExtras: NavigationExtras = {
queryParams: { 'session_id': sessionId},
fragment: 'anchor'
};
this.router.navigate(['/login'], navigationExtras);
d.2 上一条路由对应跳转的路由,如果再次跳转的时候,还想带查询参数的话,只需要按下列方式添加即可,跳转的下一个路由,还会带上相同的查询参数
// login对应的路由文件
let navigationExtras: NavigationExtras = {
queryParamsHandling: 'preserve',
preserveFragment: true
};
this.router.navigate([redirect], navigationExtras);
d.3 在跳转组件中获取对应参数
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { PARAMETERS } from '@angular/core/src/util/decorators';
@Component({
selector: 'app-admin-dashboard',
templateUrl: './admin-dashboard.component.html',
styleUrls: ['./admin-dashboard.component.css']
})
export class AdminDashboardComponent implements OnInit {
sessionId: Observable<string>;
token: Observable<string>;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.sessionId = this.route
.queryParamMap
.pipe(map(params => params.get('session_id') || 'None'));
this.token = this.route
.fragment
.pipe(map(fragment => fragment || 'None'));
}
}
8. 异步路由
8.1 惰性加载
惰性加载目的,是当前端程序特别大的时候,只加载主要的模块,部分不常用的模块,到需要加载的时候再实现加载
路由文件添加loadChildren
,
且在根模块中从 NgModule 的 imports
数组中移除
{
path: 'admin',
loadChildren: './admin/admin.module#AdminModule',
},
可以在请求中看到,只有点击了admin模块时,才会加载admin模块
8.2 CanLoad守卫
如果使用了CanActivate
来保护路由,如果验证不通过,不会跳转到对应的路由,但是依旧会加载该模块,此时需要使用CanLoad
组件来进行懒加载
方法如下:
a. 在守卫中添加canload
canLoad(route: Route): boolean {
let url = `/${route.path}`;
return this.checkLogin(url);
}
b. 将CanLoad
添加到对应的路由中
{
path: 'admin',
loadChildren: './admin/admin.module#AdminModule',
canLoad: [AuthGuard]
},
8.3 预加载
异步延迟加载,即不会阻塞页面的显示(不是立即加载)
当用户切换的时候,也不会像惰性加载一样,需要重新进行加载
方法如下:
在路由模块中导入PreloadAllModules
模块(预加载所有模块)
RouterModule.forRoot(
appRoutes,
{
enableTracing: true, // <-- debugging purposes only
preloadingStrategy: PreloadAllModules
}
)
注:CanLoad依旧会阻塞预加载,因为CanLoad守卫优先级高于预加载策略
8.4 自定义预加载策略
当不需要加载所有模块的功能的时候,就不能添加PreloadAllModules
,此时需要自定义加载功能
方法如下:
a. 添加新的预加载服务
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class SelectivePreloadingStrategyService implements PreloadingStrategy {
preloadedModules: string[] = [];
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data && route.data['preload']) {
// add the route path to the preloaded module array
this.preloadedModules.push(route.path);
// log the route path to the console
console.log('Preloaded: ' + route.path);
return load();
} else {
return of(null);
}
}
}
注: SelectivePreloadingStrategyService 会把所选路由的 path 记录在它的公共数组 preloadedModules 中
b. 在路由中增加对应的标志位
{
path: 'crisis-center',
loadChildren: './crisis-center/crisis-center.module#CrisisCenterModule',
data: { preload: true }
}
c. 将该服务注入到forRoot
中
RouterModule.forRoot(
appRoutes,
{
enableTracing: false,
preloadingStrategy: SelectivePreloadingStrategyService
}
)
9. 查看最终的路由配置
export class AppModule {
// Diagnostic only: inspect router configuration
constructor(router: Router) {
// Use a custom replacer to display function names in the route configs
const replacer = (key, value) => (typeof value === 'function') ? value.name : value;
console.log('Routes: ', JSON.stringify(router.config, replacer, 2));
}
}
在F12下面可以看到最终的路由文件
10. 附录
路由切换动画,等到更新动画这一章的时候再叙;或者可以直接跟着官网来一遍
-
官网相关附录
链接参数数组
如何在<a>
标签下添加路由参数关于LocationStrategy 以及浏览器 URL 样式
解释了hash URL及H5 pushState URL,以及服务端渲染的应用 区别及选择