最近在使用angular来作项目,今天就简单的聊聊angular的路由管理。javascript
不管是angular仍是其它两个主流框架,总体上就是一个大的组件树,包括一些能够复用的UI组件或者业务组件。而路由的做用就是关于如何使用分配这些组件。它来以为在某种状况下该显示哪些组件,隐藏哪些组件。 咱们来看一个angular的路由配置:前端
const routes: Routes = [
{
path: 'home',
component: HomeComponent,
children: [
{
path: 'persons',
children: [
{
path: 'list',
component:PersonListCom,
}
]
},
{
path: 'single',
children: [
{
path: 'personDetail',
component: PersonDetailCom
},
{
path: 'personDetail/:id',
component: PersonDetailCom
}
]
},
{
path: '/', redirectTo: '/persons/list', pathMatch: 'full'
},
]
},
{
path: 'login',
component: LoginComponent,
}
];
复制代码
Route对象定义应用程序中某些可路由状态(组件、重定向等)与URL段之间的关系。经过导入RouterModule
并将路由配置数组传递给RouterModule.forRoot()
在应用程序中声明性地指定路由器配置。java
你们能够先不看具体代码,我将路由的配置结构转成了下面这张图。 web
咱们在访问路由的时候,就是访问组件树的某条子树。好比,我想要访问PersonListCom
组件,那就是home/persons/list
的路由访问顺序,路由节点对应的组件就会进行实例化。 数组
home
路径,下面有两条子树,它们没法同时渲染,由于从标准上来讲,
outlet
就是指组件具体放置在DOM中的位置,因此同一个位置只能放一个组件。因此咱们添加了
redirect
配置,在没法访问的时候,进行重定向。
路由的一个重要的功能就是在不一样的路由状态之间进行切换,而且更新组件的实例。 从路由访问部分能够看出,要访问某一个路由节点,就是将某条子树的节点串联起来,好比:home/persons/list
,从另外一个角度来讲,这条节点串就是咱们常说的URL,即,URL就是路由的扁平化标识。 而导航的做用就是从一个路由到另外一个路由状态的切换。咱们在访问不一样页面的时候,主要就是修改浏览器中的URL地址,框架会分析新的路由,隐藏旧的组件,并将新的组件渲染出来加载到页面上。 因此路由器只容许咱们表达应用程序可能处于的全部潜在状态,并提供一种从一种状态导航到另外一种状态的机制。 与原生应用程序相比,URL栏为web应用程序提供了巨大的优点。它容许咱们引用状态,为它们添加书签,并与咱们的朋友共享它们。在性能良好的web应用程序中,任何应用程序状态转换都会致使URL更改,而任何URL更改都会致使状态转换。换句话说,URL只不过是序列化的路由器状态。angular路由器负责管理URL,以确保它始终与路由器状态同步。浏览器
路由器的核心是一个强大的URL匹配引擎。若是不能将url与要呈现的适当组件集相关联,则没法在应用程序中导航。因此咱们须要了解整个URL的访问过程 好比咱们要访问: home/single/personDetail/23
bash
重定向:重定向是URL部分的替换。重定向能够是本地的,也能够是绝对的。本地重定向用不一样的段替换单个段。绝对重定向替换整个URL。重定向是本地的,除非您在url前面加上斜线。数据结构
当用户点击跳转时,URL地址会发生变化,router就会检测到这种变化,首先要处理的就是是否须要重定向。重定向能够在路由器配置树中的每一个嵌套级别发生,但每一个级别只能发生一次。这是为了不任何无限的重定向循环。 在前面的配置中有一个重定向的配置path: '/', redirectTo: '/persons/list', pathMatch: 'full'
,使用/persons/list
替换 /
,这就是一种绝对替换。 咱们要访问home/persons/list
,不是/
因此不会发生重定向。框架
下一步就是路由状态的识别。路由会从URL中获取到路由状态,而后路由器一个接一个地遍历路由配置数组,检查URL是否以路由的路径开始。若是没法匹配到正确的路由,导航跳转就会失败。但若是它完成匹配,表明应用会构造出将要跳转页面的路由器状态。async
路由器状态由激活的路由组成。每一个激活的路由能够与一个组件相关联。另外,请注意,咱们老是有一个激活的路由与应用程序的根组件相关联。
在这个阶段,我已经有了将要跳转页面的路由状态。接下来就要经过路由守卫来检测是否容许完成此次跳转。
import { HomeGuard } from './page/home/home.guard';
{
path: 'home',
canLoad: [HomeGuard],
canActivate: [HomeGuard],
canActivateChild: [HomeGuard],
}
复制代码
能够看到这部分代码,有canLoad
、canActivateChild
、canActivate
几个节点,表示在路由执行的这几个节点都须要执行HomeGuard文件中的判断方法来检测是否容许执行下面的步骤。若是全部守卫都返回 true,就会继续导航。若是任何一个守卫返回了 false,就会取消导航。 HomeGuard中包括一下几个接口,对应不一样阶段的处理。
/** * 决定该路由可否激活 **/
canActivate(){
}
/** * 来决定该路由的子路由可否激活 **/
canActivateChild() {
}
/** * 是否可加载模块 */
canLoad(){
}
复制代码
在经过路由守卫以后,就能够处理数据了。在这个路径中,23
是要访问的用户id,咱们须要经过这个id参数获取用户的数据。 在平常业务中,有时候只须要在组件加载完成以后,组件本身获取到URL中的参数,请求数据就能够了。但有些时候,须要在页面加载以前去获取用户数据,若是有数据,就将数据传给组件去渲染,若是没有就中止加载页面。
[
{
path: 'single',
children: [
{
path: 'personDetail',
component: PersonDetailCom
},
{
path: 'personDetail/:id',
component: PersonDetailCom,
resolve: {
conversations: ConversationsResolver
}
}
]
}
]
复制代码
在下面,咱们定义了ConversationsResolver
。
@Injectable()
class ConversationsResolver implements Resolve<any> {
constructor(
private repo: ConversationsRepo,
private currentPerson: Person) {}
resolve(route: ActivatedRouteSnapshot, state: RouteStateSnapshot):
Promise<Conversation[]> {
return this.repo.fetchAll(
route.paramMap.get('id'),
this.currentPerson
);
}
}
复制代码
当导航到single/personDetail/23
的时候,会获取到当前路由的状态,包括参数id:
23。使用这个参数请求用户的Conversation数据。 而后在组件PersonDetailCom
中,能够获取到resolver中请求获取到的数据。
@Component({
template: ` <conversation *ngFor="let c of conversations | async"> </conversation> `
})
class PersonDetailCom {
conversations: Observable<Conversation[]>;
constructor(route: ActivatedRoute) {
this.conversations = route.data.pluck('conversations');
}
}
复制代码
在这个阶段,咱们经过已经获取到的路由状态实例化须要的组件,将它放到对应的router-outlet
。 下面看下怎么使用router-outlet
,咱们在HomeComponent中添加两个outlets:默认的和header
。
@Component({ template: ` ... <router-outlet name="header"></router-outlet> ... <router-outlet></router-outlet> `
})
class HomeComponent { }
复制代码
咱们能够在路由配置中指定组件渲染的outlet。
[
{
path: 'single',
children: [
{
path: 'personDetail',
component: PersonDetailCom
},
{
path: 'personDetail/:id',
children: [
{ path: '', component: PersonDetailCom },
{
path: '',
component: HeaderCom,
outlet: 'header',
}
],
resolve: {
conversations: ConversationsResolver
}
}
]
}
]
复制代码
在路由personDetail/:id
下经过children添加了两个组件PersonDetailCom
和HeaderCom
,而且HeaderCom
指定了outlet
是header
。 这样,PersonDetailCom
组件实例化以后会被放在默认的outelet上,HeaderCom
会被放置在header上。
除了配置resolver
以外,还能够在组件中获取参数id,请求对应数据。
@Component({...})
class PersonDetailCom {
conversation: Observable<Conversation>;
id: Observable<string>;
constructor(r: ActivatedRoute) {
// r.data is an observable
this.conversation = r.data.map(d => d.conversation);
// r.paramMap is an observable
this.id = r.paramMap.map(p => p.get('id')); }
}
复制代码
经过ActivatedRoute
能够获取到URL上的参数,经过参数获取数据。
在这一点上,路由器已经建立了一个路由器状态并实例化了组件。接下来,咱们须要可以从这个路由器状态导航到另外一个路由器状态。有两种方法能够实现这一点:
this.router.navigateByUrl('/home/single/personDetail/23');
<button nz-button routerLink="/home/single/personDetail/23"></button>
复制代码
这样就完成了路由的整个处理过程。 成功匹配URL的结果是,一些组件集将被路由到,并经过使用router outlet
指令在屏幕上呈现。但此操做还有一个有用的反作用——建立RouterState和RouterStateSnapshot对象。
路由完成后,咱们可能但愿访问有关URL和路由到的组件集(称为当前路由器状态)的信息。咱们介绍一下RouterState
和RouterStateSnapshot
属性,它容许咱们访问有关当前路由到的URL和组件的信息。 在重定向以后,就会获取到将要跳转页面的路由器状态RouterStateSnapshot
。 **routestastesnapshot是一种不可变的数据结构,表示路由器在特定时刻的状态。**在组件增长、删除或者参数变化的时候就会建立新的snapshot。 router state与RouteStateSnapshot相似,只是它表示路由器随时间变化的状态。
interface RouterStateSnapshot {
root: ActivatedRouteSnapshot;
}
interface ActivatedRouteSnapshot {
url: UrlSegment[];
params: {[name:string]:string};
data: {[name:string]:any};
queryParams: {[name:string]:string};
fragment: string;
root: ActivatedRouteSnapshot;
parent: ActivatedRouteSnapshot;
firstchild: ActivatedRouteSnapshot;
children: ActivatedRouteSnapshot[];
}
复制代码
从定义能够看出RouterStateSnapshot
是一个已经激活的路由树,此树中的每一个节点都知道“已使用”的URL段、提取的参数和已解析的数据。 每一个节点头能够经过parent和children属性访问它的父节点和子节点。 当咱们导航到/home/single/personDetail/23
时,路由器将查看URL并构造如下RouterStateSnapshot:
/home/single/personDetail/24
,那么它的snapshot会变为
RouterStateSnapshot
就不能知足咱们的需求了。 这就须要另外一个数据结构了:
RouterState
。
interface RouterState {
snapshot: RouterStateSnapshot; //returns current snapshot
root: ActivatedRoute;
}
interface ActivatedRoute {
snapshot: ActivatedRouteSnapshot; //returns current snapshot
url: Observable<UrlSegment[]>;
params: Observable<{[name:string]:string}>;
data: Observable<{[name:string]:any}>;
queryParams: Observable<{[name:string]:string}>;
fragment: Observable<string>;
root: ActivatedRout;
parent: ActivatedRout;
firstchild: ActivatedRout;
children: ActivatedRout[];
}
复制代码
它的结构和RouterStateSnapshot
类似,只不过它暴露出来的值都是可观察的,这对于处理获取随时间变化的值很是有用。 路由器实例化的任何组件均可以注入ActivatedRoute。
@Component(...)
class PersonDetailCom {
id: Observable<string>;
constructor(r: ActivatedRoute) {
this.id = r.data.map(d => d.id);
}
}
复制代码
在id由23变为24的时候,能够获取到最新的id值,经过此值做为参数请求咱们须要的数据。
**ActivatedRoute提供对url、params、data、queryParams和fragment observates的访问。**由于用户访问页面是经过更改URL实现的,因此URL的变化是引发路由变化的缘由。 **每当URL改变时,路由器就从中派生出一组新的参数:**路由器接受匹配URL段的位置参数(例如':id')和最后一个匹配URL段的矩阵参数并将它们组合起来。此操做是纯操做:必须更改URL才能更改参数。或者换句话说,相同的URL将始终致使相同的参数集。 **接下来,路由器调用路由的数据解析器,并将结果与提供的静态数据相结合。**因为数据解析器是任意的函数,路由器没法保证在给定相同的URL时,您将得到相同的对象。URL包含资源的id,该id是固定的,数据解析器获取该资源的内容,这些内容一般随时间而变化。
@Component({...})
class ConversationCmp {
constructor(r: ActivatedRoute) {
/** * 获取url **/
r.url.subscribe((s:UrlSegment[]) => {
console.log("url", s);
});
/** * 获取参数 **/
r.params.subscribe((p => {
console.log("params", params);
});
}
}
复制代码
看下关于data的处理,在路由配置中能够添加data属性,给对应路由的组件传递固定的data值。data属性用于将固定对象传递到激活的路由。它在应用程序的整个生命周期内都不会更改。
{
path: 'personDetail/:id',
children: [
{ path: '', component: PersonDetailCom },
{
path: '',
component: HeaderCom,
outlet: 'header',
}
],
data: [
personStatus: 'activited'
],
resolve: {
conversations: ConversationsResolver
}
}
复制代码
能够经过订阅ActivatedRoute
中的data属性,获取对应的值。
@Component({...})
class MessageCmp {
constructor(r: ActivatedRoute) {
/**
* 获取路由配置中的data
**/
r.data.subscribe((d => {
console.log('data', d);
});
}
}
复制代码
好了,今天先介绍这些,但愿对你们有帮助。
参考文章:
Angular Router: Understanding Router State
The Three Pillars of the Angular Router
Angular-router
订阅“前端记事本”,了解最新的前端信息。