Angular路由

背景

Angular相关内容一个文件写不下,分开在多个文档里可能更好一些.

路由的引入

基础的实现只需要在模块的imports数据中使用.

1
2
3
4
5
6
7
8
9
10
11
@NgModule({
// ...
imports: [
// ...
// 可以直接在一个模块中定义路由表
RouterModule.forRoot([
{path: '', component: FirstComponent},
])
]
})
export class AppModule { }

不过通常会将路由的定义独立到外部的一个模块中

1
2
3
4
5
6
7
8
9
10
11
12
import { Routes, RouterModule } from '@angular/router';

// 所有路由表放在了一个数组中
const routes: Routes = [
{path:'', component: FirstComponent},
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

然后在主要的模块中引用

1
2
3
4
5
6
7
8
@NgModule({
// ...
imports: [
// 从主要模块引用定义了路由的模块
AppRoutingModule,
],
})
export class AppModule { }

此处的 AppRoutingModule 本身是一个专注与提供路由功能的 模块.
只不过没有组件,且直接放在另一个模块的文件夹下而已

注意由于 app.module 是根模块,所以使用 forRoot 的写法,
之后会遇到功能拆分后独立出来的feature模块,他们的路由就需要使用 forChild 了,
这种规定似乎让angular能够按照特定顺序将路由组合在一起.

各种各样的路由

路由本身有多种情况

  1. 普通路由
  2. 子路由
  3. 带id的路由
  4. 默认路由,通常用来重定向到首页
  5. 通配符路由(通常用来显示404页面)

Angular的路由遵循先到显得的顺序,因此为了防止覆盖,通常在书写顺序上,
先写普通路由,然后是路径为空的默认路由,最后是通配符路由.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const routes: Routes = [
// 普通路由
{ path: 'first-component', component: SecondComponent },
{
path: 'heroes',
children: [
// 各种子路由
{ path: '', component: HeroListComponent },
{ path: 'search', component: SearchResultComponent },
{ path: ':id', component: CourseDetailComponent } // 使用id访问的路由, 比如访问 /heros/2
]
},
// 默认路由,通常会搭配重定向来决定一个默认的页面,pathMatch要求完全匹配时才跳转
{ path: '', redirectTo: '/first-component', pathMatch: 'full' },
// 通配符路由
{ path: '**', component: PageNotFoundComponent },
];

访问特定地址

  1. 在浏览器地址栏中直接输入

  2. html文件中使用 routerlink,使用数组则代表拼接

    1
    2
    3
    4
    <a routerLink="./crises" routerLinkActive="active">简单使用</a>
    <!-- routerLinkActive用于指定一个该路由被激活时使用的css,active好像是内置css -->
    <a [routerLink]="['/hero', hero.id]">拼接id,相当于/hero/id</a>
    <a [routerLink]="['/crisis-center', { foo: 'foo' }]">使用可选参数</a>
  3. ts文件中使用navigate,路径格式类似routerLink

    1
    2
    3
    4
    gotoItems(hero: Hero) {
    const heroId = hero ? hero.id : null;
    this.router.navigate(['/heroes', { id: heroId }]);
    }
  4. 使用 router.parseUrl 让路由器自己去导航

    1
    2
    3
    canActivate(next: some, route: some) :boolean|urlTree {
    return router.parseUrl()
    }

路由信息的解析

常见web应用中,通常会解析访问时的一些

  • 必选参数(比如id)
  • 可选参数
  • 查询参数(问号后面的)
  • 数据

在Angular中可以使用 ActivatedRoute 来获取这些信息,
且获取到的是一个Observable.
对应上方

  • 必选参数和可选参数放在 paramMap 中(params 是旧版用法)
  • 查询参数在 queryParamMap 中( queryParams 是旧版用法)
  • 数据放在 data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Router, ActivatedRoute, ParamMap } from '@angular/router';

constructor(
private route: ActivatedRoute,
) {}

ngOnInit() {
// rxjs友好
this.route.queryParamMap.subscribe(params => {
// 内容物是一个类似字典的结构,可以用字符下标获取
// this.name = params.get['name'];

// 不过现在都改用api以防止下标不存在的报错
// 不存在则返回null
this.name = params.get('name');
});
}

如果不希望得到一个Observable的值,
那么可以使用 route.snapshot.paramMap.get('id) 的方式来直接获得Obesrvable中包裹的值.
同时, route.snapshot 得到的数据类型为 ActivatedRouteSnapshot

之后会遇到另外的两个类

  • RouterStateSnapshot 用在路由守卫中,仅仅有一个成员 state.url 表示用户来自的url
  • Route 路由配置文件中的信息被放在了这个类中,不过这个类一般不怎么使用

路由的参数

按是否可观察分

  • observable的,使用 this.route.paramMap 等获得.
  • 非observable的,使用 this.route.snapshot.paramMap 直接获得内容.

通常选observable的,这样可以只是更新数据而不需要重新构造组件,性能好.

按照参数用途分

  • 必选参数(就像上面的id)
  • 可选参数(可有可无)

可选参数的一个场景是:
从详情页面返回列表页面,可以选择性地带上该详情页面的id,并在列表页面中突出显示刚才点击的那个入口.

使用举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 详情页面
gotoCrises() {
const crisisId = this.crisis ? this.crisis.id : null;
// 使用一个对象来传递可选参数
// 如果使用相对路径,需要另外指定相对于谁
this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });
}

// 列表页面
ngOnInit() {
this.crises$ = this.route.paramMap.pipe( // 依然可以从paramMap获取参数.
switchMap(params => {
// 取出了可能会有的参数
// 加号表示将字符串转数字,因为url里的东西都被视为字符串
this.selectedId = +params.get('id');
return this.service.getCrises();
})
);
}

另外可选参数在地址栏中的显示,使用的是 矩阵url 标记法

1
https://path/to/component;id=15;foo=foo

路由出口与第二路由

路由出口

路由出口其实是父级路由对应的组件的模板中,为子路由预留的占位符.
假如有以下结构的路由

1
2
3
4
- crisis-center
- crisis-list
- crisis-detail
- crisis-center-home

一般情况下可能直接用list来做center的页面,不过这里多套了一层也没关系.
在list页面中,如果点击了某个条目则在下方显示detail,
如果没有点击,则在下方显示默认的welcome信息(center-home组件里面).
每个组件对应的模板为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- crisis-center -->
<h2>CRISIS CENTER</h2>
<router-outlet></router-outlet> <!-- 用于嵌套crisis-list -->

<!-- crisis-list -->
<ul class="crises">
<li *ngFor="let crisis of crises$ | async"
[class.selected]="crisis.id === selectedId">
<a [routerLink]="[crisis.id]">
<span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
</a>
</li>
</ul>

<router-outlet></router-outlet> <!-- 用于嵌套crisis-detail或者crisis-center-home,根据路由决定 -->

<!-- crisis-detail -->
<div *ngIf="crisis">
<h3>"{{ editName }}"</h3>
<div>
<label>Id: </label>{{ crisis.id }}</div>
<div>
<label>Name: </label>
<input [(ngModel)]="editName" placeholder="name"/>
</div>
<p>
<button (click)="save()">Save</button>
<button (click)="cancel()">Cancel</button>
</p>
</div>

<!-- crisis-center-home -->
<p>Welcome to the Crisis Center</p>

这样,路由嵌套起来的页面就是

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- crisis-center -->
<h2>CRISIS CENTER</h2>
<!-- crisis-list -->
<ul class="crises">
<li *ngFor="let crisis of crises$ | async"
[class.selected]="crisis.id === selectedId">
<a [routerLink]="[crisis.id]">
<span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
</a>
</li>
</ul>
<!-- crisis-center-home -->
<p>Welcome to the Crisis Center</p>

需要注意的是如果父路由对应的组件中没有outlet,那么子组件就无法显示出来,
除非父路由不使用组件,那么子路由对应的组件就会去找上级outlet.

第二路由

一个组件模板不仅仅可以有一个outlet,也可以有多个outlet.
比如想在页面上划分一个区域,这个区域显示的内容在用户切换路径时都不变.
一种方法是另外建立一套路由树并将页面划分出一部分用于显示这个路由树.
angular的想法正好是划分出一个outlet,并让该outlet响应单独的一套路由树.
不过为了管理方便,只允许有一个未命名的outlet作为默认,其他outlet都需要命名.

1
2
3
4
<div [@routeAnimation]="getAnimationData(routerOutlet)">
<router-outlet #routerOutlet="outlet"></router-outlet> <!-- 一个没有name的outlet -->
</div>
<router-outlet name="popup"></router-outlet> <!-- 一个有name的outlet -->

如果一个路由需要使用名为popup的出口,则需要指定outlet的名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 路由中指定
{
path: 'compose',
component: ComposeMessageComponent,
outlet: 'popup'
}

// 模板中指定
// outlets是字段
// popup是出口名称
// compose 是要导航到的path
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>

// 组件类中指定,类似模板中的指定
closePopup() {
// 若将名为popup的出口导航到了null,该出口的内容会消失
this.router.navigate([{ outlets: { popup: null }}]);
}

而这无疑给浏览器出了个难题,不过如今浏览器的显示可能是

1
https://simple/path(outlet-name: second-route-tree-path)

路由守卫

Angular提供了多种路由守卫,在特定场景下拒绝对特定路由的访问,具体实现方法见常见用例

  • CanActivate (没有登陆不能访问)
  • CanActivateChild (没有权限不能访问子页面,只能看个首页)
  • CanDeactivate (页面数据没有保存不能离开)
  • Resolve (为了用户体验,需要先见准备数据再进入页面而不是先进入页面再准备数据)
  • CanLoad (满足条件才能开始加载一个模块,比如管理员登陆不成功就永远不加载管理相关模块,保证性能,
    严格来说不仅仅用来拒绝对路由的访问了)

其实我看来守卫是路由定义时的字段,程序员来实现的,其实是用来答守卫话的代理人.
而如何答话,angular定义好了 接口,接口里面有相应的函数.

守卫的基本行为逻辑决定了其返回值的类型.

  • 返回true表示可以继续导航
  • 返回false表示导航终止,留在原地
  • 返回UrlTree则表示导航终止,同时导航到该UrlTree.
    不过个人认为守卫返回false表意才更明确.
    至于想重新导航,完全可以在函数体中发起一个新的导航. this.router.navigate()
  • 有时候守卫不能立即获得答案(比如向用户提出警告并要求确认),需要返回能够异步处理的类型.
    以允许表面有阻塞的对话框的同时,背地里却依然可以做耗时处理.珍惜一分一秒挖个矿什么的.
    这些可观察对象还必须是可结束的.
    • Obesrevable<boolean>
    • Promise<boolean>

时常会遇到多层守卫的情况,此时就像从公司请假回家过年.
先从基层问起,canDeactivate?出了公司后,依次从大地方过安检去小地方.每次都问canActivate?

CanActivate

使用场景举例:

  1. 没有登陆不能访问
  2. 不是管理者不能访问

首先定义方法本身

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export class AuthGuard implements CanActivate { // 需要实现CanActivate接口里的canActivate方法
constructor(
// 和登陆有关的东西,委托AuthService来做
private auth: AuthService
){}

canActivate(
next: ActivatedRouteSnapshot, // 预计要导航到的路由
state: RouterStateSnapshot // 目前的路由状态,仅有state.url表示用户来自的url
): boolean {

if (next.queryParamMap.has("token")) {
// 通常会利用路由中的一些信息做点什么
// 比如如果已经有了token就直接返回true
}

return this.auth.isLogin();
}
}

然后就能在路由定义时使用了

1
2
3
4
5
6
7
8
9
const adminRoutes: Routes = [
{
path: 'admin',
canActivate: [AuthGuard],
children: [
// ...
]
}
];

CanActivateChild

注意到其实CanActivate只能保护 /hero 而不能连带 /hero/1 一起保护.
因此需要canActivateChild.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export class AuthGuard implements CanActivate, CanActivateChild { // 1. 继承接口
constructor(private authService: AuthService, private router: Router) {}

canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): true|UrlTree {
const url: string = state.url;

return this.checkLogin(url);
}

canActivateChild( // 2. 实现方法
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): true|UrlTree { // 换行后比较难看但应该习惯这种写法
return this.canActivate(route, state); // 3. 通常子路由的保护和父路由的保护是一样的
}

checkLogin(url: string): true|UrlTree {
// ...
}
}

在路由中可以这样使用

1
2
3
4
5
6
7
8
9
10
11
{
path: '',
component: AdminComponent,
canActivate: [AuthGuard],
canActivateChild: [AuthGuard]
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
]
}

附带一个可能有用的checkLogin函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
checkLogin(url: string): true|UrlTree {
// 1. 如果已经登陆则继续导航
if (this.authService.isLoggedIn) { return true; }

// 2. 如果还没有登陆就要做一些准备了

// 2.1 通常会暂存用户想要到的页面
this.authService.redirectUrl = url;

// 2.2(版本1) 然后就重定向到login页面
// 注意该函数的返回值正好是一个UrlTree类型
return this.router.parseUrl('/login');

// 2.2(版本2) 带上一些参数再重定向
const navigationExtras: NavigationExtras = {
queryParams: { session_id: sessionId },
fragment: 'anchor'
};
return this.router.createUrlTree(['/login'], navigationExtras);
}

CanDeactivate

使用场景举例:

  1. 用户修改了form内容,想要离开

与上面两个守卫不同的是,canDeactivate函数必须再指定一个component的引用.
(如果用来做页面上form内容的校验,则很可能要用到组件本身)

1
2
3
4
5
canDeactivate(
component: XXXComponent, // 组件的引用
route: ActivatedRouteSnapshot, // 其他引用1
state: RouterStateSnapshot // 其他引用2
): Observable<boolean> | boolean { }

守卫本身的实现方式上有不同的做法

  1. 为每个组件都定义一个专用的canDeactivate守卫,守卫亲历亲为验证组件的属性.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent> {

    canDeactivate(
    component: CrisisDetailComponent,
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
    ): Observable<boolean> | boolean {

    // 引用compnent来检查内容,如果没有变就返回true
    if (!component.crisis || component.crisis.name === component.editName) {
    return true;
    }
    // 如果变了就提问用户,该方法返回observable
    return component.dialogService.confirm('Discard changes?');
    }
    }
  2. 每个组件自己写好校验方法.然后写一个通用的守卫,只是调用一下组件内的方法.

    1
    2
    3
    4
    // 假设所有组件都实现了该接口
    export interface CanComponentDeactivate {
    canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
    }
    1
    2
    3
    4
    5
    6
    export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
    canDeactivate(component: CanComponentDeactivate) { // 可能参数个数并不是固定死的?TODO
    // 如果该组件有canDeactive方法则执行,否则直接返回true
    return component.canDeactivate ? component.canDeactivate() : true;
    }
    }

在路由中使用起来很简单 canDeactivate: [CanDeactivateGuard]

Resolve

用户从列表页面进入详情页面后,等了一会儿, ngOnInit 才调用service把数据加载成功.过程中看到的是白页面.
使用上体验不如,用户进入页面前先在一个 工具函数 xxx 中调用service准备了数据,
然后带着数据导航到详情页面(当然如果找不到数据,就需要终止路由).

因此 工具函数 的功能有:

  1. 能够获取数据(需要注入获取数据的service)
  2. 能够控制路由接下来要如何做.(需要注入router)
  3. 能够返回一个特定类型的数据(为了返回值能够有类型,定义时就需要类型提示)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export class CrisisDetailResolverService implements Resolve<Crisis> {

constructor(private cs: CrisisService, private router: Router) {}

resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<Crisis> | Observable<never> {

// 1. 比详情页面提前获取id
const id = route.paramMap.get('id');

return this.cs.getCrisis(id).pipe(
take(1), // 使用了take才能让observable变成可结束的
mergeMap(crisis => { // 不是特别懂为什么不用map
if (crisis) {
return of(crisis); // 有数据则把数据装在瓶子里放走
} else {
this.router.navigate(['/crisis-center']); // 没有数据则重定向到列表
return EMPTY; // 一个类似NULL的快捷定义
}
})
);
}
}

路由中使用时要注意构造一个对象,用于存放resolver的返回值

1
2
3
4
5
6
7
{
path: ':id',
component: CrisisDetailComponent,
resolve: {
crisis: CrisisDetailResolverService
}
}

在接下来的组件中要接收来自路由的数据并显示

1
2
3
4
5
6
7
ngOnInit() {
this.route.data // 从路由来的数据
.subscribe((data: { crisis: Crisis }) => {
this.editName = data.crisis.name;
this.crisis = data.crisis;
});
}

惰性加载(异步路由)

许多系统中都有类似的例子,为了加快启动速度,
一开始只加载一些必要的模块,当用户真正用到某些特定的模块时,才加载之.
angular也是如此.
做法上angular似乎采用了ES6里那种返回promise的动态rimpot方法.
触发的时间点通常是当用户访问了指定的path或满足了一定条件.
比如如下路由就是当用户真的访问了admin路径时才开始加载admin相关模块.

1
2
3
4
5
// app-routing.module.ts
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}

不过这里需要注意,外层的app路由中已经指定了路径,那么模块自己的路由中,顶层的路径就不再需要字符

1
2
3
4
5
6
7
8
// admin-routing.module.ts
{
path: '', // 这里不再需要字符了
component: AdminComponent,
children: [
// { ... }
]
}

最后要保证根模块中不再引用这个feature模块,完成真正的动态加载

1
2
3
4
5
6
7
8
9
10
11
12
@NgModule({
imports: [
HeroesModule,
AuthModule,
- AdminModule,
AppRoutingModule,
],
declarations: [
// ...
],
bootstrap: [ AppComponent ]
})

CanLoad

CanLoad是一个路由守卫的接口,允许angular在异步加载模块时有了更多的判断标准,
而不仅仅是用户是否访问了特定的路径.
比如用户虽然访问了admin路径,但却没能登陆成功,此时没有必要加载admin相关模块.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad { // 注意接口
constructor(private authService: AuthService, private router: Router) {}

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { }
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { }

// 这里的canload可以和检查登陆状态为依据的其他守卫写在一起
canLoad(route: Route): boolean {
const url = `/${route.path}`;

return this.checkLogin(url);
}

checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }

this.router.navigate(['/login']);
return false;
}
}

然后路由中就可以使用该守卫

1
2
3
4
5
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canLoad: [AuthGuard]
}

预加载

有一些虽然不必要但常用的模块,希望让其稍微延迟一会儿,
等必要的加载完了,用户能看到界面了,就立即加载.而不是等待用户访问这个路径才慢半拍反应过来.
这种做法,angular起名叫预加载.

angular默认提供两种策略:

  1. 不做预加载,用户访问路径逼不得已才加载模块
  2. 预加载所有模块

有时会觉得这个方法一刀切,希望自定义一个策略:

  1. 看路由配置,如果有 { preload: true } 则预加载

为了达成这一效果,angular认为可以做一个 service 来实现

1
2
3
4
5
6
7
8
9
10
export class SelectivePreloadingStrategyService implements PreloadingStrategy {

preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data && route.data.preload) {
return load(); // 这里的load返回一个Observable,意为可以继续?
} else {
return of(null); // 如果返回的是null,则表示预加载取消
}
}
}

然后可以在路由中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
path: 'crisis-center',
loadChildren: () => import('./crisis-center/crisis-center.module').then(m => m.CrisisCenterModule),
data: { preload: true }
}

// ....
@NgModule({
imports: [
RouterModule.forRoot(
appRoutes,
{
enableTracing: false, // <-- debugging purposes only
preloadingStrategy: SelectivePreloadingStrategyService, // 作为一个参数传递给forRoot方法
}
)
],
exports: [
RouterModule
]
})

路由动画

在不同路由之间切换时可以加入动画.
Angular的动画基于state,切换路由也是切换动画的state,
而在两个state之间如何切换,则需要人来定义.
另外将动画呈现在哪个路由出口,什么时候启用动画(trigger),都需要自行定义.

要使用动画,需要几个步骤

  1. 在app模块中引入 BrowserAnimationsModule

  2. 通常是在最外层的app组件中,引用动画内容.

    1. 模板中要定义哪个路由出口要使用该动画

      1
      2
      3
      4
      5
      <!-- at符约定一个触发器 -->
      <!-- 后面的函数中要将这个outlet作为显示动画的对象 -->
      <div [@routeAnimation]="getAnimationData(routerOutlet)">
      <router-outlet #routerOutlet="outlet"></router-outlet>
      </div>
    2. 然后ts代码中要定义好要用的动画,其实有些看不懂

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      import { slideInAnimation } from './animations';

      @Component({
      // ...
      animations: [ slideInAnimation ] // 动画集合的名称
      })
      export class AppComponent {
      getAnimationData(outlet: RouterOutlet) {
      return outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation;
      }
      }
  3. 想好要用那些state
    比如想在英雄列表(计划绑定heroes状态)和英雄详情页面(计划绑定hero状态)之间切换.

  4. 定义动画的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    // src/app/animations.ts
    import {
    trigger, animateChild, group,
    transition, animate, style, query
    } from '@angular/animations';

    export const slideInAnimation =
    trigger('routeAnimation', [ // trigger要照应组件中的定义
    transition('heroes <=> hero', [ // 在heroes状态和hero状态之间切换
    // 不知道做了什么,但缺了会导致页面混乱
    style({ position: 'relative' }),

    // 首先声明,无论是即将进入的组件还是要离开的组件
    // 被移动的页面会被视为一个position属性为absolute的HTML元素
    // 然后像对齐原点
    query(':enter, :leave', [
    style({
    position: 'absolute',
    top: 0,
    left: 0,
    width: '100%'
    })
    ]),

    // 即将进入的组件,先放在-100%,即屏幕的左侧
    query(':enter', [ // enter代表即将进入视角的组件页面
    style({ left: '-100%'})
    ]),
    query(':leave', animateChild()), // 似乎什么也不影响

    // 组合动画,300ms内,一个页面离开,一个页面进入
    group([
    query(':leave', [
    // 离开的页面的left达到100%,向右移出屏幕
    animate('300ms ease-out', style({ left: '100%'}))
    ]),
    query(':enter', [
    // 进入的页面left变为0,已经进入页面
    animate('300ms ease-out', style({ left: '0%'}))
    ])
    ]),
    query(':enter', animateChild()), // 似乎什么也不影响
    ])
    ]);
  5. 路由中绑定路由与动画定格.

    1
    2
    3
    4
    5
    const heroesRoutes: Routes = [
    // ...
    { path: 'superheroes', component: HeroListComponent, data: { animation: 'heroes' } },
    { path: 'superhero/:id', component: HeroDetailComponent, data: { animation: 'hero' } },
    ]

总结: 路由定义时的字段

事实上参考 Route 类的属性就知道了

  • path 地址
  • pathMatch 如果有重定向的话,路径匹配的策略如何
  • matcher 还没见过
  • redirectTo 重定向到的地址
  • outlet 路由出口
  • component 用来处理请求的组件
  • canActivate,canActivateChild,canDeactivate,canLoad,resolve等路由守卫
  • data 数据
  • children 如果是一个父路由,则代表子路由们
  • loadChildren? 专门用于惰性启动
  • runGuardsAndResolvers 目前还没见过

参考

  1. 主要参考