Angular4 开发实战:(10) 路由导航(Router)
app/index.html
添加路由说明。
由于当前项目的根目录是app,所以添加如下代码:
<base href="/">
路由模块已经从Angular中分离出来,所以我们在使用时要导入:
import { RouterModule, Routes } from '@angular/router';
现在可以来写一个简单的路由配置,我们将路由放置到一个模块中:
// app/modules/app-routing.module.ts
import { NgModule } from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {DemoComponentComponent} from '../demo/demo-component/demo-component.component';
const routes: Routes = [
{ path: 'demoComponent', component: DemoComponentComponent},
...
{ path: '**', component: DemoComponentComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(routes)
],
exports: [RouterModule]
})
export class AppRoutingModule { }
在上面的代码中,我们定义了一个路由数组,每一个都会把一个URL路径(path
)映射到一个组件。
最后的**
是路由通配符,当所有其上面的路由路径都不符合(包括无效路径)时,就会采取通配符路由。
注意:
- 在特性模块中,应该使用
RouterModule.forChild()
path
不能以斜杠(/
)开头
// app.module.ts
...
import { AppComponent } from './app.component';
import {AppRoutingModule} from './modules/app-routing.module';
@NgModule({
...
imports: [
...
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
有了路由导航,我们还需要一个容器来显示对应的组件内容:
<router-outlet></router-outlet>
添加如上代码后,当路由切换(路由模块中存在的路由)时,对应的组件会显示在其之后。
注:可以参考app/app.component.html
。
注(来源官网):路由器使用先匹配者优先的策略来匹配路由,所以,具体路由应该放在通用路由的前面。在上面的配置中,带静态路径的路由被放在了前面,后面是空路径路由,因此它会作为默认路由。而通配符路由被放在最后面,这是因为它能匹配上每一个URL,因此应该只有在前面找不到其它能匹配的路由时才匹配它。
既然是路由导航,除了可以直接在浏览器的地址栏输入地址跳转外,我们更多时候需要一系列可以点击的链接元素(比如a,button):
<a routerLink="/demoComponent">创建组件</a>
由于导航路径是固定的,所以我们将一个字符串赋给routerLink
。
注:a标签上的RouterLink
指令让路由器得以控制这个a元素。
当然,如果我们需要更复杂的路由路径,我们可以使用链接参数数组:
[routerLink] = ['/demoComponent', 'id']
[routerLink] = ['/demoComponent', { name: 'route', id: 123}]
一般情况下,我们会为当前激活的路径链接添加一些高亮效果,这时我们可以利用routerLinkActive
属性:
<a routerLink="/demoComponent" routerLinkActive="active">创建组件</a>
它的值是一个以空格分割的CSS样式类列表。
还可以是一个CSS类数组:
[routerLinkActive]="['...']"
注(来源官网):RouterLinkActive
指令会基于当前的RouterState
对象来为激活的RouterLink
切换CSS类。 这会一直沿着路由树往下进行级联处理,所以父路由链接和子路由链接可能会同时激活。 要改变这种行为,可以把[routerLinkActiveOptions]绑定到{exact: true}
表达式。 如果使用了{ exact: true }
,那么只有在其URL与当前URL精确匹配时才会激活指定的RouterLink。
重定向
在路由配置中,我们还可以添加重定向参数:
{ path: '', redirectTo: '/demoComponent', pathMatch: 'full' }
注:重定向路由需要一个pathMatch
属性,来告诉路由器如何用URL去匹配路由的路径,否则路由器就会报错。
路由参数
在配置路由时,我们还可以定义路由参数:
// app/modules/app-routing.module.ts
{ path: 'demoRouter/:id', component: DemoRouterComponent}
比如输入http://localhost:4200/demoRouter/123,然后我们需要通过ActivatedRoute
服务来获取这些路由参数:
// demo-router.component.ts
import { Component, OnInit } from '@angular/core';
import {ActivatedRoute} from '@angular/router';
...
export class DemoRouterComponent implements OnInit {
id: any;
constructor(private route: ActivatedRoute) {
this.route.params.subscribe(param => {
this.id = param['id'];
})
}
...
}
在上面的代码中,我们注入ActivatedRoute
服务并定义了一个私有属性route
,然后通过route.params
属性来获取路由参数,ActivatedRoute.params
是一个路由参数的可观察对象,所以我们要通过subscribe()
方法来访问。
注:所有的路由参数或查询参数都是字符串。
子路由
子路由也是我们常用的,定义也很简单:
// app/modules/app-routing.module.ts
import {DemoChildRouterComponent} from '../demo/demo-child-router/demo-child-router.component';
const routes: Routes = [
...
{
path: 'demoRouter2', component: DemoRouter2Component,
children: [{
path: 'child',
component: DemoChildRouterComponent
}]
}
];
子路由组件:
// app/demo/demo-child-router.component.html
<div class="box">
我是子路由
</div>
前面说过我们要给路由添加视图容器,子路由也是一样,不过子路由的视图容器要放在另一个组件中:
// app/demo/demo-router2.component.html
<div class="box">
<a routerLink="/demoRouter2/child">访问子路由</a>
</div>
<router-outlet></router-outlet>
当我们点击上面的链接时,你会看到浏览器地址栏变为:http://localhost:4200/demoRouter2/child,组件DemoChildRouterComponent
中的模板就会显示出来。
路由动画
对于路由,我们还可以添加动画,但使用动画之前,我们要安装动画模块(Angular4后动画模块分离出来了):
npm install @angular/animations --save
然后在主模块app.module.ts
中注册:
...
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
@NgModule({
declarations: [... ],
imports: [
...
BrowserAnimationsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
我们添加一个具有动画的子路由:
// app/modules/app-routing.module.ts
import {DemoChildRouter2Component} from '../demo/demo-child-router/demo-child-router2.component';
const routes: Routes = [
...
{
path: 'demoRouter2', component: DemoRouter2Component,
children: [
...
{
path: 'child2',
component: DemoChildRouter2Component
}]
}
];
对于DemoChildRouter2Component
组件,我们修改如下:
// demo/demo-child-router2.component.html
import {Component, HostBinding, OnInit} from '@angular/core';
import {animate, state, style, trigger, transition} from '@angular/animations';
@Component({
selector: 'app-demo-child-router2',
templateUrl: './demo-child-router2.component.html',
styleUrls: ['./demo-child-router2.component.scss'],
animations: [
trigger('fadeInUpState', [
state('in', style({opacity: 1, transform: 'translate3d(0, 0, 0)'})),
transition('void => *', [
style({
opacity: 0,
transform: 'translate3d(0, 100%, 0)'
}), animate('.4s cubic-bezier(.25,.8,.25,1)')
])
])
]
})
export class DemoChildRouter2Component implements OnInit {
@HostBinding('@fadeInUpState') fadeInUpState;
@HostBinding('style.display') display = 'block';
constructor() { }
}
对于@HostBinding()
是属性装饰器,用来给宿主设置属性。在这里我们将动画@fadeInUpState
添加到宿主上,同时还将宿主的display
的值设置为block
。
注意:给宿主添加动画,一定要设置`display`的值,因为默认自定义元素的display
是空字符串,CSS动画是不起作用的。
关于动画的更多细节,可以看《动画(Animation)》一章。
异步路由
随着项目越来越大,我们一般不会将所有组件都添加到主模块中,这时我们可以根据不同路由的;奥惰性加载不同的模块:
const routes: Routes = [
...
{
path: 'demoRouter2',
component: DemoRouter2Component,
children: [
...
{
path: 'loadModule',
loadChildren: 'app/demo/demo-router-load.module#RouterLoadModule',
}]
}
];
当路由器导航到这个路由时,它会用loadChildren
字符串来动态加载RouterLoadModule
,然后把RouterLoadModule
添加到当前的路由配置中, 最后,它把所请求的路由加载到目标DemoRouterLoadComponent
组件中。
RouterLoadModule
代码如下:
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {DemoRouterLoadComponent} from './demo-router-load.component';
import {DemoRouterLoadRougingModule} from './demo-router-load-routing.module';
@NgModule({
imports: [CommonModule, DemoRouterLoadRougingModule],
declarations: [DemoRouterLoadComponent]
})
export class RouterLoadModule {}
仔细看的话,你会发现一个新的子路由模块DemoRouterLoadRougingModule
,它是用来加载异步路由配置的:
// demo-router-load-routing.module.ts
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {DemoRouterLoadComponent} from './demo-router-load.component';
const routes: Routes = [
{
path: '',
component: DemoRouterLoadComponent
}
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DemoRouterLoadRougingModule {}
使用:
// demo-router2.component.html
<a routerLink="/demoRouter2/loadModule">访问异步路由</a>
当你点击上面的链接时,你可以打开控制台看看Network看看会多了一个加载文件:
注:惰性加载和重新配置工作只会发生一次,也就是在该路由首次被请求时。在后续的请求中,该模块和路由都是立即可用的。