原文地址点 这里
一个angular应用程序就是一颗组件树。这些组件中有的是可重用的“UI组件”(如:列表,表格),还有一些表示逻辑部分的“应用组件”。路由器的关注点在“应用组件”上,或者,更具体地说,应该如何组织它们。我们可以这样称呼这种安排方式—路由状态(router states)。换句话说,路由器状态是定义在屏幕可视化的“应用组件”的组织方式。
在本文中,我们将深入了解RouterState。
本文基于Angular Router书籍,您可以在这里找到https://leanpub.com/router。 这本书超越了一个如何开始的指南,并深入讨论了路由器。 涵盖了心理模型,设计约束以及API的细微之处。 如果你喜欢这篇文章,看看这本书!
RouterState and RouterStateSnapshot
在导航过程中,应用重定向后,路由器会创建一个RouterStateSnapshot。 什么是RouterStateSnapshot,它与RouterState有什么不同?
RouteStateSnapshot是一个不可变数据结构,表示路由器在特定时刻的状态。 任何时候添加或删除组件或更新参数,都会创建一个新的快照。
RouterState类似于RouteStateSnapshot,但它表示路由器随时间变化的状态。
RouterStateSnapshot
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片段,获取的参数以及解析出的数据。
[
{
path: ':folder',
children: [
{
path: '',
component: ConversationsCmp
},
{
path: ':id',
component: ConversationCmp,
children: [
{
path: 'messages',
component: MessagesCmp
},
{
path: 'messages/:id',
component: MessageCmp,
resolve: {
message: MessageResolver
}
}
]
}
]
}
]
当我们导航到“/inbox/33/messages/44”时,路由器将查看URL,并构造以下RouterStateSnapshot:
之后路由器将ConversationCmp实例化并将MessageCmp放到里面
现在想象我们正在导航到一个不同的URL:“/inbox/33/messages/45”,这将导致以下快照:
为避免不必要的DOM修改,当相应路由的参数发生变化时,路由器将再次使用组件。 在此示例中,消息组件的id参数已从44更改为45.这意味着我们不能将ActivatedRouteSnapshot注入到MessageCmp中,因为该快照将始终将id参数设置为44,里面的数据已经旧了。
路由器状态快照表示应用程序在某一时刻的状态,因此命名为“快照”。 但组件能保持激活状态很长时间,其显示的数据可能会改变。 所以只有快照能保存它 - 我们需要一个数据结构,使我们能够处理变化。
介绍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[];
}
RouterState和ActivatedRoute类似于它们的快照,区别在于它们是可观察流(observables),这对于处理随时间变化的值非常有用。
路由器实例化的任何组件都可以注入其ActivatedRoute。
@Component({
template: `
Title: {{(message|async).title}}
...
`
})
class MessageCmp {
message: Observable<Message>;
constructor(r: ActivatedRoute) {
this.message = r.data.map(d => d.message);
}
}
如果我们从“/inbox/33/messages/44”导航到“/inbox/33/messages/45”,则可观察数据流将使用新的消息对象发出一组新的数据,组件将显示Message 45。
访问快照
流在大多数时候都是很方便的,但有时候我们想要一个可以马上检查的状态快照。
@Component({...})
class MessageCmp {
constructor(r: ActivatedRoute) {
r.url.subscribe(() => {
r.snapshot; // 任何时间 url变化 则调用此回调
});
}
}
ActivatedRoute
ActivatedRoute提供对url,params,data,queryParams和fragment流的访问。我们将仔细讨论这些,但首先让我们来看看它们之间的关系。
在一个路由中,URL的变化是一切变化的源头。这是必然的,因为用户可以直接修改它。
每当URL变化时,路由器从中导出一组新的参数:路由器将匹配的URL段的位置参数(例如“:id”)和最后匹配的URL段的矩阵参数进行组合。
这个操作是高纯度的—-URL必须改变才能改变参数。或者换句话说,相同的URL将始终导致相同的参数集合。
接下来,路由器调用路由的数据解析器( route’s data resolvers),并将结果与提供的静态数据组合。 由于数据解析器是任意函数,因此当给定相同的URL时,路由器不能保证您将获得相同的对象。 更重要的是,通常情况不一定如此! URL包含资源的ID,它是固定的,数据解析器提取该资源的内容,这些内容通常会随时间而变化。
最后,被激活的路由提供queryParams和fragment observables流。 与其他特定的某个路由上的可观察对象相反,查询参数和片段可以在多个路由之间共享。
URL
如下:
@Component({...})
class ConversationCmp {
constructor(r: ActivatedRoute) {
r.url.subscribe((s:UrlSegment[]) => {
console.log("url", s);
});
}
}
从“/inbox/33/messages/44” 到 “/inbox/33/messages/45”, 我们将看到:
url [{path: ‘messages’, params: {}}, {path: ‘44’, params: {}}]
url [{path: ‘messages’, params: {}}, {path: ‘45’, params: {}}]
我们不会经常监听URL变化,因为这些是很底层的。 一个可以实用的用例是当一个组件被通配符路由激活时。 因为在这种情况下,URL段的数组不是固定的,检查它可能会向用户显示不同的数据,这个功能可能是有用的。
Params
如下:
@Component({...})
class MessageCmp {
constructor(r: ActivatedRoute) {
r.params.subscribe((p => {
console.log("params", params);
});
}
}
从“/inbox/33/messages;a=1/44;b=1”到“/inbox/33/messages;a=2/45;b=2”
params {id: ‘44’, b: ‘1’}
params {id: ‘45’, b: ‘2’}
首先注意的是:id参数是一个字符串(当处理URL时,我们总是使用字符串)。其次,路由只能获得最后一个URL段的矩阵参数。 这就是为什么’a’参数不存在的原因。
Data
我们调整以上配置,看看data observable的工作原理。
{
path: 'messages/:id',
component: MessageCmp,
data: {
allowReplyAll: true
},
resolve: {
message: MessageResolver
}
}
MessageResolver定义如下:
class MessageResolver implements Resolve<any> {
constructor(private repo: ConversationsRepo, private currentUser: User) {}
resolve(route: ActivatedRouteSnapshot, state: RouteStateSnapshot):
Promise<Message> {
return this.repo.fetchMessage(route.params['id'], this.currentUser);
}
}
data属性用于传递一个编辑好的对象到激活的路由上,它在应用程序的整个生命周期内都不会改变。 resolve属性用于动态数据。
请注意,在上面的配置中,“message:MessageResolver”行不会告诉路由器实例化解析器。 它指示路由器通过依赖注入获取一个实例。 这意味着您必须在某个地方的提供商列表中注册“MessageResolver”。
一旦路由器获取了resolver,它就会调用它的“resolve”方法。 该方法可以返回promise ,流或任何其他对象。 如果返回值是promise 或流,路由器将在继续激活之前等待该promise 或流完成。
解析器不一定是一个实现“Resolve”接口的类。 它也可以是一个函数:
function resolver(route: ActivatedRouteSnapshot, state: RouteStateSnapshot):
Promise<Message> {
return repo.fetchMessage(route.params['id'], this.currentUser);
}
路由器将解析的和静态的数据组合成一个可以访问的属性,如下所示:
@Component({...})
class MessageCmp {
constructor(r: ActivatedRoute) {
r.data.subscribe((d => {
console.log('data', d);
});
}
}
当从“/inbox/33/message/44”到“/inbox/33/message/45”时
data {allowReplyAll: true, message: {id: 44, title: ‘Rx Rocks’, …}}
data {allowReplyAll: true, message: {id: 45, title: ‘Angular Rocks’, …}}
queryParams和fragment流
与其他特定的某个路由上的可观察对象相反,查询参数和片段可以在多个路由之间共享。
@Component({...})
class MessageCmp {
debug: Observable<string>;
fragment: Observable<string>;
constructor(route: ActivatedRoute) {
this.debug = route.queryParams.map(p => p.debug);
this.fragment = route.fragment;
}
}