Angular Router: 理解Router State

原文地址点 这里
一个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;
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值