①.路由参数
paramMap和Snapshot
当组件需要复用的时候使用paramMap获取路由参数:如一个组件不刷新,只更改了路由参数,那么就可以实时获取路由参数
当确定组件不复用的时候直接使用Snapshot获取路由参数,每一次打开这个组件都是一个新的实例
paramMap:paramMap
的处理过程有点稍复杂。当这个 map 的值变化时,你可以从变化之后的参数中 get()
到其 id
参数。
然后,让 HeroService
去获取一个具有此 id
的英雄,并返回这个 HeroService
请求的结果。
你可能想使用 RxJS 的 map
操作符。 但 HeroService
返回的是一个 Observable<Hero>
。 所以你要改用 switchMap
操作符来打平这个 Observable
。switchMap
操作符还会取消以前未完成的在途请求。如果用户使用新的 id
再次导航到该路由,而 HeroService
仍在接受老 id
对应的英雄,那么 switchMap
就会抛弃老的请求,并返回这个新 id
的英雄信息。
ngOnInit() {
this.hero$ = this.route.paramMap.pipe(
switchMap((params: ParamMap) =>
this.service.getHero(params.get('id')))
);
}
成员 | 说明 |
---|---|
| 如果参数名位于参数列表中,就返回 |
| 如果这个 map 中有参数名对应的参数值(字符串),就返回它,否则返回 |
| 如果这个 map 中有参数名对应的值,就返回一个字符串数组,否则返回空数组。当一个参数名可能对应多个值的时候,请使用 |
返回这个 map 中的所有参数名组成的字符串数组。 |
Snapshot:route.snapshot
提供了路由参数的初始值。 你可以通过它来直接访问参数,而不用订阅或者添加 Observable 的操作符。 这样在读写时就会更简单:
ngOnInit() {
let id = this.route.snapshot.paramMap.get('id');
this.hero$ = this.service.getHero(id);
}
②.第二路由:
在app-routing.module.ts中添加如下代码
{
path: 'compose',
component: ComposeMessageComponent,
outlet: 'popup'
},
在app.component.html中添加如下代码:
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a><br/>
<router-outlet name="popup"></router-outlet>
运行后,在页面上点击Contact按钮,会出现ComposeMessageComponent的内容.
这时候浏览器地址栏后面会出现http://localhost:4200/xx/xx(popup:compose),如果此时切换主路由,第二路由是不会发生变化的
路由器在导航树中对两个独立的分支保持追踪,并在 URL 中对这棵树进行表达。
清除第二路由
this.router.navigate([{ outlets: { popup: null }}]);
③.路由守卫
1.CanActivate: 要求认证
应用程序通常会根据访问者来决定是否授予某个特性区的访问权。 你可以只对已认证过的用户或具有特定角色的用户授予访问权,还可以阻止或限制用户访问权,直到用户账户激活为止。CanActivate
守卫是一个管理这些导航类业务规则的工具。
使用命令在app/auth文件夹下创建一个守卫文件 ng generate guard auth/auth
src/app/auth/auth.guard.ts中canActivate()方法解读:
canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.checkLogin(url); // 自己定义的方法.
}
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }
this.authService.redirectUrl = url;
this.router.navigate(['/login']);
return false;
}
// this.checkLogin(url)返回的结果只能是true或者false,
// 当checkLogin(url)返回为true的时候就表示守卫通过,可以访问被守卫的路由
// 当checkLogin(url)返回为false的时候就表示守卫通不过,不可以访问被守卫的路由
被守卫的路由需要使用 canActivate: [AuthGuard],
如果canActivate()返回结果为true,则可以访问admin下面的子路由,否组不可以
import { AuthGuard } from '../auth/auth.guard';
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
children: [
...
]
}
];
...
2.CanActivateChild:保护子路由
你还可以使用 CanActivateChild
守卫来保护子路由。 CanActivateChild
守卫和 CanActivate
守卫很像。 它们的区别在于,CanActivateChild
会在任何子路由被激活之前运行。
src/app/auth/auth.guard.ts中添加canActivateChild()方法
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
return this.canActivate(route, state);
}
把这个 AuthGuard
添加到“无组件的”管理路由,来同时保护它的所有子路由,而不是为每个路由单独添加这个 AuthGuard
。
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
children: [
{
path: '',
canActivateChild: [AuthGuard], // CanActivateChild 会在任何子路由被激活之前运行
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
]
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(adminRoutes)
],
exports: [
RouterModule
]
})
export class AdminRoutingModule {}
3.CanDeactivate:处理未保存的更改(离开路由时,如果有未保存的信息)
命令行新建一个guard 文件 ng generate guard can-deactivate;
src/app/can-deactivate.guard.ts代码如下:
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { Observable } from 'rxjs';
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
@Injectable({
providedIn: 'root',
})
export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(component: CanComponentDeactivate) {
return component.canDeactivate ? component.canDeactivate() : true;
}
}
上面的代码中:
1.component.canDeactivate:canDeactivate()为使用的那个组件中的方法 ,返回的结果也只能是true或者false
2.如果有canDeactivate()方法,就使用canDeactivate.如果没有就返回true,true则是正常离开路由,否则运行对应的程序
路由中使用:
children: [
{
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard]
}
]
component中使用:
canDeactivate(): Observable<boolean> | boolean {
if (!this.crisis || this.crisis.name === this.editName) {
return true;
}
return this.dialogService.confirm('Discard changes?');
}
component的代码中:
1.canDeactivate为自己定义的方法,can-deactivate.guard.ts中会用到component.canDeactivate就是此方法
那么离开路由的时候,如何知道当前页面中哪些数据变化了,这时候就要用到Resolve了
4.Resolve: 预先获取组件数据 参考:Resolve
新建service 文件 ng generate service crisis-center/crisis-detail-resolver
crisis-detail-resolver.ts代码如下:
...
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');
//this.cs.getCrisis(id):自定义的方法,根据id获取当前数据.返回的数据在component中可以通过ActivatedRoute.data获取
return this.cs.getCrisis(id).pipe(
take(1), // 只取一次数据
mergeMap(crisis => { // 同时合并多个流
if (crisis) {
return of(crisis);
} else { // id 没找到的话,直接跳转到/crisis-center
this.router.navigate(['/crisis-center']);
return EMPTY;
}
})
);
}
}
路由中使用:
import { CrisisDetailResolverService } from './crisis-detail-resolver.service';
...
children: [
{
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard],
resolve: {
crisis: CrisisDetailResolverService
}
},
...
]
component中使用:
constructor( private route: ActivatedRoute, private router: Router, public dialogService: DialogService ) {}
ngOnInit() {
this.route.data
.subscribe((data: { crisis: Crisis }) => {
this.editName = data.crisis.name;
this.crisis = data.crisis;
});
}
// this.route.data中就是crisis-detail-resolver.ts中resolve()的返回值
5.查询参数及片段 参考:查询参数及片段
如何定义一些所有路由中都可用的可选参数呢? 这就该“查询参数”登场了。
src/app/auth/auth.guard.ts中设置
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }
this.authService.redirectUrl = url;
let sessionId = 123456789;
// 设置
let navigationExtras: NavigationExtras = {
queryParams: { 'session_id': sessionId },
fragment: 'anchor'
};
this.router.navigate(['/login'], navigationExtras);
return false;
}
component中使用:
this.route
.queryParamMap
.pipe(map(params => params.get('session_id') || 'None'));
④:异步路由
在继续构建特征区的过程中,应用的尺寸将会变得更大。在某一个时间点,将达到一个顶点,应用将会需要过多的时间来加载。如何才能解决这个问题呢?通过引进异步路由,可以获得在请求时才惰性加载特性模块的能力。 惰性加载有多个优点:
· 你可以只在用户请求时才加载某些特性区。
· 对于那些只访问应用程序某些区域的用户,这样能加快加载速度。
· 你可以持续扩充惰性加载特性区的功能,而不用增加初始加载的包体积。
1.惰性加载路由配置Router
支持空路径路由,可以使用它们来分组路由,而不用往 URL 中添加额外的路径片段
使用 loadChildren
属性替换掉 children
属性。 loadChildren
属性接收一个函数,该函数使用浏览器内置的动态导入语法 import('...')
来惰性加载代码,并返回一个承诺(Promise)
admin-routing.module.ts是一个子路由,正常如下,并且在app.module.ts中imports: [ AdminModule,AppRoutingModule],
...
const adminRoutes: Routes = [{
path: 'admin',
component: AdminComponent,
children: [{ path: 'crises', component: ManageCrisesComponent },...]
}];
@NgModule({
imports: [RouterModule.forChild(adminRoutes)],
exports: [RouterModule]
})
export class AdminRoutingModule { }
app.routing-module.ts如下
const appRoutes: Routes = [
{ path: '', redirectTo: '/heros', pathMatch: 'full' }, // 定向重路由
{ path: '**', component: PageNotFoundComponent },
];
@NgModule({
imports: [RouterModule.forRoot(appRoutes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
改造:
1.把 admin-routing.module.ts
中的 admin
路径从 'admin'
改为空路径 ''
。
2.app-routing.module.ts中的appRoutes下添加如下代码
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(mod => mod.AdminModule),
},
3.预加载的话,要在在app.module.ts中@NgModule=>imports数组中注释掉 AdminModule,
将路由添加一个CanLoad
守卫,它只在用户已登录并且尝试访问管理特性区的时候,才加载 AdminModule
一次。
src/app/auth/auth.guard.ts添加如下代码,this.checkLogin(url)方法在③-1和③-5中
canLoad(route: Route): boolean {
let url = `/${route.path}`;
return this.checkLogin(url);
}
app-routing.module.ts (lazy admin route)
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(mod => mod.AdminModule),
canLoad: [AuthGuard]
},
⑤预加载:特性区的后台加载 - 异步加载模块
AppModule
在应用启动时就被加载了,它是立即加载的。 而 AdminModule(loadChildren)子路由
只有当用户点击某个链接时才会加载,它是惰性加载的。预加载是介于两者之间的一种方式
为了获得尽可能小的初始加载体积和最快的加载速度,应该对 第一视图(AppModule
和 默认显示的路由
) 进行急性加载
1.什么是这就是预加载?
比如现在有CrisisCenterModule和HerosModule模块,用户默认显示HerosModule模块 ,几乎可以肯定用户会在启动应用之后的几分钟内访问CrisisCenterModule,理想情况下,应用启动时应该只加载 AppModule
和 HeroesModule
,然后几乎立即开始后台加载 CrisisCenterModule
。 在用户浏览到CrisisCenterModule之前,该模块应该已经加载完毕,可供访问了。
2.预加载的工作原理
在每次成功的导航后,路由器会在自己的配置中查找尚未加载并且可以预加载的模块。 是否加载某个模块,以及要加载哪些模块,取决于预加载策略。
3.Router内置了两种预加载策略:
3.1.完全不预加载,这是默认值。惰性加载的特性区仍然会按需加载。
3.2.预加载所有惰性加载的特性区。
4.惰性加载AdminModule步骤,同:④ - 1 (惰性加载路由配置)
把 AdminModule 中的路径从 'adimin'
改为空字符串。 同④ 1.1
往 AppRoutingModule
中添加一个 admin
路由。 同④ 1.2
设置 loadChildren
字符串来加载 AdminModule。 同④ 1.2
从 app.module.ts
中移除所有对 AdminModule 的引用。 同④ 1.3
要为所有惰性加载模块启用预加载功能,请从 Angular 的路由模块中导入 PreloadAllModules
。
src/app/app-routing.module.ts
RouterModule.forRoot(
appRoutes,
{ preloadingStrategy: PreloadAllModules}
)
5.自定义预加载策略
命令行新建文件ng generate service selective-preloading-strategy
selective-preloading-strategy.ts代码如下:
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']) {
this.preloadedModules.push(route.path);
console.log('Preloaded: ' + route.path);
return load();
} else {
return of(null);
}
}
}
路由更改添加data: { preload: true }:
{
path: 'crisis-center',
loadChildren: () => import('./crisis-center/crisis-center.module').then(mod => mod.CrisisCenterModule),
data: { preload: true }
},
component.ts中获取:
constructor(preloadStrategy: SelectivePreloadingStrategyService) {
this.modules = preloadStrategy.preloadedModules; // modules:string[]
}
最后在路由中preloadingStrategy
@NgModule({
imports: [
RouterModule.forRoot(
appRoutes,
{preloadingStrategy: SelectivePreloadingStrategyService,}
)
],
...
})
这时候访问heros页面的时候,CrisisCenterModule
也被预加载了,可以在控制台看下输出
⑥使用重定向迁移 URL
// 原始路由
const heroesRoutes: Routes = [
{ path: 'heroes', component: HeroListComponent, data: { animation: 'heroes' } },
{ path: 'hero/:id', component: HeroDetailComponent, data: { animation: 'hero' } }
];
// 使用重定向迁移 URL
const heroesRoutes: Routes = [
{ 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' } }
];
总结:
添加路由出口:<router-outlet></router-outlet>
定义通配符路由:{ path: '**', component: PageNotFoundComponent }
默认路由redirect: { path: '', redirectTo: '/heroes', pathMatch: 'full' },
paramMap和Snapshot:获取路由参数,paramMap实时获取,Snapshot获取一次
CanActivate:路由守卫
CanActivateChild:保护子路由,会在任何子路由被激活之前运行
CanDeactivate:处理未保存的更改
Resolve: 预先获取组件数据
loadChildren:惰性加载路由配置
CanLoad守卫:保护对特性模块的未授权加载
定义路由,注册路由:
// 定义路由
const appRoutes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'heroes', component: HeroListComponent },
];
// 注册路由:
@NgModule({
imports: [
...
RouterModule.forRoot(appRoutes)
],
...
})
父路由,子路由
// 子路由AdminRoutingModule
...
const adminRoutes: Routes = [
{
path: '',
component: AdminComponent,
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
]
}
];
@NgModule({
imports: [RouterModule.forChild(adminRoutes)],
exports: [RouterModule]
})
export class AdminRoutingModule { }
// 父路由,AppRoutingModule
...
const appRoutes: Routes = [
{ path: 'crisis-center', component: CrisisListComponent },
{ path: 'heroes', component: HeroListComponent },
];
@NgModule({
imports: [
...
RouterModule.forRoot(appRoutes)
],
...
})
//app.module.ts
@NgModule({
imports: [AdminModule,AppRoutingModule,...],
...
})