4232 lines
206 KiB
Plaintext
4232 lines
206 KiB
Plaintext
include ../_util-fns
|
||
|
||
:marked
|
||
The Angular ***Router*** enables navigation from one [view](./glossary.html#view) to the next
|
||
as users perform application tasks.
|
||
|
||
在用户使用应用程序时,Angular的***路由器***能让用户从一个[视图](./glossary.html#view)导航到另一个视图。
|
||
|
||
We cover the router's primary features in this chapter, illustrating them through the evolution
|
||
of a small application that we can <live-example>run live</live-example>.
|
||
|
||
本章覆盖了该路由器的主要特性。我们通过一个小型应用的成长演进来讲解它。参见<live-example>在线例子</live-example>。
|
||
|
||
.l-sub-section
|
||
|
||
img(src='/resources/images/devguide/plunker-separate-window-button.png' alt="pop out the window" align="right" style="margin-right:-20px")
|
||
|
||
:marked
|
||
To see the URL changes in the browser address bar,
|
||
pop out the preview window by clicking the blue 'X' button in the upper right corner.
|
||
|
||
请点击右上角的蓝色'X'按钮以弹出预览窗口,这样可以看到浏览器地址栏的变化情况。
|
||
|
||
.l-main-section
|
||
|
||
:marked
|
||
## Overview
|
||
|
||
## 概览
|
||
|
||
The browser is a familiar model of application navigation.
|
||
We enter a URL in the address bar and the browser navigates to a corresponding page.
|
||
We click links on the page and the browser navigates to a new page.
|
||
We click the browser's back and forward buttons and the browser navigates
|
||
backward and forward through the history of pages we've seen.
|
||
|
||
浏览器是一个熟悉的应用导航操作模型。
|
||
如果在地址栏中输入一个URL,浏览器就会导航到相应的页面。
|
||
如果你在页面中点击链接,浏览器就会导航到一个新的页面。
|
||
如果你点击浏览器上的前进和后退按钮,浏览器就会根据你看过的页面历史向前或向后进行导航。
|
||
|
||
The Angular ***Router*** ("the router") borrows from this model.
|
||
It can interpret a browser URL as an instruction
|
||
to navigate to a client-generated view and pass optional parameters along to the supporting view component
|
||
to help it decide what specific content to present.
|
||
We can bind the router to links on a page and it will navigate to
|
||
the appropriate application view when the user clicks a link.
|
||
We can navigate imperatively when the user clicks a button, selects from a drop box,
|
||
or in response to some other stimulus from any source. And the router logs activity
|
||
in the browser's history journal so the back and forward buttons work as well.
|
||
|
||
Angular的***路由器***(以下简称“路由器”)借鉴了这个模型。它把浏览器中的URL看做一个操作指南,
|
||
据此导航到一个由客户端生成的视图,并可以把参数传给支撑视图的相应组件,帮它决定具体该展现哪些内容。
|
||
我们可以为页面中的链接绑定一个路由,这样,当用户点击链接时,就会导航到应用中相应的视图。
|
||
当用户点击按钮、从下拉框中选取,或响应来自任何地方的事件时,我们也可以在代码控制下进行导航。
|
||
路由器还在浏览器的历史日志中记录下这些活动,这样浏览器的前进和后退按钮也能照常工作。
|
||
|
||
We'll learn many router details in this chapter which covers
|
||
|
||
在本章中,我们将会学到关于路由器的更多细节知识:
|
||
|
||
* Setting the [base href](#base-href)
|
||
|
||
* 设置[页面的基地址(base href)](#base-href)
|
||
|
||
* Importing from the [router library](#import)
|
||
|
||
* 从[路由库](#import)中导入
|
||
|
||
* [configuring the router](#route-config)
|
||
|
||
* [配置路由器](#route-config)
|
||
|
||
* the [link parameters array](#link-parameters-array) that propels router navigation
|
||
|
||
* 推动路由器导航的[链接参数数组](#link-parameters-array),
|
||
|
||
* navigating when the user clicks a data-bound [RouterLink](#router-link)
|
||
|
||
* 在用户点击绑定到数据的[RouterLink](#router-link)时进行导航
|
||
|
||
* navigating under [program control](#navigate)
|
||
|
||
* 在[程序的控制下](#navigate)进行导航
|
||
|
||
* retrieving information from the [route](#activated-route)
|
||
|
||
* 从[当前路由获取信息](#activated-route)
|
||
|
||
* [animating](#route-animation) transitions for route components
|
||
|
||
* 为路由组件添加转场[动画](#route-animation)
|
||
|
||
* navigating [relative](#relative-navigation) to our current URL
|
||
|
||
* [相对当前URL进行导航](#relative-navigation)
|
||
|
||
* toggling css classes for the [active router link](#router-link-active)
|
||
|
||
* 利用[`router-link-active`指令]切换CSS类(#router-link-active)
|
||
|
||
* embedding critical information in the URL with [route parameters](#route-parameters)
|
||
|
||
* 用[路由参数](#route-parameters)把重要信息嵌入URL
|
||
|
||
* providing non-critical information in [optional route parameters](#optional-route-parameters)
|
||
|
||
* 在[可选路由参数](#optional-route-parameters)中提供非关键信息
|
||
|
||
* refactoring routing into a [routing module](#routing-module)
|
||
|
||
* 重构路由到[路由模块](#routing-module)
|
||
|
||
* add [child routes](#child-routing-component) under a feature section
|
||
|
||
* 在“特性分区”下添加[子路由](#child-routing-component)
|
||
|
||
* [grouping child routes](#component-less-route) without a component
|
||
|
||
* 不借助组件[对子路由进行分组](#component-less-route)
|
||
|
||
* [redirecting](#redirect) from one route to another
|
||
|
||
* 从一个路由[重定向](#redirect)到另一个路由
|
||
|
||
* confirming or canceling navigation with [guards](#guards)
|
||
|
||
* 借助[守卫函数](#guards)确认或取消导航
|
||
|
||
* [CanActivate](#can-activate-guard) to prevent navigation to a route
|
||
|
||
* 用[CanActivate](#can-activate-guard)阻止导航进某路由
|
||
|
||
* [CanActivateChild](#can-activate-child-guard) to prevent navigation to a child route
|
||
|
||
* 用[CanActivateChild](#can-activate-child-guard)阻止导航进某子路由
|
||
|
||
* [CanDeactivate](#can-deactivate-guard) to prevent navigation away from the current route
|
||
|
||
* 用[CanDeactivate](#can-deactivate-guard)阻止离开当前路由的导航
|
||
|
||
* [Resolve](#resolve-guard) to pre-fetch data before activating a route
|
||
|
||
* 用[Resolve](#resolve-guard)在路由激活之前预先获取数据
|
||
|
||
* [CanLoad](#can-load-guard) to prevent asynchronous routing
|
||
|
||
* 用[CanLoad](#can-load-guard)阻止异步路由
|
||
|
||
* providing optional information across routes with [query parameters](#query-parameters)
|
||
|
||
* 用[查询参数](#query-parameters)提供跨路由的可选信息
|
||
|
||
* jumping to anchor elements using a [fragment](#fragment)
|
||
|
||
* 使用[fragment](#fragment)跳转到其它元素
|
||
|
||
* loading feature areas [asynchronously](#asynchronous-routing)
|
||
|
||
* [异步](#asynchronous-routing)加载特性分区
|
||
|
||
* pre-loading feature areas [during navigation](#preloading)
|
||
|
||
* [在导航时](#preloading)预加载特性分区
|
||
|
||
* using a [custom strategy](#custom-preloading) to only pre-load certain features
|
||
|
||
* 使用[自定义策略](#custom-preloading)来只预加载指定分区
|
||
|
||
* choosing the "HTML5" or "hash" [URL style](#browser-url-styles)
|
||
|
||
* 选择"HTML5"或"hash"[URL风格](#browser-url-styles)
|
||
|
||
We proceed in phases marked by milestones building from a simple two-pager with placeholder views
|
||
up to a modular, multi-view design with child routes.
|
||
|
||
接下来,我们分为几个里程碑阶段,把一个简单的、只有占位(placeholder)视图的双页范例,升级成一个模块化的、带有子路由的多视图设计。
|
||
|
||
But first, an overview of router basics.
|
||
|
||
不过,还是先来做一个路由器基础知识的概览。
|
||
|
||
.l-main-section
|
||
|
||
:marked
|
||
## The Basics
|
||
|
||
## 基础知识
|
||
|
||
Let's begin with a few core concepts of the Router.
|
||
Then we can explore the details through a sequence of examples.
|
||
|
||
我们从路由器的少量核心概念开始,然后在通过一系列的例子来了解它们的各种细节。
|
||
|
||
:marked
|
||
### *<base href>*
|
||
|
||
### *<base href>*
|
||
|
||
Most routing applications should add a `<base>` element to the **`index.html`** as the first child in the `<head>` tag
|
||
to tell the router how to compose navigation URLs.
|
||
|
||
大多数带路由的应用都要在**`index.html`**的`<head>`标签下先添加一个`<base>`元素,来告诉路由器该如何合成导航用的URL。
|
||
|
||
If the `app` folder is the application root, as it is for our sample application,
|
||
set the `href` value *exactly* as shown here.
|
||
|
||
如果`app`文件夹是该应用的根目录(就像我们的范例应用一样),那就把`href`的值设置为下面这样:
|
||
|
||
+makeExcerpt('index.1.html', 'base-href')
|
||
|
||
:marked
|
||
### Router imports
|
||
|
||
### 从路由库中导入
|
||
|
||
The Angular Router is an optional service that presents a particular component view for a given URL.
|
||
It is not part of the Angular core. It is in its own library package, `@angular/router`.
|
||
We import what we need from it as we would from any other Angular package.
|
||
|
||
Angular的路由器是一个可选的服务,它用来呈现指定的URL所对应的视图。
|
||
它并不是Angular 2核心库的一部分,而是在它自己的`@angular/router`包中。
|
||
像其它Angular包一样,我们可以从它导入所需的一切。
|
||
|
||
+makeExcerpt('app/app.module.1.ts (import)', 'import-router')
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
We cover other options in the [details below](#browser-url-styles).
|
||
|
||
我们将会在[后面](#browser-url-styles)详细讲解其它选项。
|
||
|
||
:marked
|
||
### Configuration
|
||
|
||
### 配置
|
||
|
||
The application will have one *`router`*. When the browser's URL changes, the router looks for a corresponding **`Route`**
|
||
from which it can determine the component to display.
|
||
|
||
该应用将有一个*`router`(路由器)*。当浏览器的地址变化时,该路由器会查找相应的**`Route`(路由定义,简称路由)**,并据此确定所要显示的组件。
|
||
|
||
A router has no routes until we configure it.
|
||
We bootstrap our application with an array of routes that we'll provide to our **`RouterModule.forRoot`** function.
|
||
|
||
需要先配置路由器,才会有路由信息。
|
||
首选方案是用带有“路由数组”的**`provideRouter`**工厂函数(`[provideRouter(routes)]`)来启动此应用。
|
||
|
||
In the following example, we configure our application with four route definitions. The configured `RouterModule` is
|
||
add to the `AppModule`'s `imports` array.
|
||
|
||
在下面的例子中,我们用四个路由定义配置了本应用的路由器。这个配置的`RouterModule`被添加到`AppModule`的`imports`数组中。
|
||
|
||
+makeExcerpt('app/app.module.0.ts (excerpt)', 'route-config')
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
The `RouterModule` is provided an array of *routes* that describe how to navigate.
|
||
Each *Route* maps a URL `path` to a component.
|
||
|
||
`RouterConfig`是一个*路由*数组,它会决定如何导航。
|
||
每个*Route*会把一个URL的`path`映射到一个组件。
|
||
|
||
There are no **leading slashes** in our **path**. The router parses and builds the URL for us,
|
||
allowing us to use relative and absolute paths when navigating between application views.
|
||
|
||
**path**中**不能用斜线`/`**开头。路由器会为我们解析和生成URL,以便在多个视图间导航时,可以自由使用相对路径和绝对路径。
|
||
|
||
The `:id` in the first route is a token for a route parameter. In a URL such as `/hero/42`, "42"
|
||
is the value of the `id` parameter. The corresponding `HeroDetailComponent`
|
||
will use that value to find and present the hero whose `id` is 42.
|
||
We'll learn more about route parameters later in this chapter.
|
||
|
||
第一个路由中的`:id`是一个路由参数的令牌(Token)。比如`/hero/42`这个URL中,“42”就是`id`参数的值。
|
||
此URL对应的`HeroDetailComponent`组件将据此查找和展现`id`为42的英雄。
|
||
在本章中稍后的部分,我们将会学习关于路由参数的更多知识。
|
||
|
||
The `data` property in the third route is a place to store arbitrary data associated with each
|
||
specific route. This data is accessible within each activated route and can be used to store
|
||
items such as page titles, breadcrumb text and other read-only data. We'll use the [resolve guard](#resolve-guard)
|
||
to retrieve additional data later in the chapter.
|
||
|
||
第三个路由中的`data`属性用来存放于每个具体路由有关的任意信息。该数据可以被任何一个激活路由访问,并能用来保存诸如
|
||
页标题、面包屑以及其它只读数据。本章稍后的部分,我们将使用[resolve守卫](#resolve-guard)来获取更多数据。
|
||
|
||
The `empty path` in the fourth route matches as the default path for each level of routing. It
|
||
also allows for adding routes without extending the URL path.
|
||
|
||
第四个路由中的`empty path`匹配各级路由的默认路径。
|
||
它还支持在不扩展URL路径的前提下添加路由。
|
||
|
||
The `**` in the fourth route denotes a **wildcard** path for our route. The router will match this route
|
||
if the URL requested doesn't match any paths for routes defined in our configuration. This is useful for
|
||
displaying a 404 page or redirecting to another route.
|
||
|
||
第四个路由中的`**`代表该路由是一个**通配符**路径。如果当前URL无法匹配上我们配置过的任何一个路由中的路径,路由器就会匹配上这一个。当需要显示404页面或者重定向到其它路由时,该特性非常有用。
|
||
|
||
**The order of the routes in the configuration matters** and this is by design. The router uses a **first-match wins**
|
||
strategy when matching routes, so more specific routes should be placed above less specific routes. In our
|
||
configuration above, the routes with a static path are listed first, followed by an empty path route,
|
||
that matches as the default route. The wildcard route is listed last as it's the most generic route and should be
|
||
matched **only** if no other routes are matched first.
|
||
|
||
**这些路由的定义顺序**是故意如此设计的。路由器使用**先匹配者优先**的策略来匹配路由,所以,具体路由应该放在通用路由的前面。在上面的配置中,带静态路径的路由被放在了前面,后面是空路径路由,因此它会作为默认路由。而通配符路由被放在最后面,这是因为它是最通用的路由,应该**只在**前面找不到其它能匹配的路由时才匹配它。
|
||
|
||
:marked
|
||
### Router Outlet
|
||
|
||
### 路由插座
|
||
|
||
Given this configuration, when the browser URL for this application becomes `/heroes`,
|
||
the router matches that URL to the `Route` path `/heroes` and displays the `HeroListComponent`
|
||
in a **`RouterOutlet`** that we've placed in the host view's HTML.
|
||
|
||
有了这份配置,当本应用在浏览器中的URL变为`/heroes`时,路由器就会匹配到`path`为`heroes`的`Route`,并在宿主视图中的**`RouterOutlet`**中显示`HeroListComponent`组件。
|
||
|
||
code-example(language="html").
|
||
<!-- Routed views go here -->
|
||
<router-outlet></router-outlet>
|
||
|
||
:marked
|
||
### Router Links
|
||
|
||
### 路由器链接
|
||
|
||
Now we have routes configured and a place to render them, but
|
||
how do we navigate? The URL could arrive directly from the browser address bar.
|
||
But most of the time we navigate as a result of some user action such as the click of
|
||
an anchor tag.
|
||
|
||
现在,我们已经有了配置好的一些路由,还找到了渲染它们的地方,但又该如何导航到它呢?固然,从浏览器的地址栏直接输入URL也能做到,但是大多数情况下,导航是某些用户操作的结果,比如点击一个A标签。
|
||
|
||
We add a **`RouterLink`** directive to the anchor tag. Since
|
||
we know our link doesn't contain any dynamic information, we can use a one-time binding to our route *path*.
|
||
|
||
我们往A标签上添加了**`RouterLink`**指令。由于我们知道链接中不包含任何动态信息,因此我们使用一次性绑定的方式把它绑定到我们路由中的*path*值。
|
||
|
||
If our `RouterLink` needed to be more dynamic we could bind to a template expression that
|
||
returns an array of route link parameters (the **link parameters array**). The router ultimately resolves that array
|
||
into a URL and a component view.
|
||
|
||
如果`RouterLink`需要动态信息,我们就可以把它绑定到一个能返回路由链接数组(**链接参数数组**)的模板表达式上。
|
||
路由器最终会把此数组解析成一个URL和一个组件视图。
|
||
|
||
We also add a **`RouterLinkActive`** directive to each anchor tag to add or remove CSS classes to the
|
||
element when the associated *RouterLink* becomes active. The directive can be added directly on the element
|
||
or on its parent element.
|
||
|
||
我们还往每个A标签上添加了一个**`RouterLinkActive`**指令,用于在相关的*RouterLink*被激活时为所在元素添加或移除CSS类。
|
||
该指令可以直接添加到该元素上,也可以添加到其父元素上。
|
||
|
||
We see such bindings in the following `AppComponent` template:
|
||
|
||
我们会在下面的`AppComponent`模板中看到类似这样的绑定:
|
||
|
||
+makeExcerpt('app/app.component.1.ts', 'template', '')
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
We're adding two anchor tags with `RouterLink` and `RouterLinkActive` directives.
|
||
We bind each `RouterLink` to a string containing the path of a route.
|
||
'/crisis-center' and '/heroes' are the paths of the `Routes` we configured above.
|
||
|
||
我们用`RouterLink`指令添加了两个带`RouterLink`和`RouterLinkActive`指令的A标签。每个`RouterLink`都绑定到了一个包含路由路径的字符串上。
|
||
'/crisis-center'和'/heroes'都是我们前面配置过的`Routes`中的路径。
|
||
|
||
We'll learn to write link expressions — and why they are arrays —
|
||
[later](#link-parameters-array) in the chapter.
|
||
|
||
在本章的[后面](#link-parameters-array),我们还将学到如何写链接表达式,以及了解它们为什么是数组。
|
||
|
||
We define `active` as the CSS class we want toggled to each `RouterLink` when they become
|
||
the current route using the `RouterLinkActive ` directive. We could add multiple classes to
|
||
the `RouterLink` if we so desired.
|
||
|
||
利用`RouterLinkActive`指令,我们把`active`作为当路由被激活时为`RouterLink`切换的CSS类。
|
||
必要时,还可以为`RouterLink`添加多个类。
|
||
|
||
:marked
|
||
### Router State
|
||
|
||
### 路由器状态
|
||
|
||
After the end of each successful navigation lifecycle, the router builds a tree of `ActivatedRoute` objects
|
||
that make up the current state of the router. We can access the current `RouterState` from anywhere in our
|
||
application using the `Router` service and the `routerState` property.
|
||
|
||
在导航时的每个生命周期成功完成时,路由器会构建出一个`ActivatedRoute`组成的树,它表示路由器的当前状态。
|
||
我们可以在应用中的任何地方用`Router`服务及其`routerState`属性来访问当前的`RouterState`值。
|
||
|
||
Each `ActivatedRoute` in the `RouterState` provides methods to traverse up and down the route tree
|
||
to get information we may need from parent, child and sibling routes.
|
||
|
||
路由器状态为我们提供了从任意激活路由开始向上或向下遍历路由树的一种方式,以获得关于父、子、兄弟路由的信息。
|
||
|
||
:marked
|
||
### Let's summarize
|
||
|
||
### 总结一下
|
||
|
||
The application is provided with a configured router.
|
||
The component has a `RouterOutlet` where it can display views produced by the router.
|
||
It has `RouterLink`s that users can click to navigate via the router.
|
||
|
||
为应用提供了一个配置过的路由器。
|
||
组件中有一个`RouterOutlet`,它能显示路由器所生成的视图。
|
||
它还有一些`RouterLink`,用户可以点击它们,来通过路由器进行导航。
|
||
|
||
Here are the key *Router* terms and their meanings:
|
||
|
||
下面是一些*路由器*中的关键词汇及其含义:
|
||
|
||
table
|
||
tr
|
||
th
|
||
p Router Part
|
||
|
||
p 路由器部件
|
||
|
||
th
|
||
p Meaning
|
||
|
||
p 含义
|
||
|
||
tr
|
||
td
|
||
p <code>Router</code>
|
||
|
||
p <code>Router</code>(路由器)
|
||
|
||
td
|
||
p.
|
||
Displays the application component for the active URL.
|
||
Manages navigation from one component to the next.
|
||
|
||
p 为激活的URL显示应用组件。管理从一个组件到另一个组件的导航
|
||
|
||
tr
|
||
td
|
||
p <code>RouterModule</code>
|
||
|
||
p <code>RouterModule</code>(路由器模块)
|
||
|
||
td
|
||
p.
|
||
A separate Angular module that provides the necessary service providers
|
||
and directives for navigating through application views.
|
||
|
||
p 一个独立的Angular模块,用于提供所需的服务提供商,以及用来在应用视图之间进行导航的指令。
|
||
|
||
tr
|
||
td
|
||
p <code>Routes</code>
|
||
p <code>Routes</code>(路由数组)
|
||
td
|
||
p.
|
||
Defines an array of Routes, each mapping a URL path to a component.
|
||
p 定义了一个路由数组,每一个都会把一个URL路径映射到一个组件。
|
||
tr
|
||
td
|
||
p <code>Route</code>
|
||
|
||
p <code>Route</code>(路由)
|
||
|
||
td
|
||
p.
|
||
Defines how the router should navigate to a component based on a URL pattern.
|
||
Most routes consist of a path and a component type.
|
||
|
||
p 定义路由器该如何根据URL模式(pattern)来导航到组件。大多数路由都由路径和组件类构成。
|
||
|
||
tr
|
||
td
|
||
p <code>RouterOutlet</code>
|
||
|
||
p <code>RouterOutlet</code>(路由插座)
|
||
|
||
td
|
||
p.
|
||
The directive (<code><router-outlet></code>) that marks where the router should display a view.
|
||
|
||
p 该指令(<code><router-outlet></code>)用来标记出路由器该在哪里显示视图。
|
||
|
||
tr
|
||
td
|
||
p <code>RouterLink</code>
|
||
|
||
p <code>RouterLink</code>(路由链接)
|
||
|
||
td
|
||
p.
|
||
The directive for binding a clickable HTML element to
|
||
a route. Clicking an anchor tag with a <code>routerLink</code> directive
|
||
that is bound to a <i>Link Parameters Array</i> triggers a navigation.
|
||
|
||
p.
|
||
该指令用来把一个可点击的HTML元素绑定到路由。
|
||
点击带有绑定到<i>字符串</i>或<i>链接参数数组</i>的<code>routerLink</code>指令的A标签就会触发一次导航。
|
||
|
||
tr
|
||
td
|
||
p <code>RouterLinkActive</code>
|
||
|
||
p <code>RouterLinkActive</code>(活动路由链接)
|
||
|
||
td
|
||
p.
|
||
The directive for adding/removing classes from an HTML element when an associated
|
||
routerLink contained on or inside the element becomes active/inactive.
|
||
p.
|
||
当HTML元素上或元素内的routerLink变为激活或非激活状态时,该指令为这个HTML元素添加或移除CSS类。
|
||
tr
|
||
td
|
||
p <code>ActivatedRoute</code>
|
||
p <code>ActivatedRoute</code>(激活的路由)
|
||
td
|
||
p.
|
||
A service that is provided to each route component that contains route specific
|
||
information such as route parameters, static data, resolve data, global query params and the global fragment.
|
||
p 为每个路由组件提供提供的一个服务,它包含特定于路由的信息,比如路由参数、静态数据、解析数据、全局查询参数和全局碎片(fragment)。
|
||
td
|
||
p <code>RouterState</code>
|
||
|
||
p <code>RouterState</code>(路由器状态)
|
||
|
||
td
|
||
p.
|
||
The current state of the router including a tree of the currently activated
|
||
routes in our application along convenience methods for traversing the route tree.
|
||
p 路由器的当前状态包含了一棵由程序中激活的路由构成的树。它包含一些用于遍历路由树的快捷方法。
|
||
tr
|
||
td
|
||
p <code><i>Link Parameters Array</i></code>
|
||
|
||
p <code><i>链接参数数组</i></code>
|
||
|
||
td
|
||
p.
|
||
An array that the router interprets into a routing instruction.
|
||
We can bind a <code>RouterLink</code> to that array or pass the array as an argument to
|
||
the <code>Router.navigate</code> method.
|
||
|
||
p 这个数组会被路由器解释成一个路由操作指南。我们可以把一个<code>RouterLink</code>绑定到该数组,或者把它作为参数传给<code>Router.navigate</code>方法。
|
||
|
||
tr
|
||
td
|
||
p <code><i>Routing Component</i></code>
|
||
|
||
p <code><i>路由组件</i></code>
|
||
|
||
td
|
||
p An Angular component with a <code>RouterOutlet</code> that displays views based on router navigations.
|
||
|
||
p 一个带有<code>RouterOutlet</code>的Angular组件,它根据路由器的导航来显示相应的视图。
|
||
|
||
:marked
|
||
We've barely touched the surface of the router and its capabilities.
|
||
|
||
我们已经对路由器及其能力有了肤浅的了解。
|
||
|
||
The following detail sections describe a sample routing application
|
||
as it evolves over a sequence of milestones.
|
||
We strongly recommend taking the time to read and understand this story.
|
||
|
||
下面的详情区描述了一个带路由的范例应用,它经过一系列里程碑一步步演进。我们强烈建议你花点时间阅读并理解这个过程。
|
||
|
||
.l-main-section
|
||
|
||
:marked
|
||
## The Sample Application
|
||
|
||
## 范例应用
|
||
|
||
We have an application in mind as we move from milestone to milestone.
|
||
|
||
从一个里程碑前进到另一个里程碑,我们总是有一个应用程序在心中。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
While we make incremental progress toward the ultimate sample application, this chapter is not a tutorial.
|
||
We discuss code and design decisions pertinent to routing and application design.
|
||
We gloss over everything in between.
|
||
|
||
虽然我们会渐进式的前进到最终的范例应用,但本章并不是一个教程。
|
||
我们讨论路由和应用设计有关的代码和设计决策,并在这期间,处理遇到的所有问题。
|
||
|
||
The full source is available in the <live-example></live-example>.
|
||
|
||
完整代码可以在<live-example>在线例子</live-example>中找到。
|
||
|
||
:marked
|
||
Our client is the Hero Employment Agency.
|
||
Heroes need work and The Agency finds Crises for them to solve.
|
||
|
||
我们的客户是“英雄管理局”。
|
||
英雄们需要找工作,而“英雄管理局”为它们寻找待解决的危机。
|
||
|
||
The application has three main feature areas:
|
||
|
||
本应用分为三个主要的特性区:
|
||
|
||
1. A *Crisis Center* where we maintain the list of crises for assignment to heroes.
|
||
|
||
1. 一个*危机中心*区,它维护一个危机列表,用来分派给英雄。
|
||
|
||
1. A *Heroes* area where we maintain the list of heroes employed by The Agency.
|
||
|
||
1. 一个*英雄*区,它维护由英雄管理局雇佣的英雄列表。
|
||
|
||
1. An *Admin* area where we manage the list of crises and heroes displayed.
|
||
|
||
1. 一个*管理*区,用来维护该中心危机列表和雇佣英雄的列表。
|
||
|
||
Run the <live-example></live-example>.
|
||
It opens in the *Crisis Center*. We'll come back to that.
|
||
|
||
运行<live-example>在线例子</live-example>。它打开了*危机中心*,一会儿我们就会回到那里。
|
||
|
||
Click the *Heroes* link. We're presented with a list of Heroes.
|
||
|
||
点击*英雄*链接,我们就会展现出一个英雄列表。
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/hero-list.png' alt="Hero List" width="250")
|
||
|
||
:marked
|
||
We select one and the application takes us to a hero editing screen.
|
||
|
||
选择其中之一,该应用就会把我们带到此英雄的编辑页面。
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/hero-detail.png' alt="Crisis Center Detail" width="250")
|
||
|
||
:marked
|
||
Our changes take effect immediately. We click the "Back" button and the
|
||
app returns us to the Heroes list.
|
||
|
||
修改会立即见效。我们再点击“后退”按钮,该应用又把我们带回了英雄列表页。
|
||
|
||
We could have clicked the browser's back button instead.
|
||
That would have returned us to the Heroes List as well.
|
||
Angular app navigation updates the browser history as normal web navigation does.
|
||
|
||
另外我们也可以点击浏览器本身的后退按钮,这样也同样会回到英雄列表页。
|
||
在Angular应用中导航也会和标准的Web导航一样更新浏览器中的历史。
|
||
|
||
Now click the *Crisis Center* link. We go to the *Crisis Center* and its list of ongoing crises.
|
||
|
||
现在,点击*危机中心*链接,我们就会前往*危机中心*页,那里列出了待处理的危机。
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/crisis-center-list.png' alt="Crisis Center List" )
|
||
|
||
:marked
|
||
We select one and the application takes us to a crisis editing screen.
|
||
|
||
选择其中之一,该应用就会把我们带到此危机的编辑页面。
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/crisis-center-detail.png' alt="Crisis Center Detail")
|
||
|
||
:marked
|
||
This is a bit different from the *Hero Detail*. *Hero Detail* saves the changes as we type.
|
||
In *Crisis Detail* our changes are temporary until we either save or discard them by pressing the "Save" or "Cancel" buttons.
|
||
Both buttons navigate back to the *Crisis Center* and its list of crises.
|
||
|
||
这和*英雄详情*页略有不同。*英雄详情*会立即保存我们所做的更改。
|
||
而*危机详情*页中,我们的更改都是临时的 —— 除非按“保存”按钮保存它们,或者按“取消”按钮放弃它们。
|
||
这两个按钮都会导航回*危机中心*,显示危机列表。
|
||
|
||
Suppose we click a crisis, make a change, but ***do not click either button***.
|
||
Maybe we click the browser back button instead. Maybe we click the "Heroes" link.
|
||
|
||
假如我们点击一个危机、做了修改,但是***没有点击任何按钮***,可能点击了浏览器的后退按钮,也可能点击了“英雄”链接。
|
||
|
||
Do either. Up pops a dialog box.
|
||
|
||
无论哪种情况,我们都应该弹出一个对话框。
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/confirm-dialog.png' alt="Confirm Dialog" width="300")
|
||
|
||
:marked
|
||
We can say "OK" and lose our changes or click "Cancel" and continue editing.
|
||
|
||
我们可以回答“确定”以放弃这些更改,或者回答“取消”来继续编辑。
|
||
|
||
The router supports a `CanDeactivate` guard that gives us a chance to clean-up
|
||
or ask the user's permission before navigating away from the current view.
|
||
|
||
路由器支持`CanDeactivate`守卫函数,它让我们有机会进行清理工作,或在离开当前视图之前征求用户的许可。
|
||
|
||
Here we see an entire user session that touches all of these features.
|
||
|
||
这里,我们看到的是一次完整的用户会话,它涉及到了所有这些我们要讲的特性。
|
||
|
||
<a id="full-app-demo"></a>
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/router-anim.gif' alt="App in action" )
|
||
|
||
:marked
|
||
Here's a diagram of all application routing options:
|
||
|
||
下面是该应用中所有可选路由导航图:
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/complete-nav.png' alt="Navigation diagram" )
|
||
|
||
:marked
|
||
This app illustrates the router features we'll cover in this chapter
|
||
|
||
该应用展示了本章中涉及到的所有路由器特性:
|
||
|
||
* organizing the application features into modules
|
||
|
||
* 把应用的特性组织成模块
|
||
|
||
* navigating to a component (*Heroes* link to "Heroes List")
|
||
|
||
* 导航到一个组件(*英雄*链接到“英雄列表”)
|
||
|
||
* including a route parameter (passing the Hero `id` while routing to the "Hero Detail")
|
||
|
||
* 包含一个路由参数(当路由到“英雄详情”时传入该英雄的`id`)
|
||
|
||
* child routes (the *Crisis Center* has its own routes)
|
||
|
||
* 子路由(*危机中心*有一组自己的路由)
|
||
|
||
* the `CanActivate` guard (checking route access)
|
||
|
||
* `CanActivate`守卫(检查路由访问权)
|
||
|
||
* the `CanActivateChild` guard (checking child route access)
|
||
|
||
* `CanActivateChild`守卫(检查子路由访问权)
|
||
|
||
* the `CanDeactivate` guard (ask permission to discard unsaved changes)
|
||
|
||
* `CanDeactivate`守卫(询问是否丢弃未保存的修改)
|
||
|
||
* the `Resolve` guard (pre-fetching route data)
|
||
|
||
* `Resolve`守卫(预先获取路由数据)
|
||
|
||
* lazy loading feature modules
|
||
|
||
* 惰性加载特性路由
|
||
|
||
* the `CanLoad` guard (check before loading feature module assets)
|
||
|
||
* `CanLoad`守卫(在加载特性模块之前进行检查)
|
||
|
||
<a id="getting-started"></a>
|
||
|
||
.l-main-section
|
||
|
||
:marked
|
||
## Milestone #1: Getting Started with the Router
|
||
|
||
## 里程碑#1:从路由器开始
|
||
|
||
Let's begin with a simple version of the app that navigates between two empty views.
|
||
|
||
我们从该应用的一个简化版开始,它在两个空视图之间导航。
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/router-1-anim.gif' alt="App in action" )
|
||
|
||
a#base-href
|
||
:marked
|
||
### Set the *<base href>*
|
||
|
||
### 设置*<base href>*
|
||
|
||
The Router uses the browser's
|
||
[history.pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries)
|
||
for navigation. Thanks to `pushState`, we can make our in-app URL paths look the way we want them to
|
||
look, e.g. `localhost:3000/crisis-center`. Our in-app URLs can be indistinguishable from server URLs.
|
||
|
||
路由器使用浏览器的[history.pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries)进行导航。
|
||
感谢`pushState`!有了它,我们就能按所期望的样子来显示应用内部的URL路径,比如:`localhost:3000/crisis-center`。虽然我们使用的全部是客户端合成的视图,但应用内部的这些URL看起来和来自服务器的没有什么不同。
|
||
|
||
Modern HTML 5 browsers were the first to support `pushState` which is why many people refer to these URLs as
|
||
"HTML 5 style" URLs.
|
||
|
||
现代HTML 5浏览器是最早支持`pushState`的,这也就是很多人喜欢把这种URL称作“HTML 5风格的”URL的原因。
|
||
|
||
We must **add a [<base href> element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag**
|
||
to the `index.html` to make `pushState` routing work.
|
||
The browser also needs the base `href` value to prefix *relative* URLs when downloading and linking to
|
||
css files, scripts, and images.
|
||
|
||
我们必须往`index.html`中**添加一个[<base href>元素](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)标签**来让`pushState`路由正常工作。
|
||
浏览器也需要这个`base`的`href`值,以便在下载和链接css文件、脚本和图片时,为那些*相对*URL加前缀。
|
||
|
||
Add the base element just after the `<head>` tag.
|
||
If the `app` folder is the application root, as it is for our application,
|
||
set the `href` value in **`index.html`** *exactly* as shown here.
|
||
|
||
在`<head>`标签中的最前面添加base元素。如果`app`文件夹是应用的根目录(就像我们这个一样),那么就像下面展示的这样在`index.html`中设置`href`的值:
|
||
|
||
+makeExcerpt('index.1.html', 'base-href')
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
HTML 5 style navigation is the Router default.
|
||
Learn why "HTML 5" style is preferred, how to adjust its behavior, and how to switch to the
|
||
older hash (#) style if necessary in the [Browser URL Styles](#browser-url-styles) appendix below.
|
||
|
||
HTML 5风格的导航是路由器的默认值。请到下面的附录[浏览器URL风格](#browser-url-styles)中学习为什么首选“HTML 5”风格、如何调整它的行为,以及如何在必要时切换回老式的hash(#)风格。
|
||
|
||
:marked
|
||
.l-sub-section
|
||
|
||
:marked
|
||
#### Live example note
|
||
|
||
#### 在线例子说明
|
||
|
||
We have to get tricky when we run the live example because the host service sets
|
||
the application base address dynamically. That's why we replace the `<base href...>` with a
|
||
script that writes a `<base>` tag on the fly to match.
|
||
|
||
当我们运行在线例子时,我们不得不耍一点小花招,因为在线例子的宿主服务会动态设置应用的基地址。这就是为什么我们要把`<base href...>`替换成了一段脚本,用来动态写入`<base>`标签来适应这种情况。
|
||
|
||
code-example(format="")
|
||
<script>document.write('<base href="' + document.location + '" />');</script>
|
||
|
||
:marked
|
||
We should only need this trick for the live example, not production code.
|
||
|
||
我们只应该在在线例子这种情况下使用这种小花招,不要把它用到产品的正式代码中。
|
||
|
||
:marked
|
||
### Configure the routes for the Router
|
||
|
||
### 为路由器配置一些路由
|
||
|
||
|
||
We begin by importing some symbols from the router library.
|
||
|
||
我们先从路由库中导入一些符号。
|
||
|
||
The Router is in its own `@angular/router` package.
|
||
It's not part of the Angular core. The router is an optional service because not all applications
|
||
need routing and, depending on your requirements, you may need a different routing library.
|
||
|
||
路由器在它自己的`@angular/router`包中。
|
||
它不是Angular 2内核的一部分。该路由器是可选的服务,这是因为并不是所有应用都需要路由,并且,如果需要,你还可能需要另外的路由库。
|
||
|
||
We teach our router how to navigate by configuring it with routes.
|
||
|
||
通过一些路由来配置路由器,我们可以教它如何进行导航。
|
||
|
||
a#route-config
|
||
|
||
h4#define-routes Define routes
|
||
|
||
h4#define-routes 定义一些路由
|
||
|
||
:marked
|
||
A router must be configured with a list of route definitions.
|
||
|
||
路由器必须用“路由定义”的列表进行配置。
|
||
|
||
Our first configuration defines an array of two routes with simple paths leading to the
|
||
`CrisisListComponent` and `HeroListComponent` components.
|
||
|
||
我们的第一个配置中定义了由两个路由构成的数组,它们分别通过路径(path)导航到了`CrisisListComponent`和`HeroListComponent`组件。
|
||
|
||
Each definition translates to a [Route](../api/router/index/Route-interface.html) object which has a
|
||
`path`, the URL path segment for this route, and a
|
||
`component`, the component associated with this route.
|
||
|
||
每个定义都被翻译成了一个[Route](../api/router/index/Route-interface.html)对象。该对象有一个`path`字段,表示该路由中的URL路径部分,和一个`component`字段,表示与该路由相关联的组件。
|
||
|
||
The router draws upon its registry of such route definitions when the browser URL changes
|
||
or when our code tells the router to navigate along a route path.
|
||
|
||
当浏览器的URL变化时或在代码中告诉路由器导航到一个路径时,路由器就会翻出它用来保存这些路由定义的注册表。
|
||
|
||
In plain English, we might say of the first route:
|
||
|
||
直白的说,我们可以这样解释第一个路由:
|
||
|
||
* *When the browser's location URL changes to match the path segment `/crisis-center`, create or retrieve an instance of
|
||
the `CrisisListComponent` and display its view.*
|
||
|
||
* *当浏览器地址栏的URL变化时,如果它匹配上了路径部分`/crisis-center`*,路由器就会创建或获取一个`CrisisListComponent`的实例,并显示它的视图。
|
||
|
||
* *When the application requests navigation to the path `/crisis-center`, create or retrieve an instance of
|
||
the `CrisisListComponent`, display its view, and update the browser's address location and history with the URL
|
||
for that path.*
|
||
|
||
* **当应用程序请求导航到路径`/crisis-center`时,创建或者取回一个`CrisisListComponent`的实例,显示它的视图,并将该路径更新到浏览器地址栏和历史。**
|
||
|
||
:marked
|
||
Here is our first configuration. We pass the array of routes to the `RouterModule.forRoot` method
|
||
which returns a module containing the configured `Router` service provider ... and some other,
|
||
unseen providers that the routing library requires. Once our application is bootstrapped, the `Router`
|
||
will perform the initial navigation based on the current browser URL.
|
||
|
||
下面是第一个配置。我们将路由数组传递到`RouterModule.forRoot`方法,该方法返回一个包含已配置的`Router`服务提供商模块和一些其它路由包需要的服务提供商。应用启动时,`Router`将在当前浏览器URL的基础上进行初始导航。
|
||
|
||
+makeExcerpt('app/app.module.1.ts')
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Adding the configured `RouterModule` to the `AppModule` is sufficient for simple route configurations.
|
||
As our application grows, we'll want to refactor our routing configuration into a separate file
|
||
and create a **[Routing Module](#routing-module)**, a special type of `Service Module` dedicating for the purpose
|
||
of routing in feature modules.
|
||
|
||
作为简单的路由配置,将添加配置好的`RouterModule`到`AppModule`中就足够了。
|
||
随着应用的成长,我们将需要将路由配置重构到单独的文件,并创建**[路由模块](#routing-module)** - 一种特别的、专门为特征模块的路由器服务的**服务模块**。
|
||
|
||
:marked
|
||
Providing the `RouterModule` in our `AppModule` makes the Router available everywhere in our application.
|
||
|
||
在`AppModule`中提供`RouterModule`,让该路由器在应用的任何地方都能被使用。
|
||
|
||
|
||
h3#shell The <i>AppComponent</i> shell
|
||
|
||
h3#shell 壳组件<i>AppComponent</i>
|
||
|
||
:marked
|
||
The root `AppComponent` is the application shell. It has a title at the top, a navigation bar with two links,
|
||
and a *Router Outlet* at the bottom where the router swaps views on and off the page. Here's what we mean:
|
||
|
||
根组件`AppComponent`是本应用的壳。它在顶部有一个标题、一个带两个链接的导航条,在底部有一个*路由器插座*,路由器会在它所指定的位置上把视图切入或调出页面。就像下图中所标出的:
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/shell-and-outlet.png' alt="Shell" width="300" )
|
||
|
||
a#shell-template
|
||
|
||
:marked
|
||
The corresponding component template looks like this:
|
||
|
||
该组件所对应的模板是这样的:
|
||
|
||
+makeExcerpt('app/app.component.1.ts', 'template', '')
|
||
|
||
a#router-outlet
|
||
:marked
|
||
### *RouterOutlet*
|
||
|
||
`RouterOutlet` is a component from the router library.
|
||
The router displays views within the bounds of the `<router-outlet>` tags.
|
||
|
||
`RouterOutlet`是一个来自路由库的组件。
|
||
路由器会在`<router-outlet>`标签中显示视图。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
A template may hold exactly one ***unnamed*** `<router-outlet>`.
|
||
The router supports multiple *named* outlets, a feature we'll cover in future.
|
||
|
||
一个模板中只能有一个***未命名的***`<router-outlet>`。
|
||
但路由器可以支持多个*命名的*插座(outlet),将来我们会涉及到这部分特性。
|
||
|
||
a#router-link
|
||
:marked
|
||
### *RouterLink* binding
|
||
|
||
Above the outlet, within the anchor tags, we see [Property Bindings](template-syntax.html#property-binding) to
|
||
the `RouterLink` directive that look like `routerLink="..."`. We use the `RouterLink` from the router library.
|
||
|
||
在插座上方的A标签中,有一个绑定`RouterLink`指令的[属性绑定](template-syntax.html#property-binding),就像这样:`routerLink="..."`。我们从路由库中导入了`RouterLink`。
|
||
|
||
The links in this example each have a string path, the path of a route that
|
||
we configured earlier. We don't have route parameters yet.
|
||
|
||
例子中的每个链接都有一个字符串型的路径,也就是我们以前配置过的路由路径,但还没有指定路由参数。
|
||
|
||
We can also add more contextual information to our `RouterLink` by providing query string parameters
|
||
or a URL fragment for jumping to different areas on our page. Query string parameters
|
||
are provided through the `[queryParams]` binding which takes an object (e.g. `{ name: 'value' }`), while the URL fragment
|
||
takes a single value bound to the `[fragment]` input binding.
|
||
|
||
我们还可以通过提供查询字符串参数为`RouterLink`提供更多情境信息,或提供一个URL片段(Fragment或hash)来跳转到本页面中的其它区域。
|
||
查询字符串可以由`[queryParams]`绑定来提供,它需要一个对象型参数(如`{ name: 'value' }`),而URL片段需要一个绑定到`[fragment]`的单一值。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
Learn about the how we can also use the **link parameters array** in the [appendix below](#link-parameters-array).
|
||
|
||
还可以到[后面的附录](#link-parameters-array)中学习如何使用**链接参数数组**。
|
||
|
||
a#router-link-active
|
||
|
||
h3#router-link <i>RouterLinkActive</i> binding
|
||
|
||
h3#router-link <i>RouterLinkActive</i>绑定
|
||
|
||
:marked
|
||
On each anchor tag, we also see [Property Bindings](template-syntax.html#property-binding) to
|
||
the `RouterLinkActive` directive that look like `routerLinkActive="..."`.
|
||
|
||
每个A标签还有一个到`RouterLinkActive`指令的[属性绑定](template-syntax.html#property-binding),就像`routerLinkActive="..."`。
|
||
|
||
The template expression to the right of the equals (=) contains our space-delimited string of CSS classes.
|
||
We can also bind to the `RouterLinkActive` directive using an array of classes
|
||
such as `[routerLinkActive]="['...']"`.
|
||
|
||
等号(=)右侧的模板表达式包含用空格分隔的一些CSS类。我们还可以把`RouterLinkActive`指令绑定到一个CSS类组成的数组,如`[routerLinkActive]="['...']"`。
|
||
|
||
The `RouterLinkActive` directive toggles css classes for active `RouterLink`s based on the current `RouterState`.
|
||
This cascades down through each level in our route tree, so parent and child router links can be active at the same time.
|
||
To override this behavior, we can bind to the `[routerLinkActiveOptions]` input binding with the `{ exact: true }` expression.
|
||
By using `{ exact: true }`, a given `RouterLink` will only be active if its URL is an exact match to the current URL.
|
||
|
||
`RouterLinkActive`指令会基于当前的`RouterState`对象来为激活的`RouterLink`切换CSS类。
|
||
这会一直沿着路由树往下进行级联处理,所以父路由链接和子路由链接可能会同时激活。
|
||
要改变这种行为,可以把`[routerLinkActiveOptions]`绑定到`{exact: true}`表达式。
|
||
如果使用了`{ exact: true }`,那么只有在其URL与当前URL精确匹配时才会激活指定的`RouterLink`。
|
||
|
||
h3#router-directives <i>Router Directives</i>
|
||
|
||
h3#router-directives <i>路由器指令集</i>
|
||
|
||
:marked
|
||
`RouterLink`, `RouterLinkActive` and `RouterOutlet` are directives provided by the Angular `RouterModule` package.
|
||
They are readily available for us to use in our template.
|
||
|
||
`RouterLink`、`RouterLinkActive`和`RouterOutlet`是由`RouterModule`包提供的指令。
|
||
现在它已经可用于我们自己的模板中。
|
||
:marked
|
||
The current state of `app.component.ts` looks like this:
|
||
|
||
`app.component.ts`目前看起来是这样的:
|
||
|
||
+makeExcerpt('app/app.component.1.ts')
|
||
|
||
:marked
|
||
### "Getting Started" wrap-up
|
||
|
||
### “起步阶段”总结
|
||
|
||
We've got a very basic, navigating app, one that can switch between two views
|
||
when the user clicks a link.
|
||
|
||
我们得到了一个非常基本的、带导航的应用,当用户点击链接时,它能在两个视图之间切换。
|
||
|
||
We've learned how to
|
||
|
||
我们已经学会了如何:
|
||
|
||
* load the router library
|
||
|
||
* 加载路由库
|
||
|
||
* add a nav bar to the shell template with anchor tags, `routerLink` and `routerLinkActive` directives
|
||
|
||
* 往壳组件的模板中添加一个导航条,导航条中有一些A标签、`routerLink`指令和`routerLinkActive`指令
|
||
|
||
* add a `router-outlet` to the shell template where views will be displayed
|
||
|
||
* 往壳组件的模板中添加一个`router-outlet`指令,视图将会被显示在那里
|
||
|
||
* configure the router module with `RouterModule.forRoot`
|
||
|
||
* 用`RouterModule.forRoot`配置路由器模块
|
||
|
||
* set the router to compose "HTML 5" browser URLs.
|
||
|
||
* 设置路由器,使其合成“HTML 5”模式的浏览器URL。
|
||
|
||
The rest of the starter app is mundane, with little interest from a router perspective.
|
||
Here are the details for readers inclined to build the sample through to this milestone.
|
||
|
||
这个初学者应用的其它部分有点平淡无奇,从路由器的角度来看也很平淡。
|
||
如果你还是倾向于在这个里程碑里构建它们,参见下面的构建详情。
|
||
|
||
Our starter app's structure looks like this:
|
||
|
||
这个初学者应用的结构看起来是这样的:
|
||
|
||
.filetree
|
||
.file router-sample
|
||
.children
|
||
.file app
|
||
.children
|
||
.file app.component.ts
|
||
.file app.module.ts
|
||
.file crisis-list.component.ts
|
||
.file hero-list.component.ts
|
||
.file main.ts
|
||
.file node_modules ...
|
||
.file index.html
|
||
.file package.json
|
||
.file styles.css
|
||
.file tsconfig.json
|
||
:marked
|
||
Here are the files discussed in this milestone
|
||
|
||
下面是当前里程碑中讨论过的文件列表:
|
||
|
||
+makeTabs(
|
||
`router/ts/app/app.component.1.ts,
|
||
router/ts/app/app.module.1.ts,
|
||
router/ts/app/main.ts,
|
||
router/ts/app/hero-list.component.ts,
|
||
router/ts/app/crisis-list.component.ts,
|
||
router/ts/index.html`,
|
||
',,,,',
|
||
`app.component.ts,
|
||
app.module.ts,
|
||
main.ts,
|
||
hero-list.component.ts,
|
||
crisis-list.component.ts,
|
||
index.html`)
|
||
|
||
.l-main-section#routing-module
|
||
:marked
|
||
## Milestone #2: The *Routing Module*
|
||
|
||
## 里程碑 #2:**路由模块**
|
||
|
||
In our initial route configuration, we provided a simple setup with two routes used
|
||
to configure our application for routing. This is perfectly fine for simple routing.
|
||
As our application grows and we make use of more *Router* features, such as guards,
|
||
resolvers, and child routing, we'll naturally want to refactor our routing. We
|
||
recommend moving the routing into a separate file using a special-purpose
|
||
service called a *Routing Module*.
|
||
|
||
在原始的路由配置中,我们提供了仅有两个路由的简单配置来设置应用的路由。对于简单的路由,这没有问题。
|
||
随着应用的成长,我们使用更多**路由器**特征,比如守卫、解析器和子路由等,我们很自然想要重构路由。
|
||
建议将路由移到单独的文件,使用专门目的的服务,叫做**路由模块**。
|
||
|
||
The **Routing Module**
|
||
|
||
**路由模块**
|
||
|
||
* separates our routing concerns from our feature module
|
||
|
||
* 分离路由与特征模块
|
||
|
||
* provides a module to replace or remove when testing our feature module
|
||
|
||
* 测试特征模块时,可以替换或移除路由模块
|
||
|
||
* provides a common place for require routing service providers including guards and resolvers
|
||
|
||
* 为路由服务提供商(包括守卫和解析器等)提供一个共同的地方
|
||
|
||
* is **not** concerned with feature [module declarations](../cookbook/ngmodule-faq.html#!#routing-module)
|
||
|
||
* 与特征的[声明](../cookbook/ngmodule-faq.html#!#routing-module)**无关**
|
||
|
||
:marked
|
||
### Refactor routing into a module
|
||
|
||
### 将路由重构为模块
|
||
|
||
We'll create a file named `app-routing.module.ts` in our `/app` folder to
|
||
contain our `Routing Module`. The routing module will import our `RouterModule` tokens
|
||
and configure our routes. We'll follow the convention of our filename and name
|
||
the Angular module `AppRoutingModule`.
|
||
|
||
在`/app`目录新建一个文件,名叫`app-routing.module.ts`。路由模块将导入`RouterModule`令牌,并配置路由。
|
||
我们遵循文件名约定,并命名Angular模块为`AppRoutingModule`。
|
||
|
||
We import the `CrisisListComponent` and the `HeroListComponent` components
|
||
just like we did in the `app.module.ts`. Then we'll move the `Router` imports
|
||
and routing configuration including `RouterModule.forRoot` into our routing module.
|
||
|
||
和`app.module.ts`中一样,导入`CrisisListComponent`和`HeroListComponent`组件。
|
||
然后在路由模块中导入`Router`和路由配置(`RouterModule.forRoot`)。
|
||
|
||
We'll also export the `AppRoutingModule` so we can add it to our `AppModule` imports.
|
||
|
||
同时导出`AppRoutingModule`,这样我们可以将它添加到`AppModule`的`imports`中。
|
||
|
||
Our last step is to re-export the `RouterModule`. By re-exporting the `RouterModule`,
|
||
our feature module will be provided with the `Router Directives` when using our `Routing Module`.
|
||
|
||
最后,重新导出`RouterModule`,这样,特征模块在使用**路由模块**时,将获得**路由指令**。
|
||
|
||
Here is our first `Routing Module`:
|
||
|
||
下面是首个**路由模块**:
|
||
|
||
+makeExcerpt('app/app-routing.module.1.ts')
|
||
|
||
:marked
|
||
Next, we'll update our `app.module.ts` file by importing our `AppRoutingModule` token
|
||
from the `app-routing.module.ts` and replace our `RouterModule.forRoot` with our newly
|
||
created `AppRoutingModule`.
|
||
|
||
接下来,我们将更新`app.module.ts`文件,从`app-routing.module.ts`中导入`AppRoutingModule`令牌,
|
||
并用它替换`RouterModule.forRoot`。
|
||
|
||
+makeExcerpt('app/app.module.2.ts')
|
||
|
||
:marked
|
||
Our application continues to work just the same, and we can use our routing module as
|
||
the central place to maintain our routing configuration for each feature module.
|
||
|
||
应用继续正常运行,我们可以把路由模块作为为每个特征模块维护路由配置的中心地方。
|
||
|
||
a#why-routing-module
|
||
:marked
|
||
### Do you need a _Routing Module_?
|
||
|
||
### 你需要**路由模块**吗?
|
||
|
||
The _Routing Module_ *replaces* the routing configuration in the root or feature module.
|
||
_Either_ configure routes in the Routing Module _or_ within the module itself but not in both.
|
||
|
||
**路由模块**在根模块或者特征模块替换了路由配置。在路由模块或者在模块内部配置路由,但不要同时在两处都配置。
|
||
|
||
The Routing Module is a design choice whose value is most obvious when the configuration is complex
|
||
and includes specialized guard and resolver services.
|
||
It can seem like overkill when the actual configuration is dead simple.
|
||
|
||
路由模块是设计选择,它的价值在配置很复杂,并包含专门守卫和解析器服务时尤其明显。
|
||
在配置很简单时,它可能看起来很多余。
|
||
|
||
Some developers skip the Routing Module (e.g., `AppRoutingModule`) when the configuration is simple and
|
||
merge the routing configuration directly into the companion module (e.g., `AppModule`).
|
||
|
||
在配置很简单时,一些开发者跳过路由模块(例如`AppRoutingModule`),并将路由配置直接混合在关联模块中(比如`AppModule` )。
|
||
|
||
We recommend that you choose one pattern or the other and follow that pattern consistently.
|
||
|
||
我们建议你选择其中一种模式,并坚持模式的一致性。
|
||
|
||
Most developers should always implement a Routing Module for the sake of consistency.
|
||
|
||
大多数开发者应该采用路由模块,以保持一致性。
|
||
|
||
It keeps the code clean when configuration becomes complex.
|
||
It makes testing the feature module easier.
|
||
Its existence calls attention to the fact that a module is routed.
|
||
It is where developers expect to find and expand routing configuration.
|
||
|
||
它在配置复杂时,能确保代码干净。
|
||
它让测试特征模块更加容易。
|
||
它的存在突出了模块时被路由的事实。
|
||
开发者可以很自然的从路由模块中查找和扩展路由配置。
|
||
|
||
.l-main-section#heroes-feature
|
||
:marked
|
||
## Milestone #3: The Heroes Feature
|
||
|
||
## 里程碑 #2 英雄特征区
|
||
|
||
We've seen how to navigate using the `RouterLink` directive.
|
||
|
||
我们刚刚学习了如何用`RouterLink`指令进行导航。
|
||
|
||
Now we'll learn some new tricks such as how to
|
||
|
||
现在,我们将学习一些新的技巧,比如该如何:
|
||
|
||
* organize our app and routes into *feature areas* using modules
|
||
|
||
* 用模块把应用和路由组织为一些*特性区*
|
||
|
||
* navigate imperatively from one component to another
|
||
|
||
* 命令式地从一个组件导航到另一个组件
|
||
|
||
* pass required and optional information in route parameters
|
||
|
||
* 通过路由传递必要信息和可选信息
|
||
|
||
To demonstrate, we'll build out the *Heroes* feature.
|
||
|
||
作为演示,我们将构建出*英雄*特性区。
|
||
|
||
### The Heroes "feature area"
|
||
|
||
### 英雄“特性区”
|
||
|
||
A typical application has multiple *feature areas*, each an island of functionality
|
||
with its own workflow(s), dedicated to a particular business purpose.
|
||
|
||
典型的应用中有多个*特性区*,每个区都是一个“功能岛”,它们有自己的工作流、实现一个特定的业务目标。
|
||
|
||
We could continue to add files to the `app/` folder.
|
||
That's unrealistic and ultimately not maintainable.
|
||
We think it's better to put each feature area in its own folder.
|
||
|
||
我们可以继续把文件全添加到`app/`目录中。
|
||
但那么做不太现实,并且最终将无法维护。
|
||
因此,把每个特性区都放进自己的目录中会更好一些。
|
||
|
||
Our first step is to **create a separate `app/heroes/` folder**
|
||
and add *Hero Management* feature files there.
|
||
|
||
第一步是**创建一个独立的`app/heroes/`文件夹**,并在其中添加属于*英雄管理*特性区的文件。
|
||
|
||
We won't be creative about it. Our example is pretty much a
|
||
copy of the code and capabilities in the "[Tutorial: Tour of Heroes](../tutorial/index.html)".
|
||
|
||
我们没有在这里引进新东西。这个例子从代码上和功能上来看,几乎就是“[教程: 英雄指南](../tutorial/index.html)”的一份拷贝。
|
||
|
||
Here's how the user will experience this version of the app
|
||
|
||
下面是该版本应用的用户体验演示:
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/router-2-anim.gif' alt="App in action" )
|
||
|
||
:marked
|
||
### Add Heroes functionality
|
||
|
||
### 添加“英雄”功能
|
||
|
||
We want to break our app out into different *feature modules* that we then import
|
||
into our main module so it can make use of them. First, we'll create a `heroes.module.ts`
|
||
in our heroes folder.
|
||
|
||
我们要把应用拆成不同的*特性模块*,然后把它们导入我们的主模块中,以便使用它们。首先,我们要在heroes目录下创建一个`heroes.module.ts`文件。
|
||
|
||
We delete the placeholder `hero-list.component.ts` that's in
|
||
the `app/` folder.
|
||
|
||
我们删除了位于`app/`目录下的占位文件`hero-list.component.ts`。
|
||
|
||
We create a new `hero-list.component.ts` in the `app/heroes/`
|
||
folder and copy over the contents of the final `heroes.component.ts` from the tutorial.
|
||
We copy the `hero-detail.component.ts` and the `hero.service.ts` files
|
||
into the `heroes/` folder.
|
||
|
||
然后在`app/heroes/`目录下创建了一个`hero-list.component.ts`文件,并从上面的教程中把`heroes.component.ts`最终版的内容拷贝进来。
|
||
再把`hero-detail.component.ts`和`hero.service.ts`文件拷贝到`heroes/`目录下。
|
||
|
||
We provide the `HeroService` in the `providers` array of our `Heroes` module
|
||
so its available to all components within our module.
|
||
|
||
我们在`Heroes`模块的`providers`数组中提供了`HeroService`,以便它可用于模块中的所有组件。
|
||
|
||
Our `Heroes` module is ready for routing.
|
||
|
||
我们的`Heroes`模块准备好路由了。
|
||
|
||
+makeExcerpt('app/heroes/heroes.module.1.ts')
|
||
|
||
:marked
|
||
When we're done organizing, we have four *Hero Management* files:
|
||
|
||
安排完这些,我们就有了四个*英雄管理*特性区的文件:
|
||
|
||
.filetree
|
||
.file app/heroes
|
||
.children
|
||
.file hero-detail.component.ts
|
||
.file hero-list.component.ts
|
||
.file hero.service.ts
|
||
.file heroes.module.ts
|
||
|
||
:marked
|
||
Now it's time for some surgery to bring these files and the rest of the app
|
||
into alignment with our application router.
|
||
|
||
现在到时间做一些“外科手术”了。我们利用应用程序的路由器,把这些文件和应用的其它部分联合起来。
|
||
|
||
### *Hero* feature routing requirements
|
||
|
||
### *英雄*特性区的路由需求
|
||
|
||
The new Heroes feature has two interacting components, the list and the detail.
|
||
The list view is self-sufficient; we navigate to it, it gets a list of heroes and displays them.
|
||
It doesn't need any outside information.
|
||
|
||
新的“英雄”特性有两个相互协作的组件,列表和详情。
|
||
列表视图是自给自足的,我们导航到它,它会自行获取英雄列表并显示它们,该组件不需要任何外部信息。
|
||
|
||
The detail view is different. It displays a particular hero. It can't know which hero on its own.
|
||
That information must come from outside.
|
||
|
||
详情视图就不同了。它要显示一个特定的英雄,但是它本身却无法知道显示哪一个,此信息必须来自外部。
|
||
|
||
In our example, when the user selects a hero from the list, we navigate to the detail view to show that hero.
|
||
We'll tell the detail view which hero to display by including the selected hero's id in the route URL.
|
||
|
||
在这个例子中,当用户从列表中选择了一个英雄时,我们就导航到详情页以显示那个英雄。
|
||
通过把所选英雄的id编码进路由的URL中,就能告诉详情视图该显示哪个英雄。
|
||
|
||
### *Hero* feature route configuration
|
||
|
||
### *英雄*特性区的路由配置
|
||
|
||
We recommend giving each feature area its own route configuration file.
|
||
|
||
我们推荐的方式是为每个特性区创建它自己的路由配置文件。
|
||
|
||
Create a new `heroes-routing.module.ts` in the `heroes` folder like this:
|
||
|
||
在`heroes`目录下创建一个新的`heroes-routing.module.ts`文件,就像这样:
|
||
|
||
+makeExcerpt('app/heroes/heroes-routing.module.ts')
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Keep the Routing Module file in the same folder as its companion module file.
|
||
Here both `heroes-routing.module.ts` and `heroes.module.ts` are in the same `app/heroes` folder.
|
||
|
||
将路由模块文件放到它相关的模块文件所在目录里。
|
||
这里,`heroes-routing.module.ts`和`heroes.module.ts`都在`app/heroes`目录中。
|
||
|
||
:marked
|
||
We use the same techniques we learned in creating the `app-routing.module.ts`.
|
||
|
||
我们使用与`app.routes.ts`中一样的技巧。
|
||
|
||
We import the two components from their new locations in the `app/heroes/` folder, define the two hero routes.
|
||
and add export our `HeroRoutingModule` that returns our `RoutingModule` for the hero feature module.
|
||
|
||
从它们所在的新`app/heroes/`目录导入列表和详情组件,定义两个英雄路由并导出到`HeroesRoutes`数组。
|
||
|
||
:marked
|
||
Now that we have routes for our `Heroes` module, we'll need to register them with the *Router*.
|
||
We'll import the *RouterModule* like we did in the `app-routing.module.ts`, but there is a slight difference here.
|
||
In our `app-routing.module.ts`, we used the static **forRoot** method to register our routes and application level
|
||
service providers. In a feature module we use static **forChild** method.
|
||
|
||
现在,我们的`Heroes`模块有路由了,我们得用*路由器*注册它们。
|
||
我们像在`app.routing.ts`中那样导入*RouterModule*,但这里稍微有一点不同。
|
||
在设置`app.routing.ts`时,我们使用了静态的**forRoot**方法来注册我们的路由和全应用级服务提供商。
|
||
在特性模块中,我们要改用**Router.forChild**静态方法。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
The **RouterModule.forRoot** should only be provided for the `AppModule`. Since we are in a feature
|
||
module, we'll use **RouterModule.forChild** method to only register additional routes.
|
||
|
||
**RouterModule.forRoot**只能由`AppModule`提供。但我们位于特性模块中,所以使用**RouterModule.forChild**来单独注册更多路由。
|
||
|
||
:marked
|
||
We import our `HeroRoutingModule` token from `heroes-routing.module.ts` into our `Heroes` module and register the routing.
|
||
|
||
我们在`Heroes`模块中从`heroes-routing.module.ts`中导入`HeroRoutingModule`,并注册其路由。
|
||
|
||
+makeExcerpt('app/heroes/heroes.module.ts (heroes routing)', 'heroes-routes')
|
||
|
||
:marked
|
||
### Route definition with a parameter
|
||
|
||
### 带参数的路由定义
|
||
|
||
The route to `HeroDetailComponent` has a twist.
|
||
|
||
`HeroDetailComponent`的路由有点特殊。
|
||
|
||
+makeExcerpt('app/heroes/heroes-routing.module.ts (excerpt)', 'hero-detail-route')
|
||
|
||
:marked
|
||
Notice the `:id` token in the path. That creates a slot in the path for a **Route Parameter**.
|
||
In this case, we're expecting the router to insert the `id` of a hero into that slot.
|
||
|
||
注意路径中的`:id`令牌。它为*路由参数*在路径中创建一个“空位”。在这里,我们期待路由器把英雄的`id`插入到那个“空位”中。
|
||
|
||
If we tell the router to navigate to the detail component and display "Magneta", we expect hero `id` (15) to appear in the
|
||
browser URL like this:
|
||
|
||
如果要告诉路由器导航到详情组件,并让它显示“Magneta”,我们会期望这个英雄的`id`(15)像这样显示在浏览器的URL中:
|
||
|
||
code-example(format="." language="bash").
|
||
localhost:3000/hero/15
|
||
|
||
:marked
|
||
If a user enters that URL into the browser address bar, the router should recognize the
|
||
pattern and go to the same "Magneta" detail view.
|
||
|
||
如果用户把此URL输入到浏览器的地址栏中,路由器就会识别出这种模式,同样进入“Magneta”的详情视图。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
#### Route parameter: Required or optional?
|
||
|
||
#### 路由参数:必选还是可选?
|
||
|
||
Embedding the route parameter token, `:id`, in the route definition path is a good choice for our scenario
|
||
because the `id` is *required* by the `HeroDetailComponent` and because
|
||
the value `15` in the path clearly distinguishes the route to "Magneta" from
|
||
a route for some other hero.
|
||
|
||
在这个场景下,把路由参数的令牌`:id`嵌入到路由定义的`path`中是一个好主意,因为对于`HeroDetailComponent`来说`id`是*必须的*,
|
||
而且路径中的值`15`已经足够把到“Magneta”的路由和到其它英雄的路由明确区分开。
|
||
|
||
An [optional-route-parameter](#optional-route-parameters) might be a better choice if we were passing an *optional* value to `HeroDetailComponent`.
|
||
|
||
当我们把一个*可选*值传给`HeroDetailComponent`时,[可选路由参数](#optional-route-parameters)可能是一个更好的选择。
|
||
|
||
a#navigate
|
||
:marked
|
||
### Navigate to hero detail imperatively
|
||
|
||
### 命令式地导航到英雄详情
|
||
|
||
*We won't navigate to the detail component by clicking a link*
|
||
so we won't be adding a new `RouterLink` anchor tag to the shell.
|
||
|
||
*这次我们不打算通过点击链接来导航到详情组件*,因此也不用再把带`RouterLink`的新的A标签加到壳组件中。
|
||
|
||
Instead, when the user *clicks* a hero in the list, we'll *command* the router
|
||
to navigate to the hero detail view for the selected hero.
|
||
|
||
取而代之,当用户在列表中*点击*一个英雄时,我们将*命令*路由器导航到所选英雄的详情视图。
|
||
|
||
We'll adjust the `HeroListComponent` to implement these tasks, beginning with its constructor
|
||
which acquires the router service and the `HeroService` by dependency injection:
|
||
|
||
我们将调整`HeroListComponent`来实现这些任务。先从构造函数开始改:它通过依赖注入系统获得路由服务和`HeroService`服务。
|
||
|
||
+makeExcerpt('app/heroes/hero-list.component.1.ts (constructor)', 'ctor')
|
||
|
||
:marked
|
||
We make a few changes to the template:
|
||
|
||
还要对模板进行一点修改:
|
||
|
||
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'template', '')
|
||
|
||
:marked
|
||
The template defines an `*ngFor` repeater such as [we've seen before](displaying-data.html#ngFor).
|
||
There's a `(click)` [EventBinding](template-syntax.html#event-binding) to the component's `onSelect` method
|
||
which we implement as follows:
|
||
|
||
模板像[以前](displaying-data.html#ngFor)一样定义了一个`*ngFor`重复器。
|
||
还有一个`(click)`[事件绑定](template-syntax.html#event-binding),绑定到了组件的`onSelect`方法,就像这样:
|
||
|
||
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'select')
|
||
|
||
:marked
|
||
It calls the router's **`navigate`** method with a **Link Parameters Array**. We can use this same syntax
|
||
with a `RouterLink` if we want to use it in HTML rather than code.
|
||
|
||
它用一个**链接参数数组**调用路由器的**`navigate`**方法。
|
||
如果我们想把它用在HTML中,那么也可以把相同的语法用在`RouterLink`中。
|
||
|
||
h3#route-parameters Setting the route parameters in the list view
|
||
|
||
h3#route-parameters 在列表视图中设置路由参数
|
||
|
||
:marked
|
||
We're navigating to the `HeroDetailComponent` where we expect to see the details of the selected hero.
|
||
We'll need *two* pieces of information: the destination and the hero's `id`.
|
||
|
||
我们将导航到`HeroDetailComponent`组件。在那里,我们期望看到所选英雄的详情,这需要两部分信息:导航目标和该英雄的`id`。
|
||
|
||
Accordingly, the *link parameters array* has *two* items: the **path** of the destination route and a **route parameter** that specifies the
|
||
`id` of the selected hero.
|
||
|
||
因此,这个*链接参数数组*中有两个条目:目标路由的**`path`(路径)**,和一个用来指定所选英雄`id`的**路由参数**。
|
||
|
||
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'link-parameters-array')
|
||
|
||
:marked
|
||
The router composes the appropriate two-part destination URL from this array:
|
||
|
||
路由器从该数组中组合出一个合适的两段式目标URL:
|
||
|
||
code-example(language="bash").
|
||
localhost:3000/hero/15
|
||
|
||
a#get-route-parameter
|
||
:marked
|
||
### Getting the route parameter in the details view
|
||
### 在详情视图中获得路由参数
|
||
|
||
How does the target `HeroDetailComponent` learn about that `id`?
|
||
Certainly not by analyzing the URL! That's the router's job.
|
||
|
||
目标组件`HeroDetailComponent`该怎么知道这个`id`参数呢?
|
||
当然不会是自己去分析URL了!那是路由器的工作。
|
||
|
||
The router extracts the route parameter (`id:15`) from the URL and supplies it to
|
||
the `HeroDetailComponent` via the **ActivatedRoute** service.
|
||
|
||
路由器从URL中解析出路由参数(`id:15`),并通过**ActivatedRoute**服务来把它提供给`HeroDetailComponent`组件。
|
||
|
||
<a id="activated-route"></a>
|
||
h3#activated-route ActivatedRoute: the one-stop-shop for route information
|
||
h3#activated-route ActivatedRoute:一站式获得路由信息
|
||
:marked
|
||
Each route contains information about its path, data parameters, URL segment and much more.
|
||
All of this information is available in an injected service provided by the router called the [ActivatedRoute](../api/router/index/ActivatedRoute-interface.html).
|
||
|
||
每个路由都包含路径、数据参数、URL片段等很多信息。
|
||
所有这些信息都可以通过有路由器提供的一个叫[ActivatedRoute](../api/router/index/ActivatedRoute-interface.html)的服务提供商来获取。
|
||
|
||
The `ActivatedRoute` contains all the information you need from the current route component as well as ways to get information
|
||
about other activated routes in the `RouterState`.
|
||
|
||
`ActivatedRoute`包含你需要从当前路由组件中获得的全部信息,正如你可以从`RouterState`中获得关于其它激活路由的信息。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
**`url`**: An `Observable` of the route path(s). The value is provided as an array of strings for each part of the route path.
|
||
|
||
**`url`**: 该路由路径的`Observable`对象。它的值是一个由路径中各个部件组成的字符串数组。
|
||
|
||
**`data`**: An `Observable` that contains the `data` object provided for the route. Also contains any resolved values from the [resolve guard](#resolve-guard).
|
||
|
||
**`data`**: 该路由提供的`data`对象的一个`Observable`对象。还包含从[resolve守卫](#resolve-guard)中解析出来的值。
|
||
|
||
**`params`**: An `Observable` that contains the required and [optional parameters](#optional-route-parameters) specific to the route.
|
||
|
||
**`params`**: 包含该路由的必选参数和[可选参数](#optional-route-parameters)的`Observable`对象。
|
||
|
||
**`queryParams`**: An `Observable` that contains the [query parameters](#query-parameters) available to all routes.
|
||
|
||
**`queryParams`**: 一个包含对所有路由都有效的[查询参数](#query-parameters)的`Observable`对象。
|
||
|
||
**`fragment`**: An `Observable` of the URL [fragment](#fragment) available to all routes.
|
||
|
||
**`fragment`**: 一个包含对所有路由都有效的[片段](#fragment)值的`Observable`对象。
|
||
|
||
**`outlet`**: The name of the `RouterOutlet` used to render the route. For an unnamed outlet, the outlet name is **primary**.
|
||
|
||
**`outlet`**: `RouterOutlet`的名字,用于指示渲染该路由的位置。对于未命名的`RouterOutlet`,这个名字是**primary**。
|
||
|
||
**`routeConfig`**: The route configuration used for the route that contains the origin path.
|
||
|
||
**`routeConfig`**: 与该路由的原始路径对应的配置信息。
|
||
|
||
**`parent`**: an `ActivatedRoute` that contains the information from the parent route when using [child routes](#child-routing-component).
|
||
|
||
**`parent`**: 当使用[子路由](#child-routing-component)时,它是一个包含父路由信息的`ActivatedRoute`对象。
|
||
|
||
**`firstChild`**: contains the first `ActivatedRoute` in the list of child routes.
|
||
|
||
**`firstChild`**: 包含子路由列表中的第一个`ActivatedRoute`对象。
|
||
|
||
**`children`**: contains all the [child routes](#child-routing-component) activated under the current route.
|
||
|
||
**`children`**: 包含当前路由下激活的全部[子路由](#child-routing-component)。
|
||
|
||
:marked
|
||
We import the `Router`, `ActivatedRoute`, and `Params` tokens from the router package.
|
||
|
||
我们要从路由器(`router`)包中导入`Router`、`ActivatedRoute`和`Params`类。
|
||
|
||
+makeExcerpt('app/heroes/hero-detail.component.1.ts (activated route)', 'imports')
|
||
|
||
a#hero-detail-ctor
|
||
|
||
:marked
|
||
As usual, we write a constructor that asks Angular to inject services
|
||
that the component requires and reference them as private variables.
|
||
|
||
通常,我们会直接写一个构造函数,让Angular把组件所需的服务注入进来,自动定义同名的私有变量,并把它们存进去。
|
||
|
||
+makeExcerpt('app/heroes/hero-detail.component.ts (constructor)', 'ctor')
|
||
|
||
:marked
|
||
Later, in the `ngOnInit` method,
|
||
we use the `ActivatedRoute` service to retrieve the parameters for our route.
|
||
Since our parameters are provided as an `Observable`, we use the _forEach_ method to retrieve them for the `id` parameter by name and
|
||
tell the `HeroService` to fetch the hero with that `id`.
|
||
|
||
然后,在`ngOnInit`方法中,
|
||
我们用`ActivatedRoute`服务来接收本路由的参数。
|
||
由于这些参数是作为`Observable`(可观察对象)提供的,所以我们_订阅(`subscribe`)_它们,通过名字引用`id`参数,并告诉`HeroService`获取指定`id`的英雄。
|
||
我们还要保存这个`Subscription`(订阅的返回值)的引用,供后面做清理工作。
|
||
|
||
+makeExcerpt('app/heroes/hero-detail.component.ts (ngOnInit)', 'ngOnInit')
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
Angular calls the `ngOnInit` method shortly after creating an instance of the `HeroDetailComponent`.
|
||
|
||
在创建了`HeroDetailComponent`之后,Angular很快就会调用`ngOnInit`方法。
|
||
|
||
We put the data access logic in the `ngOnInit` method rather than inside the constructor
|
||
to improve the component's testability.
|
||
We explore this point in greater detail in the [OnInit appendix](#onInit) below.
|
||
|
||
我们要把数据访问逻辑放进`ngOnInit`方法中而不是构造函数中,以提高该组件的可测试性。
|
||
在后面的[OnInit附录](#onInit)中,我们会再详细讲解这一点。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
Learn about the `ngOnInit` method in the
|
||
[Lifecycle Hooks](lifecycle-hooks.html) chapter.
|
||
|
||
要学习关于`ngOnInit`和`ngOnDestroy`方法的更多知识,参见[生命周期钩子](lifecycle-hooks.html)一章。
|
||
|
||
h4#reuse Observable <i>params</i> and component re-use
|
||
|
||
h4#reuse Observable<i>参数</i>与组件复用
|
||
|
||
:marked
|
||
In this example, we retrieve the route params from an `Observable`.
|
||
That implies that the route params can change during the lifetime of this component.
|
||
|
||
在这个例子中,我们订阅了路由参数的`Observable`对象。
|
||
这种写法暗示着这些路由参数在该组件的生存期内可能会变化。
|
||
|
||
They might. By default, the router reuses a component instance when it re-navigates to the same component type
|
||
without visiting a different component first. The parameters can change between each re-use.
|
||
|
||
确实如此!默认情况下,如果它没有访问过其它组件就导航到了同一个组件实例,那么路由器倾向于复用组件实例。如果复用,这些参数可以变化。
|
||
|
||
Suppose a parent component navigation bar had "forward" and "back" buttons
|
||
that scrolled through the list of heroes.
|
||
Each click navigated imperatively to the `HeroDetailComponent` with the next or previous `id`.
|
||
|
||
假设父组件的导航栏有“前进”和“后退”按钮,用来轮流显示英雄列表中中英雄的详情。
|
||
每次点击都会强制导航到带前一个或后一个`id`的`HeroDetailComponent`组件。
|
||
|
||
We don't want the router to remove the current `HeroDetailComponent` instance from the
|
||
DOM only to re-create it for the next `id`.
|
||
That could be visibly jarring.
|
||
Better to simply re-use the same component instance and update the parameter.
|
||
|
||
我们不希望路由器仅仅从DOM中移除当前的`HeroDetailComponent`实例,并且用下一个`id`重新创建它。
|
||
那可能导致界面抖动。
|
||
更好的方式是复用同一个组件实例,并更新这些参数。
|
||
|
||
But `ngOnInit` is only called once per instantiation.
|
||
We need a way to detect when the route parameters change from _within the same instance_.
|
||
The observable `params` property handles that beautifully.
|
||
|
||
但是`ngOnInit`对每个实例只调用一次。
|
||
我们需要一种方式来检测_在同一个实例中_路由参数什么时候发生了变化。
|
||
而`params`属性这个可观察对象(Observable)干净漂亮的处理了这种情况。
|
||
|
||
h4#snapshot <i>Snapshot</i>: the no-observable alternative
|
||
|
||
h4#snapshot <i>快照</i>:不需要可观察(no-observable)时的替代方案
|
||
|
||
:marked
|
||
This application won't reuse the `HeroDetailComponent`.
|
||
We always return to the hero list to select another hero to view.
|
||
There's no way to navigate from hero detail to hero detail
|
||
without visiting the list component in between.
|
||
That means we get a new `HeroDetailComponent` instance every time.
|
||
|
||
本应用不需要复用`HeroDetailComponent`。
|
||
我们总会先返回英雄列表,再选择另一位英雄。
|
||
所以,不存在从一个英雄详情导航到另一个而不用经过英雄列表的情况。
|
||
这意味着我们每次都会得到一个全新的`HeroDetailComponent`实例。
|
||
|
||
Suppose we know for certain that `HeroDetailComponent` will *never, never, ever*
|
||
be re-used. We'll always re-create the component each time we navigate to it.
|
||
|
||
假如我们很确定`HeroDetailComponent`组件*永远、永远*不会被复用,每次导航到英雄详情时都会重新创建该组件。
|
||
|
||
The router offers a *Snapshot* alternative that gives us the initial value of the route parameters.
|
||
We don't need to subscribe or unsubscribe.
|
||
It's much simpler to write and read:
|
||
|
||
路由器提供了一个备选方案:*快照(snapshot)*,它会给我们路由参数的初始值。这样我们就不用订阅,也不用被迫在`ngDestroy`中反订阅了。
|
||
这样会更容易书写和阅读:
|
||
|
||
+makeExcerpt('app/heroes/hero-detail.component.2.ts (ngOnInit snapshot)', 'snapshot')
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
**Remember:** we only get the _initial_ value of the parameters with this technique.
|
||
Stick with the observable `params` approach if there's even a chance that we might navigate
|
||
to this component multiple times in a row.
|
||
We are leaving the observable `params` strategy in place just in case.
|
||
|
||
**记住:**,用这种技巧,我们只得到了这些参数的_初始_值。
|
||
如果有可能连续多次导航到此组件,那么就该用`params`可观察对象的方式。
|
||
我们在这里选择使用`params`可观察对象策略,以防万一。
|
||
|
||
a#nav-to-list
|
||
:marked
|
||
### Navigating back to the list component
|
||
|
||
### 导航回列表组件
|
||
|
||
The `HeroDetailComponent` has a "Back" button wired to its `gotoHeroes` method that navigates imperatively
|
||
back to the `HeroListComponent`.
|
||
|
||
`HeroDetailComponent`组件有一个“Back”按钮,关联到它的`gotoHeroes`方法,该方法会导航回`HeroListComponent`组件。
|
||
|
||
The router `navigate` method takes the same one-item *link parameters array*
|
||
that we can bind to a `[routerLink]` directive.
|
||
It holds the **path to the `HeroListComponent`**:
|
||
|
||
路由的`navigate`方法同样接受一个单条目的*链接参数数组*,我们也可以把它绑定到`[routerLink]`指令上。
|
||
它保存着**到`HeroListComponent`组件的路径**:
|
||
|
||
+makeExcerpt('app/heroes/hero-detail.component.1.ts (excerpt)', 'gotoHeroes')
|
||
|
||
.l-main-section#optional-route-parameters
|
||
:marked
|
||
### Route Parameters
|
||
### 路由参数
|
||
|
||
We use [*route parameters*](#route-parameters) to specify a *required* parameter value *within* the route URL
|
||
as we do when navigating to the `HeroDetailComponent` in order to view-and-edit the hero with *id:15*.
|
||
|
||
如果想导航到`HeroDetailComponent`以对id为15的英雄进行查看并编辑,就要在路由的URL中使用[*路由参数*](#route-parameters)来指定*必要*参数值。
|
||
|
||
code-example(format="." language="bash").
|
||
localhost:3000/hero/15
|
||
:marked
|
||
Sometimes we wish to add *optional* information to a route request.
|
||
For example, the `HeroListComponent` doesn't need help to display a list of heroes.
|
||
But it might be nice if the previously-viewed hero were pre-selected when returning from the `HeroDetailComponent`.
|
||
|
||
有时我们希望往路由请求中添加*可选的*信息。
|
||
例如,`HeroListComponent`虽然不需要借助此信息显示英雄列表,但是如果从`HeroDetailComponent`返回时,它能自动选中刚刚查看过的英雄就好了。
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/selected-hero.png' alt="Selected hero")
|
||
:marked
|
||
That becomes possible if we can include hero Magneta's `id` in the URL when we
|
||
return from the `HeroDetailComponent`, a scenario we'll pursue in a moment.
|
||
|
||
如果我们能在从`HeroDetailComponent`返回时在URL中带上英雄Magneta的`id`,不就可以了吗?接下来我们就尝试实现这个场景。
|
||
|
||
Optional information takes other forms. Search criteria are often loosely structured, e.g., `name='wind*'`.
|
||
Multiple values are common — `after='12/31/2015' & before='1/1/2017'` — in no particular order —
|
||
`before='1/1/2017' & after='12/31/2015'` — in a variety of formats — `during='currentYear'` .
|
||
|
||
可选信息有很多种形式。搜索条件通常就不是严格结构化的,比如`name='wind*'`;有多个值也很常见,如`after='12/31/2015'&before='1/1/2017'`;
|
||
而且顺序无关,如`before='1/1/2017'&after='12/31/2015'`,还可能有很多种变体格式,如`during='currentYear'`。
|
||
|
||
These kinds of parameters don't fit easily in a URL *path*. Even if we could define a suitable URL token scheme,
|
||
doing so greatly complicates the pattern matching required to translate an incoming URL to a named route.
|
||
|
||
这么多种参数要放在URL的*路径*中可不容易。即使我们能制定出一个合适的URL方案,实现起来也太复杂了,得通过模式匹配才能把URL翻译成命名路由。
|
||
|
||
Optional parameters are the ideal vehicle for conveying arbitrarily complex information during navigation.
|
||
Optional parameters aren't involved in pattern matching and afford enormous flexibility of expression.
|
||
|
||
可选参数是在导航期间传送任意复杂信息的理想载体。
|
||
可选参数不涉及到模式匹配并在表达上提供了巨大的灵活性。
|
||
|
||
The Router supports navigation with optional parameters as well as required route parameters.
|
||
We define _optional_ parameters in an *object* after we define our required route parameters.
|
||
|
||
和必要参数一样,路由器也支持通过可选参数导航。
|
||
我们在定义完必要参数之后,通过一个*对象*来定义*可选参数*。
|
||
|
||
### Route Parameters: Required or Optional?
|
||
### 路由参数:用必要的还是可选的?
|
||
|
||
There is no hard-and-fast rule. In general,
|
||
|
||
并没有一劳永逸的规则,通常:
|
||
|
||
*prefer a required route parameter when*
|
||
|
||
*下列情况下优先使用必要参数*
|
||
|
||
* the value is required.
|
||
* 该值是必须的。
|
||
* the value is necessary to distinguish one route path from another.
|
||
* 该值在区分此路由与其它路由时是必要的。
|
||
|
||
*prefer an optional parameter when*
|
||
|
||
*下列情况下优先使用可选参数*
|
||
|
||
* the value is optional, complex, and/or multi-variate.
|
||
|
||
* 该值是可选的、复杂的,和/或多变量的。
|
||
|
||
<a id="route-parameters-object"></a>
|
||
### Route parameter
|
||
### 路由参数
|
||
|
||
When navigating to the `HeroDetailComponent` we specified the _required_ `id` of the hero-to-edit in the
|
||
*route parameter* and made it the second item of the [*link parameters array*](#link-parameters-array).
|
||
|
||
要导航到`HeroDetailComponent`,我们需要在*路由参数*中指定要编辑英雄的必要参数`id`,把这个`id`作为[*链接参数数组*](#link-parameters-array)的第二个条目。
|
||
|
||
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'link-parameters-array')
|
||
|
||
:marked
|
||
The router embedded the `id` value in the navigation URL because we had defined it
|
||
as a route parameter with an `:id` placeholder token in the route `path`:
|
||
|
||
路由器在导航URL中内嵌了`id`的值,这是因为我们把它用一个`:id`占位符当做路由参数定义在了路由的`path`中:
|
||
|
||
+makeExcerpt('app/heroes/heroes-routing.module.ts', 'hero-detail-route')
|
||
|
||
:marked
|
||
When the user clicks the back button, the `HeroDetailComponent` constructs another *link parameters array*
|
||
which it uses to navigate back to the `HeroListComponent`.
|
||
|
||
当用户点击后退按钮时,`HeroDetailComponent`构造了另一个*链接参数数组*,可以用它导航回`HeroListComponent`。
|
||
|
||
+makeExcerpt('app/heroes/hero-detail.component.1.ts', 'gotoHeroes')
|
||
|
||
:marked
|
||
This array lacks a route parameter because we had no reason to send information to the `HeroListComponent`.
|
||
|
||
该数组缺少一个路由参数,这是因为我们那时没有理由往`HeroListComponent`发送信息。
|
||
|
||
Now we have a reason. We'd like to send the id of the current hero with the navigation request so that the
|
||
`HeroListComponent` can highlight that hero in its list.
|
||
This is a _nice-to-have_ feature; the list will display perfectly well without it.
|
||
|
||
但现在有了。我们要在导航请求中同时发送当前英雄的id,以便`HeroListComponent`可以在列表中高亮这个英雄。
|
||
这是一个*有更好,没有也无所谓*的特性,就算没有它,列表照样能显示得很完美。
|
||
|
||
We do that with an object that contains an _optional_ `id` parameter.
|
||
For demonstration purposes, we also defined a junk parameter (`foo`) that the `HeroListComponent` should ignore.
|
||
Here's the revised navigation statement:
|
||
|
||
我们通过一个包含*可选*`id`参数的对象来做到这一点。
|
||
为了演示,我们还定义了一个没用的参数(`foo`),`HeroListComponent`应该忽略它。
|
||
下面是修改过的导航语句:
|
||
|
||
+makeExcerpt('app/heroes/hero-detail.component.ts (go to heroes)', 'gotoHeroes-navigate')
|
||
|
||
:marked
|
||
The application still works. Clicking "back" returns to the hero list view.
|
||
|
||
该应用仍然能工作。点击“back”按钮返回英雄列表视图。
|
||
|
||
Look at the browser address bar.
|
||
|
||
注意浏览器的地址栏。
|
||
|
||
.l-sub-section
|
||
img(src='/resources/images/devguide/plunker-separate-window-button.png' alt="pop out the window" align="right" style="margin-right:-20px")
|
||
:marked
|
||
When running in plunker, pop out the preview window by clicking the blue 'X' button in the upper right corner.
|
||
|
||
当在plunker中运行时,请点击右上角的蓝色'X'按钮来弹出预览窗口,否则你看不到地址栏的变化。
|
||
|
||
:marked
|
||
It should look something like this, depending on where you run it:
|
||
|
||
看起来应该是这样,不过也取决于你在哪里运行它:
|
||
|
||
code-example(language="bash").
|
||
localhost:3000/heroes;id=15;foo=foo
|
||
|
||
:marked
|
||
The `id` value appears in the URL as (`;id=15;foo=foo`), not in the URL path.
|
||
The path for the "Heroes" route doesn't have an `:id` token.
|
||
|
||
`id`的值像这样出现在URL中(`;id=15;foo=foo`),但不在URL的路径部分。
|
||
“Heroes”路由的路径部分并没有定义`:id`。
|
||
|
||
The optional route parameters are not separated by "?" and "&" as they would be in the URL query string.
|
||
They are **separated by semicolons ";"**
|
||
This is *matrix URL* notation — something we may not have seen before.
|
||
|
||
可选的路由参数没有使用“?”和“&”符号分隔,因为它们将用在URL查询字符串中。
|
||
它们是**用“;”分隔的**。
|
||
这是*矩阵URL*标记法 —— 我们以前可能从未见过。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
*Matrix URL* notation is an idea first floated
|
||
in a [1996 proposal](http://www.w3.org/DesignIssues/MatrixURIs.html) by the founder of the web, Tim Berners-Lee.
|
||
|
||
*Matrix URL*写法首次提出是在[1996提案](http://www.w3.org/DesignIssues/MatrixURIs.html)中,提出者是Web的奠基人:Tim Berners-Lee。
|
||
|
||
Although matrix notation never made it into the HTML standard, it is legal and
|
||
it became popular among browser routing systems as a way to isolate parameters
|
||
belonging to parent and child routes. The Router is such a system and provides
|
||
support for the matrix notation across browsers.
|
||
|
||
虽然Matrix写法未曾进入过HTML标准,但它是合法的。而且在浏览器的路由系统中,它作为从父路由和子路由中单独隔离出参数的方式而广受欢迎。Angular的路由器正是这样一个路由系统,并支持跨浏览器的Matrix写法。
|
||
|
||
The syntax may seem strange to us but users are unlikely to notice or care
|
||
as long as the URL can be emailed and pasted into a browser address bar
|
||
as this one can.
|
||
|
||
这种语法对我们来说可能有点奇怪,不过用户不会在意这一点,因为该URL可以正常的通过邮件发出去或粘贴到浏览器的地址栏中。
|
||
|
||
:marked
|
||
### Route parameters in the *ActivatedRoute* service
|
||
|
||
### *ActivatedRoute*服务中的路由参数
|
||
|
||
The list of heroes is unchanged. No hero row is highlighted.
|
||
|
||
英雄列表仍没有改变,没有哪个英雄列被加亮显示。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
The <live-example></live-example> *does* highlight the selected
|
||
row because it demonstrates the final state of the application which includes the steps we're *about* to cover.
|
||
At the moment we're describing the state of affairs *prior* to those steps.
|
||
|
||
<live-example>在线例子</live-example>*高亮了*选中的行,因为它演示的是应用的最终状态,因此包含了我们*即将*示范的步骤。
|
||
此刻,我们描述的仍是那些步骤*之前*的状态。
|
||
|
||
:marked
|
||
The `HeroListComponent` isn't expecting any parameters at all and wouldn't know what to do with them.
|
||
Let's change that.
|
||
|
||
`HeroListComponent`还完全不需要任何参数,也不知道该怎么处理它们。我们这就改变这一点。
|
||
|
||
Previously, when navigating from the `HeroListComponent` to the `HeroDetailComponent`,
|
||
we subscribed to the route params `Observable` and made it available to the `HeroDetailComponent`
|
||
in the `ActivatedRoute` service. We injected that service in the constructor of the `HeroDetailComponent`.
|
||
|
||
以前,当从`HeroListComponent`导航到`HeroDetailComponent`时,我们通过`ActivatedRoute`服务订阅了路由参数这个`Observable`,并让它能用在`HeroDetailComponent`中。我们把该服务注入到了`HeroDetailComponent`的构造函数中。
|
||
|
||
This time we'll be navigating in the opposite direction, from the `HeroDetailComponent` to the `HeroListComponent`.
|
||
|
||
这次,我们要进行反向导航,从`HeroDetailComponent`到`HeroListComponent`。
|
||
|
||
First we extend the router import statement to include the `ActivatedRoute` service symbol;
|
||
|
||
首先,我们扩展该路由的导入语句,以包含进`ActivatedRoute`服务的类;
|
||
|
||
+makeExcerpt('app/heroes/hero-list.component.ts (import)', 'import-router')
|
||
|
||
:marked
|
||
Then we use the `ActivatedRoute` to access the `params` _Observable_ so we can subscribe
|
||
and extract the `id` parameter as the `selectedId`:
|
||
|
||
然后,使用`ActivatedRoute`来访问`params`这个`Observable`,以便我们订阅它,并把其中的`id`参数提取到`selectedId`中:
|
||
|
||
+makeExcerpt('app/heroes/hero-list.component.ts (constructor)', 'ctor')
|
||
|
||
.l-sub-section
|
||
:marked
|
||
All route/query parameters are strings.
|
||
The (+) in front of the `params['id']` expression is a JavaScript trick to convert the string to an integer.
|
||
|
||
所有的路由参数或查询参数都是字符串。
|
||
`params['id']`表达式前面的加号(+)是一个JavaScript的小技巧,用来把字符串转换成整数。
|
||
|
||
:marked
|
||
We add an `isSelected` method that returns true when a hero's id matches the selected id.
|
||
|
||
我们添加了一个`isSelected`方法,当英雄的id和选中的id匹配时,它返回真值。
|
||
|
||
+makeExcerpt('app/heroes/hero-list.component.ts', 'isSelected')
|
||
|
||
:marked
|
||
Finally, we update our template with a [Class Binding](template-syntax.html#class-binding) to that `isSelected` method.
|
||
The binding adds the `selected` CSS class when the method returns `true` and removes it when `false`.
|
||
Look for it within the repeated `<li>` tag as shown here:
|
||
|
||
最后,我们用[CSS类绑定](template-syntax.html#class-binding)更新模板,把它绑定到`isSelected`方法上。
|
||
如果该方法返回`true`,此绑定就会添加CSS类`selected`,否则就移除它。
|
||
在`<li>`标记中找到它,就像这样:
|
||
|
||
+makeExcerpt('app/heroes/hero-list.component.ts', 'template')
|
||
|
||
:marked
|
||
When the user navigates from the heroes list to the "Magneta" hero and back, "Magneta" appears selected:
|
||
|
||
当用户从英雄列表导航到英雄“Magneta”并返回时,“Magneta”看起来是选中的:
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/selected-hero.png' alt="Selected List" )
|
||
:marked
|
||
The optional `foo` route parameter is harmless and continues to be ignored.
|
||
|
||
这儿可选的`foo`路由参数人畜无害,并继续被忽略。
|
||
|
||
h3#route-animation Adding animations to the route component
|
||
h3#route-animation 为路由组件添加动画
|
||
:marked
|
||
Our heroes feature module is almost complete, but what is a feature without some smooth transitions?
|
||
We already know that Angular supports [animations](../guide/animations.html) and we want to take
|
||
advantage of them by adding some animation to our *Hero Detail* component.
|
||
|
||
我们的“英雄”这个特性模块就要完成了,但这个特性还没有平滑的专场效果。
|
||
我们知道Angular支持[动画](../guide/animations.html),想要得到这项优点,就要为*英雄详情*组件添加一些动画。
|
||
|
||
First, we'll start by importing our animation functions that build our animation triggers,
|
||
control state and manage transitions between states. We'll use these functions to add transitions
|
||
to our route component as it moves between states our application view. We'll also import the
|
||
`HostBinding` decorator for binding to our route component.
|
||
|
||
首先,我们从导入动画函数开始,它们用于构建动画触发器,以控制状态和管理状态之间的转场。
|
||
我们使用这些函数来把转场效果添加到路由组件中,这样当应用视图的多个状态之间发生转移时,就会触发动画。
|
||
我们还要导入`HostBinding`装饰器来绑定到路由组件。
|
||
|
||
+makeExcerpt('app/heroes/hero-detail.component.ts (animation imports)', 'route-animation-imports')
|
||
|
||
:marked
|
||
Next, we'll use a **host binding** for route animations named *@routeAnimation*. There is nothing special
|
||
about the choice of the binding name, but since we are controlling route animation, we'll go with `routeAnimation`.
|
||
The binding value is set to `true` because we only care about the `:enter` and `:leave` states which are
|
||
[entering and leaving](../api/core/index/transition-function.html#transition-aliases-enter-and-leave-) transition aliases.
|
||
|
||
接下来,我们将对名叫`@routeAnimation`的路由动画使用**宿主绑定(HostBinding)**。选择绑定名时没什么特别的要求,但是由于我们是在控制路由的动画,所以把它叫做`routeAnimation`。
|
||
该绑定值被设置为`true`,因为我们只关心`:enter`和`:leave`状态,这些动画状态代表[进场和离开](../api/core/index/transition-function.html#transition-aliases-enter-and-leave-)。
|
||
|
||
|
||
We'll also add some display and positioning bindings for styling.
|
||
|
||
我们还将为样式添加一些显示和位置绑定。
|
||
|
||
+makeExcerpt('app/heroes/hero-detail.component.ts (route animation binding)', 'route-animation-host-binding')
|
||
|
||
:marked
|
||
Now we can build our animation trigger, which we'll call *routeAnimation* to match the binding we previously
|
||
setup. We'll use the **wildcard state** that matches any animation state our route component is in, along with
|
||
two *transitions*. One transition animates the component as it enters the application view (`:enter`), while the other
|
||
animates the component as it leaves the application view (`:leave`).
|
||
|
||
现在,我们可以构建动画触发器了,我们称之为*routeAnimation*来匹配我们以前定义的绑定。
|
||
我们的使用**通配符状态**,它们匹配我们这个路由组件的任意动画状态,
|
||
后面是两个*转场动画*。一个转场动画(`:enter`)在组件进入应用视图时触发,
|
||
另一个(`:leave`)在离开时触发。
|
||
|
||
|
||
We could add different transitions to different route components depending on our needs. We'll just animate our `HeroDetailComponent` for this milestone.
|
||
|
||
如果需要,我们还可以为其它路由组件添加不同的转场动画。在这个里程碑中我们只为`HeroDetailComponent`添加动画。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Using route animations on individual components is something we don't want to do throughout our entire application.
|
||
It would be better to animate routes based on **route paths**, a topic to cover in a future update to this chapter.
|
||
|
||
在整个应用程序中,我们并不想在每个组件中都使用路由动画。
|
||
我们认为基于**路由路径**进行路由动画会更好一些,本章将来的更新中会涉及到这个主题。
|
||
|
||
:marked
|
||
Our route component animation looks as such:
|
||
|
||
我们的路由动画看起来像这样:
|
||
|
||
+makeExcerpt('app/heroes/hero-detail.component.ts (route animation)', 'route-animation')
|
||
|
||
:marked
|
||
Simply stated, our `HeroDetailComponent` will ease in from the left when routed to and will slide down when navigating away.
|
||
We could add more complex animations here, but we'll leave our `HeroDetailComponent` as is for now.
|
||
|
||
简单的说,当进入路由时,`HerodetailComponent`组件会从左侧弹入,离开时会向下方滑出。
|
||
这里我们还可以添加更复杂的动画,不过我们现在就先不这么做了。
|
||
|
||
h3#merge-hero-routes Import hero module into AppModule
|
||
h3#merge-hero-routes 把hero模块导入到AppModule中
|
||
:marked
|
||
Our heroes feature module is ready, but application doesn't know about our heroes module yet.
|
||
We'll need to import it into the `AppModule` we defined in `app.module.ts`.
|
||
|
||
英雄特性已经完工,但是应用还不知道我们的英雄模块。
|
||
我们得把它导入我们在`app.module.ts`中导入的`AppModule`中。
|
||
|
||
Update `app.module.ts` as follows:
|
||
|
||
像这样修改`app.module.ts`:
|
||
|
||
+makeExcerpt('app/app.module.3.ts (heroes module import)', 'hero-import')
|
||
|
||
:marked
|
||
We imported the `HeroesModule` and added it to our `AppModule`'s `imports`.
|
||
|
||
我们导入了`HeroesModule`,并把它加入了`AppModule`的`imports`中。
|
||
|
||
We removed the `HeroListComponent` from the `AppModule`'s `declarations` because its being provided by the `HeroesModule`
|
||
now. This is important because there can be only **one** owner for a declared component. In our case, the `Heroes` module is
|
||
the owner of the `Heroes` components and is making them available to the `AppModule`.
|
||
|
||
我们从`AppModule`的`declarations`中移除了`HeroListComponent`,因为它现在改有`HeroesModule`提供了。
|
||
这一步很重要,因为一个组件只能有**一个**所有者。现在这种情况下,`Heroes`模块应该是`Heroes`组件的所有者,并让它可用于`AppModule`中。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Routes provided by feature modules will be combined together into their imported module's routes by
|
||
the router. This allows us to continue defining our feature module routes without
|
||
modifying our main route configuration.
|
||
|
||
由特性模块提供的路由将会被路由器和它们导入的模块提供的路由组合在一起。这让我们可以继续定义特性路由,而不用修改主路由配置。
|
||
|
||
:marked
|
||
As a result, the `AppModule` no longer has specific knowledge of the hero feature, its components, or its route details.
|
||
We can evolve the hero feature with more components and different routes.
|
||
That's a key benefit of creating a separate module for each feature area.
|
||
|
||
这种调整的结果是:`app.module.ts`不再具有任何有关英雄特性的特殊知识,关于它的组件或路由的细节。
|
||
这个“英雄特性区”可以演化出更多的组件和不同的路由。
|
||
这是为每个特性区创建一个独立模块带来的核心优势。
|
||
|
||
Since our `Heroes` routes are defined within our feature module, we can also remove our initial `heroes` route from the `app-routing.module.ts`.
|
||
|
||
由于`Heroes`路由被定义在了我们的特性模块中,我们也可以从`app-routing.module.ts`中移除当初的`heroes`路由了。
|
||
|
||
|
||
+makeExcerpt('app/app-routing.module.2.ts (v2)', '')
|
||
|
||
:marked
|
||
### Heroes App Wrap-up
|
||
|
||
### “英雄”应用总结
|
||
|
||
We've reached the second milestone in our router education.
|
||
|
||
我们已经抵达了路由教程的第二个里程碑。
|
||
|
||
We've learned how to
|
||
|
||
我们学到了如何:
|
||
|
||
* organize our app into *feature areas*
|
||
|
||
* 把应用组织成一些*特性区*
|
||
|
||
* navigate imperatively from one component to another
|
||
|
||
* 命令式的从一个组件导航到另一个
|
||
|
||
* pass information along in route parameters and subscribe to them in our component
|
||
|
||
* 通过路由参数传递信息,并在组件中订阅它们。
|
||
|
||
* import our feature area NgModule into our `AppModule`
|
||
|
||
* 在`AppModule`中导入特征区的`NgModule`。
|
||
|
||
* apply animations to our route component
|
||
|
||
* 把动画应用到路由组件上
|
||
|
||
After these changes, the folder structure looks like this:
|
||
|
||
做完这些修改之后,目录结构看起来就像这样:
|
||
|
||
.filetree
|
||
.file router-sample
|
||
.children
|
||
.file app
|
||
.children
|
||
.file heroes
|
||
.children
|
||
.file hero-detail.component.ts
|
||
.file hero-list.component.ts
|
||
.file hero.service.ts
|
||
.file heroes.module.ts
|
||
.file heroes-routing.module.ts
|
||
.file app.component.ts
|
||
.file app.module.ts
|
||
.file app-routing.module.ts
|
||
.file crisis-list.component.ts
|
||
.file main.ts
|
||
.file node_modules ...
|
||
.file index.html
|
||
.file package.json
|
||
.file styles.css
|
||
.file tsconfig.json
|
||
:marked
|
||
<a id="heroes-app-code"></a>
|
||
|
||
### The Heroes App code
|
||
|
||
### 英雄应用的源码
|
||
|
||
Here are the relevant files for this version of the sample application.
|
||
|
||
这里是当前版本的范例程序相关文件。
|
||
|
||
+makeTabs(
|
||
`router/ts/app/app.component.1.ts,
|
||
router/ts/app/app.module.3.ts,
|
||
router/ts/app/app-routing.module.3.ts,
|
||
router/ts/app/heroes/hero-list.component.ts,
|
||
router/ts/app/heroes/hero-detail.component.ts,
|
||
router/ts/app/heroes/hero.service.ts,
|
||
router/ts/app/heroes/heroes.module.ts,
|
||
router/ts/app/heroes/heroes-routing.module.ts`,
|
||
null,
|
||
`app.component.ts,
|
||
app.module.ts,
|
||
app-routing.module.ts,
|
||
hero-list.component.ts,
|
||
hero-detail.component.ts,
|
||
hero.service.ts,
|
||
heroes.module.ts,
|
||
heroes-routing.module.ts`)
|
||
|
||
.l-main-section#crisis-center-feature
|
||
:marked
|
||
## Milestone #4: The Crisis Center
|
||
|
||
## 里程碑#4:危机中心
|
||
|
||
The *Crisis Center* is a fake view at the moment. Time to make it useful.
|
||
|
||
此刻,*危机中心*还只是一个假视图,该让它有用点了!
|
||
|
||
The new *Crisis Center* begins as a virtual copy of the *Heroes* module.
|
||
We create a new `app/crisis-center` folder, copy the Hero files,
|
||
and change every mention of "hero" to "crisis".
|
||
|
||
新的*危机中心*从*英雄*模块的一个拷贝开始。我们创建新的`app/crisis-center`目录,把英雄区的文件拷贝过去,并把所有的“hero”修改“crisis”。
|
||
|
||
A `Crisis` has an `id` and `name`, just like a `Hero`
|
||
The new `CrisisListComponent` displays lists of crises.
|
||
When the user selects a crisis, the app navigates to the `CrisisDetailComponent`
|
||
for display and editing of the crisis name.
|
||
|
||
`Crisis`有一个`id`和一个`name`,就像`Hero`一样。新的`CrisisListComponent`显示危机列表。如果用户选择了一个危机,该应用就会导航到`CrisisDetailComponent`,用于显示和编辑危机的名字。
|
||
|
||
Voila, another feature module!
|
||
|
||
真棒!另一个特性模块诞生了!
|
||
|
||
There's no point to this exercise unless we can learn something.
|
||
We do have new ideas and techniques in mind:
|
||
|
||
除非我们能学到点新东西,否则这种练习就没啥亮点。
|
||
不过,我们已经有了一些新主意和新技巧:
|
||
|
||
* We'd like our route URLs to branch in to child route trees that reflect the component trees in our feature areas.
|
||
|
||
* 我们希望把这些路由地址组织成一棵子路由树,它应该能反映本特性区中组件树的结构。
|
||
|
||
* The application should navigate to the *Crisis Center* by default.
|
||
|
||
* 该应用应该默认导航到*危机中心*路由。
|
||
|
||
* The router should prevent navigation away from the detail view while there are pending changes.
|
||
|
||
* 当还有未处理的更改时,路由器应该阻止从这个详情视图导航出去。
|
||
|
||
* The user should be able to cancel unwanted changes.
|
||
|
||
* 用户应该能取消不想要的修改。
|
||
|
||
* The router should block access to certain features until the user logs-in.
|
||
|
||
* 如果用户尚未登录,路由器就应该阻止它访问某些特性。
|
||
|
||
We'll address all of these issues in the *Crisis Center*
|
||
starting with the introduction of **child routes**
|
||
|
||
我们要在*危机中心*中处理好所有这些问题。
|
||
那就先从对**子路由**的介绍开始吧。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
We'll leave *Heroes* in its less-than-perfect state to
|
||
serve as a contrast with what we believe to be a superior *Crisis Center* design.
|
||
|
||
我们将把*英雄*特性区保留在不够完美的状态,以便与*危机中心*进行对比,我们相信后者是一个更好的设计。
|
||
|
||
:marked
|
||
### A Crisis Center with child routes
|
||
|
||
### 带子路由的“危机中心”
|
||
|
||
We'll organize the *Crisis Center* to conform to the following recommended pattern for Angular applications.
|
||
|
||
我们将按照下列模式组织*危机中心*的目录结构。
|
||
|
||
* each feature area in its own folder within a defined module
|
||
|
||
* 每个特性区位于它自己的定义了模块的目录中
|
||
|
||
* each area with its own area root component
|
||
|
||
* 每个特性区有它自己的根组件
|
||
|
||
* each area root component with its own router-outlet and child routes
|
||
|
||
* 每个特性区的根组件有它自己的`<router-outlet>`及其子路由
|
||
|
||
* area routes rarely (if ever) cross
|
||
|
||
* 特性区的路由很少交叉(如果还有的话)
|
||
|
||
If we had many feature areas, their component trees might look like this:
|
||
|
||
如果我们有更多特性区,它们的组件树看起来是这样的:
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/router/component-tree.png' alt="Component Tree" )
|
||
|
||
a#child-routing-component
|
||
:marked
|
||
### Child Routing Component
|
||
|
||
### 子路由组件
|
||
|
||
Add the following `crisis-center.component.ts` to the `crisis-center` folder:
|
||
|
||
往`crisis-center`目录下添加下列`crisis-center.component.ts`文件:
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-center.component.ts (minus imports)', 'minus-imports')
|
||
|
||
:marked
|
||
The `CrisisCenterComponent` is much like the `AppComponent` shell.
|
||
|
||
`CrisisCenterComponent`和壳组件`AppComponent`很像。
|
||
|
||
* It is the root of the *Crisis Center* area
|
||
just as `AppComponent` is the root of the entire application.
|
||
|
||
* 它是*危机中心*特性区的根组件,同样,`AppComponent`是整个应用的根组件。
|
||
|
||
* It is a shell for the crisis management feature area
|
||
just as the `AppComponent` is a shell to manage the high-level workflow.
|
||
|
||
* 它是危机管理区的壳组件,同样,`AppComponent`也是用来管理高层工作流的壳组件。
|
||
|
||
* It is dead simple — simpler even than the `AppComponent` template.
|
||
It has no content, no links, just a `<router-outlet>` for the *Crisis Center* child views.
|
||
|
||
* 它太简单了,甚至比`AppComponent`的模板还要简单。
|
||
它没有内容、没有链接,只有一个用来存放*危机中心*子视图的`<router-outlet>`指令。
|
||
|
||
Unlike `AppComponent` (and most other components), it **lacks a selector**.
|
||
It doesn't need one. We don't *embed* this component in a parent template.
|
||
We *navigate* to it from the outside, via the router.
|
||
|
||
但与`AppComponent`(以及大多数其它组件)不同的是,它**缺少一个选择器(`selector`)**。
|
||
它不需要。我们不会把该组件嵌入到父模板中,只会通过路由器从外部*导航*到它。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
We *can* give it a selector. There's no harm in it.
|
||
Our point is that we don't *need* one because we only *navigate* to it.
|
||
|
||
我们**可以**为它指定一个选择器。这么做没有什么坏处。
|
||
这里的观点是,我们**不需要**选择器,因为我们只能**导航*到它。
|
||
|
||
:marked
|
||
### Child Route Configuration
|
||
|
||
### 子路由配置
|
||
|
||
The `CrisisCenterComponent` is a *Routing Component* like the `AppComponent`.
|
||
It has its own `RouterOutlet` and its own child routes.
|
||
|
||
`CrisisCenterComponent`是一个像`AppComponent`一样的*路由组件*。
|
||
它有自己的`RouterOutlet`和自己的子路由。
|
||
|
||
Add the following `crisis-center-home.component.ts` to the `crisis-center` folder.
|
||
|
||
把下面的`crisis-center-home.component.ts`文件添加到`crisis-center`目录中。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-center-home.component.ts (minus imports)', 'minus-imports')
|
||
|
||
:marked
|
||
We create a `crisis-center-routing.module.ts` file as we did the `heroes-routing.module.ts` file.
|
||
But this time we define **child routes** *within* the parent `crisis-center` route.
|
||
|
||
像`heroes-routing.module.ts`文件一样,我们也创建一个`crisis-center-routing.module.ts`。
|
||
但这次,我们要把**子路由**定义在父路由`crisis-center`中。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-center-routing.module.1.ts (Routes)', 'routes')
|
||
|
||
:marked
|
||
Notice that the parent `crisis-center` route has a `children` property
|
||
with a single route containing our `CrisisListComponent`. The `CrisisListComponent` route
|
||
also has a `children` array with two routes.
|
||
|
||
注意,父路由`crisis-center`有一个`children`属性,它有一个包含`CrisisListComponent`的路由。
|
||
`CrisisListModule`路由还有一个带两个路由的`children`数组。
|
||
|
||
These two routes navigate to the two *Crisis Center* child components,
|
||
`CrisisCenterHomeComponent` and `CrisisDetailComponent`.
|
||
|
||
这两个路由导航到了*危机中心*的两个子组件:`CrisisCenterHomeComponent`和`CrisisDetailComponent`。
|
||
|
||
There are some *important differences* in the treatment of these routes.
|
||
|
||
对这些路由的处理中有一些*重要的不同*。
|
||
|
||
The router displays the components of these routes in the `RouterOutlet`
|
||
of the `CrisisCenterComponent`, not in the `RouterOutlet` of the `AppComponent` shell.
|
||
|
||
路由器会把这些路由对应的组件放在`CrisisCenterComponent`的`RouterOutlet`中,而不是`AppComponent`壳组件中的。
|
||
|
||
The `CrisisListComponent` contains the crisis list and a `RouterOutlet` to
|
||
display the `Crisis Center Home` and `Crisis Detail` route components.
|
||
|
||
`CrisisListComponent`包含危机列表和一个`RouterOutlet`,用以显示`Crisis Center Home`和`Crisis Detail`这两个路由组件。
|
||
|
||
The `Crisis Detail` route is a child of the `Crisis List`. Since the router [reuses components](#reuse)
|
||
by default, the `Crisis Detail` component will be re-used as we select different crises.
|
||
|
||
`Crisis Detail`路由是`Crisis List`的子路由。由于路由器默认会[复用组件](#reuse),因此当我们选择了另一个危机时,`CrisisDetailComponent`会被复用。
|
||
|
||
In contrast, back in the `Hero Detail` route, the component was recreated each time we selected a different hero.
|
||
|
||
作为对比,回到`Hero Detail`路由时,每当我们选择了不同的英雄时,该组件都会被重新创建。
|
||
|
||
At the top level, paths that begin with `/` refer to the root of the application.
|
||
But these are child routes.
|
||
They *extend* the path of the parent route.
|
||
With each step down the route tree, we add a slash followed by the route path (unless the route path is _empty_).
|
||
|
||
在顶级,以`/`开头的路径指向的总是应用的根。
|
||
但这里是子路由。
|
||
它们是在父路由路径的基础上做出的扩展。
|
||
在路由树中每深入一步,我们就会在该路由的路径上添加一个斜线`/`(除非该路由的路径是*空的*)。
|
||
|
||
For example, the parent path to the `CrisisCenterComponent` is `/crisis-center`
|
||
The router appends these child paths to the parent path to the `CrisisCenterComponent` (`/crisis-center`).
|
||
|
||
例如,`CrisisCenterComponent`的路径是`/crisis-center`。
|
||
路由器就会把这些子路由的路径中添加上到父路由`CrisisCenterComponent`的路径(`/crisis-center`)。
|
||
|
||
* to navigate to the `CrisisCenterHomeComponent`, the full URL is `/crisis-center` (`/crisis-center` + `''` + `''`).
|
||
|
||
* 要导航到`CrisisCenterHomeComponent`,完整的URL是`/crisis-center` (`/crisis-center` + `''` + `''`)。
|
||
|
||
* to navigate to the `CrisisDetailComponent` for a crisis with `id=2`, the full URL is
|
||
`/crisis-center/2` (`/crisis-center` + `''` + `'/2'`).
|
||
|
||
* 要导航到`CrisisDetailComponent`以展示`id=2`的危机,完整的URL是`/crisis-center/2` (`/crisis-center` + `''` + `'/2'`)。
|
||
|
||
The absolute URL for the latter example, including the origin, is
|
||
|
||
本例子中的绝对URL,包含源站部分,就是:
|
||
|
||
code-example.
|
||
localhost:3000/crisis-center/2
|
||
|
||
:marked
|
||
Here's the complete `crisis-center-routing.module.ts` file with its imports.
|
||
|
||
这里是完整的`crisis-center.routing.ts`及其导入语句。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-center-routing.module.1.ts', '')
|
||
|
||
h3#import-crisis-module Import crisis center module into the AppModule routes
|
||
|
||
h3#import-crisis-module 把危机中心模块导入`AppModule`的路由中
|
||
|
||
:marked
|
||
As with the `Heroes` module, we must import the `Crisis Center` module into the `AppModule`:
|
||
|
||
像`Heroes`模块中一样,我们必须把`危机中心`模块导入`AppModule`中:
|
||
|
||
+makeExcerpt('app/app.module.4.ts (import CrisisCenterModule)', 'crisis-center-module')
|
||
|
||
:marked
|
||
We also remove the initial crisis center route from our `app-routing.module.ts`. Our routes
|
||
are now being provided by our `HeroesModule` and our `CrisisCenter` feature modules. We'll keep our `app-routing.module.ts` file
|
||
for general routes which we'll cover later in the chapter.
|
||
|
||
我们还从`app.routing.ts`中移除了危机中心的初始路由。我们的路由现在是由`HeroesModule`和`CrisisCenter`特性模块提供的。
|
||
我们将保持`app.routing.ts`文件中只有通用路由,本章稍后会讲解它。
|
||
|
||
+makeExcerpt('app/app-routing.module.3.ts (v3)', '')
|
||
|
||
a#redirect
|
||
|
||
:marked
|
||
### Redirecting routes
|
||
|
||
### 重定向路由
|
||
|
||
When the application launches, the initial URL in the browser bar is something like:
|
||
|
||
当应用启动时,浏览器地址栏的初始URL是这样的:
|
||
|
||
code-example.
|
||
localhost:3000
|
||
|
||
:marked
|
||
That doesn't match any of our configured routes which means that our application won't display any component when it's launched.
|
||
The user must click one of the navigation links to trigger a navigation and display something.
|
||
|
||
它无法匹配上我们配置过的任何路由,这意味着在应用启动的时候它将不会显示任何组件。
|
||
用户必须点击一个导航链接来触发导航并显示点什么。
|
||
|
||
We prefer that the application display the list of crises as it would if the user clicked the "Crisis Center" link or pasted `localhost:3000/crisis-center/` into the address bar.
|
||
This is our intended default route.
|
||
|
||
当用户点击了“Crisis Center”链接或者在地址栏粘贴`localhost:3000/crisis-center/`时,我们更希望该应用能直接显示危机列表。这就是默认路由。
|
||
|
||
The preferred solution is to add a `redirect` route that transparently translates from the initial relative URL (`''`)
|
||
to the desired default path (`/crisis-center`):
|
||
|
||
首选的解决方案是添加一个`redirect`路由,它会把初始的相对URL(`''`)悄悄翻译成默认路径(`/crisis-center`)。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-center-routing.module.2.ts' , 'redirect', '')
|
||
|
||
:marked
|
||
A redirect route requires a `pathMatch` property to tell the router how to match a URL to the path of a route.
|
||
In this app, the router should select the route to the `CrisisListComponent` when the *entire URL* matches `''`,
|
||
so we set the `pathMatch` value to `'full'`.
|
||
|
||
“重定向(redirect)路由”需要一个`pathMatch`属性来告诉路由器如何把URL和路由中的路径进行匹配。
|
||
本应用中,路由器应该只有在*完整的URL*匹配`''`时才选择指向`CrisisListComponent`的路由,因此,我们把`pathMatch`的值设置为`'full'`。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
Technically, `pathMatch = 'full'` results in a route hit when the *remaining*, unmatched segments of the URL match `''`.
|
||
In our example, the redirect is at the top level of the route configuration tree so the *remaining* URL and the *entire* URL
|
||
are the same thing.
|
||
|
||
从原理上说,`pathMatch = 'full'`导致路由器尝试用URL中*剩下的*、未匹配过的片段去匹配`''`。
|
||
在这个例子中,重定向发生在路由配置树的顶级,所以*剩下的*URL和*完整的*URL是完全一样的。
|
||
|
||
The other possible `pathMatch` value is `'prefix'` which tells the router
|
||
to match the redirect route when the *remaining* URL ***begins*** with the redirect route's _prefix_ path.
|
||
|
||
`pathMatch`的另一个可能的值是`'prefix'`,这会告诉路由器去匹配*剩下的*URL是否以这个“重定向路由”的*前缀*路径***开头***。
|
||
|
||
That's not what we want to do here. If the `pathMatch` value were `'prefix'`,
|
||
_every_ URL would match `''`.
|
||
We could never navigate to `/crisis-center/1` because the redirect route would match first and
|
||
send us to the `CrisisListComponent`.
|
||
|
||
显然,在这里我们并不想这样。如果`pathMatch`的值是`'prefix'`,_每个_URL都会匹配`''`。
|
||
因为这个“重定向路由”会首先匹配上并把我们引向`CrisisListComponent`,所以我们就永远无法导航到`/crisis-center/1`了。
|
||
|
||
We should redirect to the `CrisisListComponent` _only_ when the _entire (remaining)_ url is `''`.
|
||
|
||
_只有_当_完整的(剩余的)_URL是`''`时,我们才应该重定向到`CrisisListComponent`。
|
||
|
||
Learn more in Victor Savkin's blog
|
||
[post on redirects](http://victorsavkin.com/post/146722301646/angular-router-empty-paths-componentless-routes).
|
||
|
||
要学习更多,请参见Victor Savkin的博客中
|
||
[关于重定向的帖子](http://victorsavkin.com/post/146722301646/angular-router-empty-paths-componentless-routes).
|
||
|
||
We'll discuss redirects in more detail in a future update to this chapter.
|
||
|
||
我们将在未来的更新中深入讨论重定向问题。
|
||
|
||
:marked
|
||
The updated route definitions look like this:
|
||
|
||
修改过的路由定义看起来是这样的:
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-center-routing.module.2.ts (routes v2)' , 'routes')
|
||
|
||
.l-main-section
|
||
h2#relative-navigation Relative Navigation
|
||
h2#relative-navigation 相对导航
|
||
|
||
:marked
|
||
While building out our *Crisis Center* feature, we've navigated to the
|
||
*Crisis Detail* route using an **absolute path** that begins with a **slash**.
|
||
This navigation starts from the top of our route configuration to find the
|
||
matching path to our route.
|
||
|
||
当构建*危机中心*特性时,我们将使用以**斜线**开头的**绝对路径**来导航到*危机详情*路由。
|
||
这次导航会从路由配置的顶部开始查找路由,以匹配路径。
|
||
|
||
We could continue to use absolute paths to navigate inside our *Crisis Center*
|
||
feature, but that makes our links very rigid. If we changed our parent `/crisis-center`
|
||
path, we would have to change our link parameters array.
|
||
|
||
我们可以在*危机中心*特性区继续使用绝对路径进行导航,但这会让我们的链接过于死板。
|
||
如果更改了父路由路径`/crisis-center`,我们就不得不到处更改链接参数数组。
|
||
|
||
We can make our links more flexible by using **relative** navigation with the router.
|
||
|
||
通过让路由器使用**相对**导航的方式,我们可以让链接更富有弹性。
|
||
|
||
* The full path to the route is not required.
|
||
* 不再需要到路由的完整路径。
|
||
* Navigation within our feature area remains intact if the parent route path is changed.
|
||
* 当修改父路由路径时在我们的特性区中导航时不需要做任何改动。
|
||
* The *link parameters array* only contains navigation relative to the current URL.
|
||
* *链接参数数组*只包含到当前URL的相对导航信息。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
The **link parameters array** supports a directory-like syntax for relative navigation.
|
||
|
||
**链接参数数组**通过目录式语法支持相对导航。
|
||
|
||
`./` or `no leading slash` is relative to the current level.
|
||
|
||
`./`或“无前导斜线”时表示相当于当前级别。
|
||
|
||
`../` to go up one level in the route path.
|
||
|
||
`../`表示在路由路径中往上走一级。
|
||
|
||
The relative navigation syntax can be used in combination with a *path*. If we wanted to navigate
|
||
from one route path to another sibling route path we could use `../path` convention to go up
|
||
one level and down to the sibling route path.
|
||
|
||
相对导航的语法可以和*路径*组合在一起,如果我们要从一个路由路径导航到一个兄弟路由路径,
|
||
可以使用`../path`来简便的导航到上一级然后再进入兄弟路由路径。
|
||
|
||
:marked
|
||
In order to navigate relatively using the `Router` service, we use the `ActivatedRoute`
|
||
to give the router knowledge of where we are in the *RouterState*, which is our tree of
|
||
activated routes. We do this by adding an object as the second argument in our
|
||
`router.navigate` method after the *link parameters array* specifying the **relativeTo** property.
|
||
We set the `relativeTo` property to our `ActivatedRoute` and the router will merge our
|
||
navigation information into to the current URL.
|
||
|
||
要使用`Router`进行相对导航,可以使用`ActivatedRoute`来告诉路由器我们正在*RouterState*中的什么地方,*RouterState*是激活路由组成的树。
|
||
要做到这一点,我们可以为`router.navigate`方法中*链接参数数组*后的对象型参数指定**relativeTo**属性。
|
||
只要把这个`relativeTo`属性设置为我们的`ActivatedRoute`,路由器就会把我们的导航信息和当前URL合并在一起。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
When using router's `navigateByUrl` method, the navigation is **always** absolute.
|
||
|
||
当使用路由器的`navigateByUrl`方法时,导航**总是**绝对的。
|
||
|
||
:marked
|
||
### Navigate to Crisis Detail relatively
|
||
|
||
### 用相对方式导航到危机详情
|
||
|
||
Let's update our *Crisis List* `onSelect` method to use relative navigation so we don't have
|
||
to start from the top of our route configuration. We've already injected the `ActivatedRoute`
|
||
into our constructor that we'll need for the relative navigation.
|
||
|
||
我们来把*危机列表*中的`onSelect`方法改成相对导航,以便我们不必从路由配置的顶部开始。
|
||
我们已经把相对导航所需的`ActivatedRoute`注入到了构造函数中。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-list.component.1.ts (constructor)', 'relative-navigation-ctor')
|
||
|
||
:marked
|
||
When we visit the *Crisis Center*, our path is `/crisis-center`, so we just want to add the `id` of the *Crisis Center*
|
||
to our existing path. When the router navigates, it will use the current path `/crisis-center`,
|
||
adding on our `id`. If our `id` were `1`, the resulting path would be `/crisis-center/1`.
|
||
|
||
当我们访问*危机中心*时,当前路径是`/crisis-center`,所以我们只要把*危机*的`id`添加到现有路径中就可以了。当路由器导航时,它使用当前路径`/crisis-center`并追加上此`id`。如果`id`为`1`,结果路径就是`/crisis-center/1`。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-list.component.ts (relative navigation)', 'relative-navigation')
|
||
|
||
:marked
|
||
We'll also update the *Crisis Detail* component to navigate back to our *Crisis Center* list. We want to go back up a level
|
||
in the path, so we use to the `../` syntax. If our current `id` is `1`, the resulting path coming from `/crisis-center/1`
|
||
would be `/crisis-center`.
|
||
|
||
我们还要修改*危机详情*组件以便导航回*危机中心*列表。我们得回到路径的上一级,所以我们使用`../`语法。如果当前`id`是`1`,那么结果路径就会从`/crisis-center/1`变成`/crisis-center`。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-detail.component.1.ts (relative navigation)', 'relative-navigation')
|
||
|
||
:marked
|
||
If we are using a `RouterLink` to navigate instead of the `Router` service, we can use the **same**
|
||
link parameters array, but we don't have to provide the object with the `relativeTo` property. The `ActivatedRoute`
|
||
is implicit in the `RouterLink` directive.
|
||
|
||
如果我们正在使用`RouterLink`进行导航,而不是`Router`服务,仍然可以使用**相同的**链接参数数组,不过我们不用提供带`relativeTo`属性的对象。在`RouterLink`指令中`ActivatedRoute`是默认的。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-list.component.1.ts (relative routerLink)', 'relative-navigation-router-link')
|
||
|
||
<a id="guards"></a>
|
||
.l-main-section
|
||
h2#guards Route Guards
|
||
|
||
h2#guards 路由守卫
|
||
|
||
:marked
|
||
## Milestone #5: Route Guards
|
||
## 里程碑#5:路由守卫
|
||
|
||
At the moment, *any* user can navigate *anywhere* in the application *anytime*.
|
||
|
||
现在,*任何用户*都能在*任何时候*导航到*任何地方*。
|
||
|
||
That's not always the right thing to do.
|
||
|
||
但有时候这样是不对的。
|
||
|
||
* Perhaps the user is not authorized to navigate to the target component.
|
||
|
||
* 该用户可能无权导航到目标组件。
|
||
|
||
* Maybe the user must login (*authenticate*) first.
|
||
|
||
* 可能用户得先登录(认证)。
|
||
|
||
* Maybe we should fetch some data before we display the target component.
|
||
|
||
* 在显示目标组件前,我们可能得先获取某些数据。
|
||
|
||
* We might want to save pending changes before leaving a component.
|
||
|
||
* 在离开组件前,我们可能要先保存修改。
|
||
|
||
* We might ask the user if it's OK to discard pending changes rather than save them.
|
||
|
||
* 我们可能要询问用户:你是否要放弃本次更改,而不用保存它们?
|
||
|
||
We can add ***guards*** to our route configuration to handle these scenarios.
|
||
|
||
我们可以往路由配置中添加***守卫***,来处理这些场景。
|
||
|
||
A guard's return value controls the router's behavior:
|
||
|
||
守卫返回一个值,以控制路由器的行为:
|
||
|
||
* if it returns `true`, the navigation process continues
|
||
|
||
* 如果它返回`true`,导航过程会继续
|
||
|
||
* if it returns `false`, the navigation process stops and the user stays put
|
||
|
||
* 如果它返回`false`,导航过程会终止,且用户会留在原地。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
The guard can also tell the router to navigate elsewhere, effectively canceling the current navigation.
|
||
|
||
守卫还可以告诉路由器导航到别处,这样也取消当前的导航。
|
||
|
||
:marked
|
||
The guard *might* return its boolean answer synchronously.
|
||
But in many cases, the guard can't produce an answer synchronously.
|
||
The guard could ask the user a question, save changes to the server, or fetch fresh data.
|
||
These are all asynchronous operations.
|
||
|
||
守卫*可以*用同步的方式返回一个布尔值。但在很多情况下,守卫无法用同步的方式给出答案。
|
||
守卫可能会向用户问一个问题、把更改保存到服务器,或者获取新数据,而这些都是异步操作。
|
||
|
||
Accordingly, a routing guard can return an `Observable<boolean>` or a `Promise<boolean>` and the
|
||
router will wait for the observable to resolve to `true` or `false`.
|
||
|
||
因此,路由的守卫可以返回一个`Observable<boolean>`或`Promise<boolean>`,并且路由器会等待这个可观察对象被解析为`true`或`false`。
|
||
|
||
The router supports multiple kinds of guards:
|
||
|
||
路由器支持多种守卫:
|
||
|
||
1. [CanActivate](../api/router/index/CanActivate-interface.html) to mediate navigation *to* a route.
|
||
|
||
1. 用[CanActivate](../api/router/index/CanActivate-interface.html)来处理导航*到*某路由的情况。
|
||
|
||
2. [CanActivateChild](../api/router/index/CanActivateChild-interface.html) to mediate navigation *to* a child route.
|
||
|
||
2. 用[CanActivateChild](../api/router/index/CanActivateChild-interface.html)处理导航*到*子路由的情况。
|
||
|
||
3. [CanDeactivate](../api/router/index/CanDeactivate-interface.html) to mediate navigation *away* from the current route.
|
||
|
||
3. 用[CanDeactivate](../api/router/index/CanDeactivate-interface.html)来处理从当前路由*离开*的情况。
|
||
|
||
4. [Resolve](../api/router/index/Resolve-interface.html) to perform route data retrieval *before* route activation.
|
||
|
||
4. 用[Resolve](../api/router/index/Resolve-interface.html)在路由激活*之前*获取路由数据。
|
||
|
||
5. [CanLoad](../api/router/index/CanLoad-interface.html) to mediate navigation *to* a feature module loaded _asynchronously_.
|
||
|
||
5. 用[CanLoad](../api/router/index/CanLoad-interface.html)来处理*异步*导航到某特性模块的情况。
|
||
|
||
:marked
|
||
We can have multiple guards at every level of a routing hierarchy.
|
||
The router checks the `CanDeactivate` and `CanActivateChild` guards first, from deepest child route to the top.
|
||
Then it checks the `CanActivate` guards from the top down to the deepest child route. If the feature module
|
||
is loaded asynchronously, the `CanLoad` guard is checked before the module is loaded.
|
||
If _any_ guard returns false, pending guards that have not completed will be canceled,
|
||
and the entire navigation is canceled.
|
||
|
||
在分层路由的每个级别上,我们都可以设置多个守卫。
|
||
路由器会先按照从最深的子路由由下往上检查的顺序来检查`CanDeactivate`守护条件。
|
||
然后它会按照从上到下的顺序检查`CanActivate`守卫。
|
||
如果_任何_守卫返回`false`,其它尚未完成的守卫会被取消,这样整个导航就被取消了。
|
||
|
||
Let's look at some examples.
|
||
|
||
我们来看一些例子。
|
||
|
||
a#can-activate-guard
|
||
:marked
|
||
### *CanActivate*: requiring authentication
|
||
|
||
### *CanActivate*: 要求认证
|
||
|
||
Applications often restrict access to a feature area based on who the user is.
|
||
We could permit access only to authenticated users or to users with a specific role.
|
||
We might block or limit access until the user's account is activated.
|
||
|
||
应用程序通常会根据访问者来决定是否授予某个特性区的访问权。
|
||
我们可以只对已认证过的用户或具有特定角色的用户授予访问权,还可以阻止或限制用户访问权,直到用户账户激活为止。
|
||
|
||
The `CanActivate` guard is the tool to manage these navigation business rules.
|
||
|
||
`CanActivate`守卫是一个管理这些导航类业务规则的工具。
|
||
|
||
#### Add an admin feature module
|
||
|
||
#### 添加一个“管理”特性模块
|
||
|
||
We intend to extend the Crisis Center with some new *administrative* features.
|
||
Those features aren't defined yet. So we add a new feature module named `AdminModule`.
|
||
We'll follow our same convention by creating an `admin` folder with a feature
|
||
module file, route file and supporting components.
|
||
|
||
我们准备扩展“危机中心”,添加一些新的*管理类*特性。
|
||
这些特性还没有定义过,所以我们先只添加一个名叫`AdminModule`的占位模块。
|
||
我们会遵循与创建`admin`目录中的特性模块文件、路由文件和支持组件时相同的约定。
|
||
|
||
Our admin feature module file structure looks like this:
|
||
|
||
管理特性区的文件是这样的:
|
||
|
||
.filetree
|
||
.file app/admin
|
||
.children
|
||
.file admin-dashboard.component.ts
|
||
.file admin.component.ts
|
||
.file admin.module.ts
|
||
.file admin-routing.module.ts
|
||
.file manage-crises.component.ts
|
||
.file manage-heroes.component.ts
|
||
|
||
:marked
|
||
Our admin feature module contains our `AdminComponent` used for routing within our
|
||
feature module, a dashboard route and two unfinished components to manage crises and heroes.
|
||
|
||
管理特性模块包含`AdminComponent`,它用于在特性模块内的仪表盘路由以及两个尚未完成的用于管理危机和英雄的组件之间进行路由。
|
||
|
||
+makeTabs(
|
||
`router/ts/app/admin/admin-dashboard.component.1.ts,
|
||
router/ts/app/admin/admin.component.ts,
|
||
router/ts/app/admin/admin.module.ts,
|
||
router/ts/app/admin/manage-crises.component.ts,
|
||
router/ts/app/admin/manage-heroes.component.ts
|
||
`,
|
||
null,
|
||
`app/admin/admin-dashboard.component.ts,
|
||
app/admin/admin.component.ts,
|
||
app/admin/admin.module.ts,
|
||
app/admin/manage-crises.component.ts,
|
||
app/admin/manage-heroes.component.ts
|
||
`)
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Since our admin dashboard `RouterLink` is an empty path route in our `AdminModule`, it
|
||
is considered a match to any route within our admin feature area. We only want the `Dashboard`
|
||
link to be active when we visit that route. We've added an additional binding to our `Dashboard` routerLink,
|
||
`[routerLinkActiveOptions]="{ exact: true }"` which will only mark the `./` link as active when
|
||
we navigate the to `/admin` URL and not when we navigate to one the other child routes.
|
||
|
||
由于`AdminModule`中管理仪表盘的`RouterLink`是一个空路径的路由,所以它会匹配到管理特性区的任何路由。但我们只有在访问`Dashboard`路由时才希望该链接被激活。所以我们往`Dashboard`这个routerLink上添加了另一个绑定`[routerLinkActiveOptions]="{ exact: true }"`,这样就只有当我们导航到`/admin`这个URL时才会激活它,而不会在导航到它的某个子路由时。
|
||
|
||
:marked
|
||
Our initial admin routing configuration:
|
||
|
||
我们的初始管理路由配置如下:
|
||
|
||
+makeExcerpt('app/admin/admin-routing.module.1.ts (admin routing)', 'admin-routes')
|
||
|
||
h3#component-less-route <i>Component-Less Route</i>: grouping routes without a component
|
||
h3#component-less-route <i>无组件路由</i>: 不借助组件对路由进行分组
|
||
:marked
|
||
Looking at our child route under the `AdminComponent`, we have a route with a **path** and a **children**
|
||
property but it's not using a **component**. We haven't made a mistake in our configuration, because we can
|
||
use a **component-less** route.
|
||
|
||
来看`AdminComponent`下的子路由,我们有一个带**path**和**children**的子路由,但它没有使用**component**。这并不是配置中的失误,而是在使用**无组件**路由。
|
||
|
||
We want to group our `Crisis Center` management routes under the `admin` path, but we don't need a component
|
||
just to group those routes under an additional `RouterOutlet`. This also allows us to [guard child routes](#can-activate-child-guard).
|
||
|
||
虽然我们希望对`admin`路径下的`危机中心`管理类路由进行分组,但并不需要另一个仅用来分组路由的组件。
|
||
这同时也允许我们[守卫子路由](#can-activate-child-guard)。
|
||
|
||
:marked
|
||
Next, we'll import the `AdminModule` into our `app.module.ts` and add it to the `imports` array
|
||
to register our admin routes.
|
||
|
||
接下来,我们把`AdminModule`导入到`app.module.ts`中,并把它加入`imports`数组中来注册这些管理类路由。
|
||
|
||
+makeExcerpt('app/app.module.4.ts (admin module)', 'admin-module')
|
||
|
||
:marked
|
||
And we add a link to the `AppComponent` shell that users can click to get to this feature.
|
||
|
||
然后我们往壳组件`AppComponent`中添加一个链接,让用户能点击它,以访问该特性。
|
||
|
||
+makeExcerpt('app/app.component.4.ts', 'template')
|
||
|
||
:marked
|
||
#### Guard the admin feature
|
||
|
||
#### 守护“管理特性”区
|
||
|
||
Currently every route within our *Crisis Center* is open to everyone.
|
||
The new *admin* feature should be accessible only to authenticated users.
|
||
|
||
现在“危机中心”的每个路由都是对所有人开放的。这些新的*管理特性*应该只能被已登录用户访问。
|
||
|
||
We could hide the link until the user logs in. But that's tricky and difficult to maintain.
|
||
|
||
我们可以在用户登录之前隐藏这些链接,但这样会有点复杂并难以维护。
|
||
|
||
Instead we'll write a `CanActivate` guard to redirect anonymous users to the login page when they try to reach the admin component.
|
||
|
||
我们换种方式:写一个`CanActivate`守卫,当匿名用户尝试访问管理组件时,把它/她重定向到登录页。
|
||
|
||
This is a general purpose guard — we can imagine other features that require authenticated users —
|
||
so we create an `auth-guard.service.ts` in the application root folder.
|
||
|
||
这是一种具有通用性的守护目标(通常会有其它特性需要登录用户才能访问),所以我们在应用的根目录下创建一个`auth-guard.ts`文件。
|
||
|
||
At the moment we're interested in seeing how guards work so our first version does nothing useful.
|
||
It simply logs to console and `returns` true immediately, allowing navigation to proceed:
|
||
|
||
此刻,我们的兴趣在于看看守卫是如何工作的,所以我们第一个版本没做什么有用的事情。它只是往控制台写日志,并且立即返回`true`,让导航继续:
|
||
|
||
+makeExcerpt('app/auth-guard.service.1.ts')
|
||
|
||
:marked
|
||
Next we open `admin-routing.module.ts `, import the `AuthGuard` class, and
|
||
update the admin route with a `CanActivate` guard property that references it:
|
||
|
||
接下来,打开`crisis-center.routes.ts`,导入`AuthGuard`类,修改管理路由并通过`CanActivate`属性来引用`AuthGuard`:
|
||
|
||
+makeExcerpt('app/admin/admin-routing.module.2.ts (guarded admin route)', 'admin-route')
|
||
|
||
:marked
|
||
Our admin feature is now protected by the guard, albeit protected poorly.
|
||
|
||
我们的管理特性区现在受此守卫保护了,不过这样的保护还不够。
|
||
|
||
#### Teach *AuthGuard* to authenticate
|
||
|
||
#### 教*AuthGuard*进行认证
|
||
|
||
Let's make our `AuthGuard` at least pretend to authenticate.
|
||
|
||
我们先让`AuthGuard`至少能“假装”进行认证。
|
||
|
||
The `AuthGuard` should call an application service that can login a user and retain information about the current user.
|
||
Here's a demo `AuthService`:
|
||
|
||
`AuthGuard`可以调用应用中的一项服务,该服务能让用户登录,并且保存当前用户的信息。下面是一个`AuthService`的示范:
|
||
|
||
+makeExcerpt('app/auth.service.ts')
|
||
|
||
:marked
|
||
Although it doesn't actually log in, it has what we need for this discussion.
|
||
It has an `isLoggedIn` flag to tell us whether the user is authenticated.
|
||
Its `login` method simulates an API call to an external service by returning an observable that resolves successfully after a short pause.
|
||
The `redirectUrl` property will store our attempted URL so we can navigate to it after authenticating.
|
||
|
||
虽然它不会真的进行登录,但足够让我们进行这个讨论了。
|
||
它有一个`isLoggedIn`标志,用来标识是否用户已经登录过了。
|
||
它的`login`方法会仿真一个对外部服务的API调用,返回一个可观察对象(observable)。在短暂的停顿之后,这个可观察对象就会解析成功。
|
||
`redirectUrl`属性将会保存在URL中,以便认证完之后导航到它。
|
||
|
||
Let's revise our `AuthGuard` to call it.
|
||
|
||
我们这就修改`AuthGuard`来调用它。
|
||
|
||
+makeExcerpt('app/auth-guard.service.2.ts (v2)', '')
|
||
|
||
:marked
|
||
Notice that we *inject* the `AuthService` and the `Router` in the constructor.
|
||
We haven't provided the `AuthService` yet but it's good to know that we can inject helpful services into our routing guards.
|
||
|
||
注意,我们把`AuthService`和`Router`服务*注入到*构造函数中。
|
||
我们还没有提供`AuthService`,这里要说明的是:可以往路由守卫中注入有用的服务。
|
||
|
||
This guard returns a synchronous boolean result.
|
||
If the user is logged in, it returns true and the navigation continues.
|
||
|
||
该守卫返回一个同步的布尔值。如果用户已经登录,它就返回`true`,导航会继续。
|
||
|
||
The `ActivatedRouteSnapshot` contains the _future_ route that will be activated and the `RouterStateSnapshot`
|
||
contains the _future_ `RouterState` of our application, should we pass through our guard check.
|
||
|
||
这个`ActivatedRouteSnapshot`包含了_即将_被激活的路由,而`RouterStateSnapshot`包含了该应用_即将_到达的状态。
|
||
它们要通过我们的守卫进行检查。
|
||
|
||
If the user is not logged in, we store the attempted URL the user came from using the `RouterStateSnapshot.url` and
|
||
tell the router to navigate to a login page — a page we haven't created yet.
|
||
This secondary navigation automatically cancels the current navigation; we return `false` just to be clear about that.
|
||
|
||
如果用户还没有登录,我们会用`RouterStateSnapshot.url`保存用户来自的URL并让路由器导航到登录页(我们尚未创建该页)。
|
||
这间接导致路由器自动中止了这次导航,我们返回`false`并不是必须的,但这样可以更清楚的表达意图。
|
||
|
||
#### Add the *LoginComponent*
|
||
|
||
#### 添加*LoginComponent*
|
||
|
||
We need a `LoginComponent` for the user to log in to the app. After logging in, we'll redirect
|
||
to our stored URL if available, or use the default URL.
|
||
There is nothing new about this component or the way we wire it into the router configuration.
|
||
|
||
我们需要一个`LoginComponent`来让用户登录进这个应用。在登录之后,我们跳转到前面保存的URL,如果没有,就跳转到默认URL。
|
||
该组件没有什么新内容,我们把它放进路由配置的方式也没什么新意。
|
||
|
||
|
||
We'll register a `/login` route in our `login-routing.module.ts` and add the necessary providers to the `providers`
|
||
array. In our `app.module.ts`, we'll import the `LoginComponent` and add it to our `AppModule` `declarations`.
|
||
We'll also import and add the `LoginRoutingModule` to our `AppModule` imports.
|
||
|
||
我们将在`login-routing.module.ts`中注册一个`/login`路由,并把必要的提供商添加`providers`数组中。
|
||
在`app.module.ts`中,我们导入`LoginComponent`并把它加入根模块的`declarations`中。
|
||
同时在`AppModule`中导入并添加`LoginRoutingModule`。
|
||
|
||
+makeTabs(
|
||
`router/ts/app/app.module.ts,
|
||
router/ts/app/login.component.1.ts,
|
||
router/ts/app/login-routing.module.ts
|
||
`,
|
||
null,
|
||
`app/app.module.ts,
|
||
app/login.component.ts,
|
||
app/login-routing.module.ts
|
||
`)
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Guards and the service providers they require **must** be provided at the module-level. This allows
|
||
the Router access to retrieve these services from the `Injector` during the navigation process.
|
||
The same rule applies for feature modules loaded [asynchronously](#asynchronous-routing).
|
||
|
||
它们所需的守卫和服务提供商**必须**在模块一级提供。这让路由器在导航过程中可以通过`Injector`来取得这些服务。
|
||
同样的规则也适用于[异步加载](#asynchronous-routing)的特性模块。
|
||
|
||
h3#can-activate-child-guard <i>CanActivateChild</i>: guarding child routes
|
||
h3#can-activate-child-guard <i>CanActivateChild</i>: 守卫子路由
|
||
:marked
|
||
As we learned about guarding routes with `CanActivate`, we can also protect child routes with the `CanActivateChild`
|
||
guard. The `CanActivateChild` guard works similarly to the `CanActivate` guard, but the difference is its run _before_
|
||
each child route is activated. We protected our admin feature module from unauthorized access, but we could also
|
||
protect child routes within our feature module.
|
||
|
||
就像我们可以通过`CanActivate`来守卫路由一样,我们也能通过`CanActivateChild`守卫来保护子路由。`CanActivateChild`守卫的工作方式和`CanActivate`守卫很相似,不同之处在于它会在每个子路由被激活*之前*运行。我们保护了管理特性模块不受未授权访问,也同样可以在特性模块中保护子路由。
|
||
|
||
Let's extend our `AuthGuard` to protect when navigating between our `admin` routes. First we'll open our
|
||
`auth-guard.service.ts` and add `CanActivateChild` interface to our imported tokens from the router package.
|
||
|
||
让我们扩展一下`AuthGuard`,让它能在`admin`路由之间导航时提供保护。首先,打开`auth-guard.serivce.ts`并从router包中导入`CanActiveChild`接口。
|
||
|
||
Next, we'll implement the `canActivateChild` method with takes the same arguments as the `canActivate` method,
|
||
an `ActivatedRouteSnapshot` and `RouterStateSnapshot`. The `canActivateChild` behaves the same way the other
|
||
guards do, returning an `Observable<boolean>` or `Promise<boolean>` for async checks and `boolean` for sync checks.
|
||
We'll return a `boolean`:
|
||
|
||
然后,我们实现`canActivateChild`方法,它接收与`canActivate`方法相同的参数:`ActivatedRouteSnapshot`和`RouterStateSnapshot`。
|
||
`canActivateChild`和其它守卫的行为一样,都返回`Observable<boolean>`或`Promise<boolean>`以支持异步检查,或返回`boolean`来支持同步检查。
|
||
这里我们直接返回`boolean`:
|
||
|
||
+makeExcerpt('app/auth-guard.service.3.ts (excerpt)', 'can-activate-child')
|
||
|
||
:marked
|
||
We add the same `AuthGuard` to our `component-less` admin route to protect all other child routes at one time
|
||
instead of adding the `AuthGuard` to each route individually.
|
||
|
||
我们往“无组件”的管理路由中添加同一个`AuthGuard`以同时保护所有子路由,而不是挨个添加它们。
|
||
|
||
+makeExcerpt('app/admin/admin-routing.module.3.ts (excerpt)', 'can-activate-child')
|
||
|
||
h3#can-deactivate-guard <i>CanDeactivate</i>: handling unsaved changes
|
||
|
||
h3#can-deactivate-guard <i>CanDeactivate</i>:处理未保存的更改
|
||
|
||
:marked
|
||
Back in the "Heroes" workflow, the app accepts every change to a hero immediately without hesitation or validation.
|
||
|
||
回到“Heroes”工作流,该应用毫不犹豫的接受对英雄的任何修改,不作任何校验。
|
||
|
||
In the real world, we might have to accumulate the users changes.
|
||
We might have to validate across fields. We might have to validate on the server.
|
||
We might have to hold changes in a pending state until the user confirms them *as a group* or
|
||
cancels and reverts all changes.
|
||
|
||
在现实世界中,我们得先把用户的改动积累起来。
|
||
我们可能不得不进行跨字段的校验,可能要找服务器进行校验,可能得把这些改动保存成一种待定状态,直到用户或者把这些改动*作为一组*进行确认或撤销所有改动。
|
||
|
||
What do we do about unapproved, unsaved changes when the user navigates away?
|
||
We can't just leave and risk losing the user's changes; that would be a terrible experience.
|
||
|
||
当用户要导航到外面时,该怎么处理这些既没有审核通过又没有保存过的改动呢?
|
||
我们不能马上离开,不在乎丢失这些改动的风险,那显然是一种糟糕的用户体验。
|
||
|
||
We'd like to pause and let the user decide what to do.
|
||
If the user cancels, we'll stay put and allow more changes.
|
||
If the user approves, the app can save.
|
||
|
||
我们应该暂停,并让用户决定该怎么做。如果用户选择了取消,我们就留下来,并允许更多改动。如果用户选择了确认,那就进行保存。
|
||
|
||
We still might delay navigation until the save succeeds.
|
||
If we let the user move to the next screen immediately and
|
||
the save failed (perhaps the data are ruled invalid), we would have lost the context of the error.
|
||
|
||
在保存成功之前,我们还可以继续推迟导航。如果我们让用户立即移到下一个界面,而保存却失败了(可能因为数据不符合有效性规则),我们就会丢失该错误的上下文环境。
|
||
|
||
We can't block while waiting for the server — that's not possible in a browser.
|
||
We need to stop the navigation while we wait, asynchronously, for the server
|
||
to return with its answer.
|
||
|
||
在等待服务器的答复时,我们没法阻塞它 —— 这在浏览器中是不可能的。
|
||
我们只能用异步的方式在等待服务器答复之前先停止导航。
|
||
|
||
We need the `CanDeactivate` guard.
|
||
|
||
我们需要`CanDeactivate`守卫。
|
||
|
||
### Cancel and Save
|
||
|
||
### 取消与保存
|
||
|
||
Our sample application doesn't talk to a server.
|
||
Fortunately, we have another way to demonstrate an asynchronous router hook.
|
||
|
||
我们的范例应用不会与服务器通讯。
|
||
幸运的是,我们有另一种方式来演示异步的路由器钩子。
|
||
|
||
Users update crisis information in the `CrisisDetailComponent`.
|
||
Unlike the `HeroDetailComponent`, the user changes do not update the
|
||
crisis entity immediately. We update the entity when the user presses the *Save* button.
|
||
We discard the changes if the user presses the *Cancel* button.
|
||
|
||
用户在`CrisisDetailComponent`中更新危机信息。
|
||
与`HeroDetailComponent`不同,用户的改动不会立即更新危机的实体对象。当用户按下了*Save*按钮时,我们就更新这个实体对象;如果按了*Cancel*按钮,那就放弃这些更改。
|
||
|
||
Both buttons navigate back to the crisis list after save or cancel.
|
||
|
||
这两个按钮都会在保存或取消之后导航回危机列表。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-detail.component.1.ts (excerpt)', 'cancel-save')
|
||
|
||
:marked
|
||
What if the user tries to navigate away without saving or canceling?
|
||
The user could push the browser back button or click the heroes link.
|
||
Both actions trigger a navigation.
|
||
Should the app save or cancel automatically?
|
||
|
||
如果用户尝试不保存或撤销就导航到外面该怎么办?
|
||
用户可以按浏览器的后退按钮,或点击英雄的链接。
|
||
这些操作都会触发导航。本应用应该自动保存或取消吗?
|
||
|
||
We'll do neither. Instead we'll ask the user to make that choice explicitly
|
||
in a confirmation dialog box that *waits asynchronously for the user's
|
||
answer*.
|
||
|
||
我们两个都不采取。我们应该弹出一个确认对话框来要求用户明确做出选择,该对话框会*用异步的方式等用户做出选择*。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
We could wait for the user's answer with synchronous, blocking code.
|
||
Our app will be more responsive ... and can do other work ...
|
||
by waiting for the user's answer asynchronously. Waiting for the user asynchronously
|
||
is like waiting for the server asynchronously.
|
||
|
||
我们也能用同步的方式等用户的答复,阻塞代码。但如果能用异步的方式等待用户的答复,应用就会响应性更好,也能同时做别的事。异步等待用户的答复和等待服务器的答复是类似的。
|
||
|
||
:marked
|
||
The `DialogService` (provided in the `AppModule` for app-wide use) does the asking.
|
||
|
||
`DialogService`(为了在应用级使用,已经注入到了`AppModule`)就可以做到这些。
|
||
|
||
It returns a [promise](http://exploringjs.com/es6/ch_promises.html) that
|
||
*resolves* when the user eventually decides what to do: either
|
||
to discard changes and navigate away (`true`) or to preserve the pending changes and stay in the crisis editor (`false`).
|
||
|
||
它返回[promise](http://exploringjs.com/es6/ch_promises.html),当用户最终决定了如何去做时,它就会被*解析* —— 或者决定放弃更改直接导航离开(`true`),或者保留未完成的修改,留在危机编辑器中(`false`)。
|
||
|
||
a#CanDeactivate
|
||
:marked
|
||
We create a `Guard` that will check for the presence of a `canDeactivate` function in our component, in this
|
||
case being `CrisisDetailComponent`. We don't need to know the details of how our `CrisisDetailComponent` confirms deactivation.
|
||
This makes our guard reusable, which is an easy win for us.
|
||
|
||
我们创建了一个`Guard`,它将检查这个组件中`canDeactivate`函数的工作现场,在这里,它就是`CrisisDetailComponent`。我们并不需要知道`CrisisDetailComponent`确认退出激活状态的详情。这让我们的守卫可以被复用,这是一次轻而易举的胜利。
|
||
|
||
+makeExample('app/can-deactivate-guard.service.ts')
|
||
|
||
:marked
|
||
Alternatively, We could make a component-specific `CanDeactivate` guard for our `CrisisDetailComponent`. The `canDeactivate` method provides us
|
||
with the current instance of our `component`, the current `ActivatedRoute` and `RouterStateSnapshot` in case we needed to access
|
||
some external information. This would be useful if we only wanted to use this guard for this component and needed to ask the component's
|
||
properties in or to confirm whether the router should allow navigation away from it.
|
||
|
||
另外,我们也可以为`CrisisDetailComponent`创建一个特定的`CanDeactivate`守卫。在需要访问外部信息时,`canDeactivate`方法为提供了组件、`ActivatedRoute`和`RouterStateSnapshot`的当前实例。如果只想为这个组件使用该守卫,并且需要使用该组件属性、或者需要路由器确认是否允许从该组件导航出去时,这个守卫就非常有用。
|
||
|
||
+makeExcerpt('app/can-deactivate-guard.service.1.ts (component-specific)', '')
|
||
|
||
:marked
|
||
Looking back at our `CrisisDetailComponent`, we have implemented our confirmation workflow for unsaved changes.
|
||
|
||
看看`CrisisDetailComponent`组件,我们已经实现了对未保存的更改进行确认的工作流。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-detail.component.1.ts (excerpt)', 'cancel-save-only')
|
||
|
||
:marked
|
||
Notice that the `canDeactivate` method *can* return synchronously;
|
||
it returns `true` immediately if there is no crisis or there are no pending changes.
|
||
But it can also return a `Promise` or an `Observable` and the router will wait for that
|
||
to resolve to truthy (navigate) or falsey (stay put).
|
||
|
||
注意,`canDeactivate`方法*可以*同步返回,如果没有危机,或者没有未定的修改,它就立即返回`true`。但是它也可以返回一个承诺(`Promise`)或可观察对象(`Observable`),路由器将等待它们被解析为真值(继续导航)或假值(留下)。
|
||
|
||
:marked
|
||
We add the `Guard` to our crisis detail route in `crisis-center-routing.module.ts` using the `canDeactivate` array.
|
||
|
||
我们往`crisis-center.routing.ts`的危机详情路由中用`canDeactivate`数组添加一个`Guard`(守卫)。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-center-routing.module.3.ts (can deactivate guard)', '')
|
||
|
||
:marked
|
||
We also need to add the `Guard` to our main `AppRoutingModule` `providers` so the `Router` can inject it during the navigation process.
|
||
|
||
我们还要把这个`Guard`添加到`appRoutingModule`的`providers`中去,以便`Router`可以在导航过程中注入它。
|
||
|
||
+makeExample('app/app-routing.module.4.ts', '', '')
|
||
|
||
:marked
|
||
Now we have given our user a safeguard against unsaved changes.
|
||
|
||
现在,我们已经给了用户一个能保护未保存更改的安全守卫。
|
||
|
||
<a id="Resolve"></a>
|
||
h3#resolve-guard <i>Resolve</i>: pre-fetching component data
|
||
h3#resolve-guard <i>解析</i>: 提前获取组件数据
|
||
:marked
|
||
In our `Hero Detail` and `Crisis Detail`, we waited until the route was activated to fetch our respective hero or crisis.
|
||
|
||
在`Hero Detail`和`Crisis Detail`中,它们等待路由读取对应的英雄和危机。
|
||
|
||
This worked well for us, but we can always do better.
|
||
If we were using a real world api, there may be some delay in when the data we want to display gets returned.
|
||
We don't want to display a blank component until the data loads in this situation.
|
||
|
||
这种方式没有问题,但是它们还有进步的空间。
|
||
如果我们在使用真实api,很有可能数据返回有延迟,导致无法即时显示。
|
||
在这种情况下,直到数据到达前,显示一个空的组件不是最好的用户体验。
|
||
|
||
We'd like to pre-fetch data from the server so it's ready the moment our route is activated.
|
||
We'd also like to handle the situation where our data fails to load or some other error condition occurs.
|
||
This would help us in our `Crisis Center` if we navigated to an `id` that doesn't return a record.
|
||
We could send the user back to the `Crisis List` where we only show valid crisis centers.
|
||
We want to delay rendering of our route component until all necessary data has been fetched or some action
|
||
has occurred.
|
||
|
||
可以预先从服务器读取数据,这样在路由器被激活时,数据已经返回。同时,我们还需要处理数据返回失败和其它出错情况。这样,在`Crisis Center`中,对处理导航到一个无返回数据的`id`有帮助。
|
||
我们可以将用户发回只列出有效危机的`Crisis List`。
|
||
我们需要延迟渲染路由组件,来等待所有必要的数据都成功获取或做一些其他操作。
|
||
|
||
We need the `Resolve` guard.
|
||
|
||
我们需要`Resolve`守卫。
|
||
|
||
### Fetch data before navigating
|
||
|
||
### 导航前预先加载路由信息
|
||
|
||
We'll update our `Crisis Detail` route to resolve our Crisis before loading the route, or if the user happens to
|
||
navigate to an invalid crisis center `:id`, we'll navigate back to our list of existing crises.
|
||
|
||
我们需要更新`Crisis Detail`路由,让它先解析必要的危机,再加载路由。或者当用户导航到一个无效的危机`:id`时,将它们导航回危机列表。
|
||
|
||
Like the `CanActivate` and `CanDeactivate` guards, the **`Resolve`** guard is an interface we can implement as a service
|
||
to resolve route data synchronously or asynchronously. In `CrisisDetailComponent`, we used the `ngOnInit` to retrieve the `Crisis`
|
||
information. We also navigated the user away from the route if the `Crisis` was not found. It would be more efficient to perform this
|
||
action before the route is ever activated.
|
||
|
||
和`CanActivate`和`CanDeactivate`守卫一样,服务可以实现**`Resolve`**守卫接口来同步或异步解析路由数据。
|
||
`Crisis Detail`组件使用`ngOnInit`来获取`Crisis`信息。如果`Crisis`找不到,用户会被导航出去。在路由被激活之前就处理这些情况会更加有效。
|
||
|
||
We'll create a `CrisisDetailResolve` service that will handle retrieving the `Crisis` and navigating the user away if the `Crisis` does
|
||
not exist. Then we can be assured that when we activate the `CrisisDetailComponent`, the associated Crisis will already be available
|
||
for display.
|
||
|
||
现在创建一个`CrisisDetailResolve`服务,用它来处理`Crisis`数据读取和在`Crisis`不存在时将用户导航出去。
|
||
然后可以确保当激活`CrisisDetailComponent`时,关联的`Crisis`已经为显示准备妥当。
|
||
|
||
Let's create our `crisis-detail-resolve.service.ts` file within our `Crisis Center` feature area.
|
||
|
||
下面在`Crisis Center`特征区新建的`crisis-detail-resolve.service.ts`文件:
|
||
|
||
+makeExample('app/crisis-center/crisis-detail-resolve.service.ts', '')
|
||
|
||
:marked
|
||
We'll take the relevant parts of the `ngOnInit` lifecycle hook in our `CrisisDetailComponent` and move them into our `CrisisDetailResolve` guard.
|
||
We import the `Crisis` model and `CrisisService` and also the `Router` for navigation from our resolve implementation. We want to be explicit about
|
||
the data we are resolving, so we implement the `Resolve` interface with a type of `Crisis`. This lets us know that what we will resolve will match our
|
||
`Crisis` model. We inject the `CrisisService` and `Router` and implement the `resolve` method that supports a `Promise`, `Observable` or a synchronous
|
||
return value.
|
||
|
||
接下来,将`CrisisDetailComponent`中`ngOnInit`生命周期钩子里面相关的部分移动到`CrisisDetailResolve`守卫里面,然后导入`Crisis`模型,`CrisisService`服务和`Router`。
|
||
为了特殊指定什么样的数据需要解析,我们在`Resolve`接口的实现上指定了`Crisis`类型。这样告诉我们解析的结果将于`Crisis`模型对应。
|
||
然后注入`CrisisService`和`Router`,并实现支持`Promise`、`Observable`和异步返回值`resolve`方法。
|
||
|
||
We'll use our `CrisisService.getCrisis` method that returns a promise to prevent our route from loading until the data is fetched. If we don't find a valid `Crisis`,
|
||
we navigate the user back to the `CrisisList`, canceling the previous in-flight navigation to the crisis details.
|
||
|
||
我们使用`CrisisService.getCrisis`方法来获取一个**承诺对象**,用于防止路由在成功获取数据之前被加载。如果没有找到对应`Crisis`,便将用户导航回`CrisisList`,取消之前导航到危机详情的路由。
|
||
|
||
Now that our guard is ready, we'll import it in our `crisis-center-routing.module.ts` and use the `resolve` object in our route configuration.
|
||
|
||
解析守卫现在准备好了,将它导入到`crisis-center-routing.module.ts`中,然后在路由配置中设置`resolve`对象。
|
||
|
||
We'll add the `CrisisDetailResolve` service to our `CrisisCenterRoutingModule`'s `providers`, so its available to the `Router` during the navigation process.
|
||
|
||
接下来,将`CrisisDetailResolve`服务添加到危机中心路由模块的`providers`数组中,这样`Router`在路由过程中可以使用它。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-center-routing.module.4.ts (resolve)', 'crisis-detail-resolve')
|
||
|
||
:marked
|
||
Now that we've added our `Resolve` guard to fetch data before the route loads, we no longer need to do this once we get into our `CrisisDetailComponent`.
|
||
We'll update the `CrisisDetailComponent` to use the `ActivatedRoute` data, which is where our `crisis` property from our `Resolve` guard will be provided.
|
||
Once activated, all we need to do is set our local `crisis` and `editName` properties from our resolved `Crisis` information. The `Crisis` is being provided
|
||
at the time the route component is activated.
|
||
|
||
因为添加了`Resolve`守卫并用它在加载路由之前读取数据,所以在加载`CrisisDetailComponent`后,不再需要读取数据。
|
||
因此,可以更新`CrisisDetailComponent`组件,让它使用`Resolve`守卫通过`crisis`属性提供的`ActivatedRoute.data`。
|
||
一旦激活`CrisisDetailComponent`,我们可以使用解析过的`Crisis`信息赋值本地属性`crisis`和`editName`。
|
||
我们也不再需要通过订阅和反订阅`ActivatedRoute`的参数来获取`Crisis`,因为它已经在路由组件被激活时被同步提供。
|
||
|
||
+makeExcerpt('app/crisis-center/crisis-detail.component.ts (ngOnInit v2)', 'crisis-detail-resolve')
|
||
|
||
:marked
|
||
**Two critical points**
|
||
|
||
**两个关键点**
|
||
|
||
1. The router interface is optional. We don't inherit from a base class. We simply implement the interface method or not.
|
||
|
||
1. 路由器接口是可选的。我们不必从基类中继承它,只需要实现这个接口方法或不实现。
|
||
|
||
1. We rely on the router to call the guard. We don't worry about all the ways that the user
|
||
could navigate away. That's the router's job.
|
||
We simply write this class and let the router take it from there.
|
||
|
||
1. 我们依赖路由器调用此守卫。不必关心用户用哪种方式导航离开,这是路由器的工作。我们只要写出这个类,等路由器从那里取出它就可以了。
|
||
|
||
The relevant *Crisis Center* code for this milestone is
|
||
|
||
本里程碑中与*危机中心*有关的代码如下:
|
||
|
||
+makeTabs(
|
||
`router/ts/app/app.component.ts,
|
||
router/ts/app/crisis-center/crisis-center-home.component.ts,
|
||
router/ts/app/crisis-center/crisis-center.component.ts,
|
||
router/ts/app/crisis-center/crisis-center-routing.module.4.ts,
|
||
router/ts/app/crisis-center/crisis-list.component.ts,
|
||
router/ts/app/crisis-center/crisis-detail.component.ts,
|
||
router/ts/app/crisis-center/crisis-detail-resolve.service.ts,
|
||
router/ts/app/crisis-center/crisis.service.ts
|
||
`,
|
||
null,
|
||
`app.component.ts,
|
||
crisis-center-home.component.ts,
|
||
crisis-center.component.ts,
|
||
crisis-center-routing.module.ts,
|
||
crisis-list.component.ts,
|
||
crisis-detail.component.ts,
|
||
crisis-detail-resolve.service.ts,
|
||
crisis.service.ts
|
||
`)
|
||
|
||
+makeTabs(
|
||
`router/ts/app/auth-guard.service.3.ts,
|
||
router/ts/app/can-deactivate-guard.service.ts
|
||
`,
|
||
null,
|
||
`auth-guard.service.ts,
|
||
can-deactivate-guard.service.ts
|
||
`)
|
||
|
||
a#query-parameters
|
||
a#fragment
|
||
:marked
|
||
### Query Parameters and Fragments
|
||
|
||
### 查询参数及片段
|
||
|
||
:marked
|
||
In our [route parameters](#optional-route-parameters) example, we only dealt with parameters specific to
|
||
our route, but what if we wanted optional parameters available to all routes? This is where our
|
||
query parameters come into play and serve a special purpose in our application.
|
||
|
||
在这个[查询参数](#query-parameters)例子中,我们只为路由指定了参数,但是该如何定义一些所有路由中都可用的可选参数呢?
|
||
要达到这个目的,该“查询参数”大显身手了。
|
||
|
||
[Fragments](https://en.wikipedia.org/wiki/Fragment_identifier) refer to certain elements on the page
|
||
identified with an `id` attribute.
|
||
|
||
[片段](https://en.wikipedia.org/wiki/Fragment_identifier)可以引用页面中带有特定`id`属性的元素.
|
||
|
||
We'll update our `AuthGuard` to provide a `session_id` query that will remain after navigating to another route.
|
||
|
||
接下来,我们将更新`AuthGuard`来提供`session_id`查询参数,在导航到其它路由后,它还会存在。
|
||
|
||
We'll also provide an arbitrary `anchor` fragment, which we would use to jump to a certain point on our page.
|
||
|
||
我们还将随意提供一个锚点片段,它用来跳转到页面中指定的位置。
|
||
|
||
We'll add the `NavigationExtras` object to our `router.navigate` method that navigates us to our `/login` route.
|
||
|
||
我们还将为`router.nativate`方法传入一个`NavigationExtras`对象,用来导航到`/login`路由。
|
||
|
||
+makeExcerpt('app/auth-guard.service.4.ts (v3)', '')
|
||
|
||
:marked
|
||
We can also **preserve** query parameters and fragments across navigations without having to re-provide them
|
||
when navigating. In our `LoginComponent`, we'll add an *object* as the second argument in our `router.navigate` function
|
||
and provide the `preserveQueryParams` and `preserveFragment` to pass along the current query parameters
|
||
and fragment to the next route.
|
||
|
||
还可以再导航之间**保留**查询参数和片段,而无需再次再导航中提供。在`LoginComponent`中的`router.navigate`方法中,添加第二个参数,该**对象**提供了`preserveQueryParams`和 `preserveFragment`,用于传递到当前的查询参数中并为下一个路由提供片段。
|
||
|
||
+makeExcerpt('app/login.component.ts', 'preserve')
|
||
|
||
:marked
|
||
Since we'll be navigating to our *Admin Dashboard* route after logging in, we'll update it to handle our
|
||
query parameters and fragment.
|
||
|
||
由于要在登录后导航到*危机管理*特征区的路由,所以我们还得更新它,来处理这些全局查询参数和片段。
|
||
|
||
+makeExcerpt('app/admin/admin-dashboard.component.2.ts (v2)', '')
|
||
|
||
:marked
|
||
*Query Parameters* and *Fragments* are also available through the `ActivatedRoute` service available to route components.
|
||
Just like our *route parameters*, query parameters and fragments are provided as an `Observable`.
|
||
For our updated *Crisis Admin* component we'll feed the `Observable` directly into our template using the `AsyncPipe`, which
|
||
will handle _unsubscribing_ from the `Observable` for us when the component is destroyed.
|
||
|
||
*查询参数*和*片段*可通过`Router`服务的`routerState`属性使用。和*路由参数*类似,全局查询参数和片段也是`Observable`对象。
|
||
在更新过的*英雄管理*组件中,我们将直接把`Observable`传给模板,借助`AsyncPipe`在组件被销毁时自动_取消_对`Observable`的订阅。
|
||
|
||
.l-sub-section
|
||
|
||
img(src='/resources/images/devguide/plunker-separate-window-button.png' alt="pop out the window" align="right" style="margin-right:-20px")
|
||
|
||
:marked
|
||
When running in plunker, pop out the preview window by clicking the blue 'X' button in the upper right corner.
|
||
|
||
当在plunker中运行时,可以点击右上角的蓝色'X'按钮来弹出预览窗口。
|
||
|
||
:marked
|
||
Following the steps in this process, we can click on the *Admin* button, that takes us to the *Login*
|
||
page with our provided `query params` and `fragment`. After we click the login button, we notice that
|
||
we have been redirected to the `Admin Dashboard` page with our `query params` and `fragment` still intact. We can use
|
||
these persistent bits of information for things that need to be provided with across pages interaction like
|
||
authentication tokens or session ids.
|
||
|
||
按照下列步骤试验下:点击*Crisis Admin*按钮,它会带着我们提供的“查询参数”和“片段”跳转到登录页。
|
||
点击登录按钮,我们就会被带到`Crisis Admin`页,仍然带着上一步提供的“查询参数”和“片段”。
|
||
我们可以用这些持久化信息来携带需要为每个页面都提供的信息,如认证令牌或会话的ID等。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
The `query params` and `fragment` can also be preserved using a `RouterLink` with
|
||
the **preserveQueryParams** and **preserveFragment** bindings respectively.
|
||
|
||
“查询参数”和“片段”也可以分别用`RouterLink`中的**preserveQueryParams**和**preserveFragment**保存。
|
||
|
||
<a id="asynchronous-routing"></a>
|
||
.l-main-section
|
||
:marked
|
||
## Milestone #6: Asynchronous Routing
|
||
|
||
## 里程碑5:异步路由
|
||
|
||
As we have completed our milestones, our application has naturally gotten larger. As we continue to build
|
||
out feature areas our overall application size will get larger also. At some point we'll reach a tipping
|
||
point in where our application takes a significant enough time to load. This is not a viable long term solution.
|
||
|
||
完成上面的里程碑后,我们的应用程序很自然的长大了。在继续构建特征区的过程中,应用的尺寸将会变得更大。在某一个时间点,我们将达到一个顶点,应用
|
||
将会需要过多的时间来加载。长远来看,这不是一个办法。
|
||
|
||
So how do we combat this problem? We introduce asynchronous routing into our application and take advantage of loading
|
||
feature areas _lazily_. This buys us multiple things:
|
||
|
||
如何才能解决这个问题呢?我们引进了异步路由到应用程序中,并获得**懒惰**加载特征区域的能力。这样给我们带来了下列好处:
|
||
|
||
* We can continue building out feature areas without increasing our initial bundle.
|
||
|
||
* 可以继续构建特征区,但不再增加初始包大小。
|
||
|
||
* We can load feature areas only when requested by the user.
|
||
|
||
* 只有在用户请求时才加载特征区。
|
||
|
||
* We can speed up load time for users that only visit certain areas of our application.
|
||
|
||
* 为那些只访问应用程序某些区域的用户加快加载速度。
|
||
|
||
These are all things we want to have in our application, so let's apply this to our current setup. We've already made
|
||
great strides by organizing our application into four modules: `AppModule`, `HeroesModule`, `AdminModule` and `CrisisCenterModule`.
|
||
Our `AdminModule` is the area of our application that would be scoped to a small set of users, so we'll take advantage
|
||
of asynchronous routing and only load the `Admin` feature area when requested.
|
||
|
||
我们接下来在当前的项目中添加这些特征。现在已经有一系列模块将应用组织为四大块:`AppModule`, `HeroesModule`, `AdminModule` 和 `CrisisCenterModule`。
|
||
`AdminModule`在我们的应用中只被小部分用户访问, 所以我们利用异步路由来实现只有在请求时才加载`Admin`特性区域。
|
||
:marked
|
||
### Lazy-Loading route configuration
|
||
|
||
### 惰性加载路由配置
|
||
|
||
We'll start by adding an `admin` route to our `app-routing.module.ts` file. We want to load our `Admin` module asynchronously,
|
||
so we'll use the `loadChildren` property in our route config where previously we used the `children` property to include our child routes.
|
||
|
||
首先,把`admin`路由添加到 `app-routing.module.ts`文件中。我们想要异步加载`Admin`模块,
|
||
就得在路由配置中使用`loadChildren`属性,而以前我们已经用`children`属性包含进了这些子路由。
|
||
|
||
We'll also change our `admin` **path** in our `admin-routing.module.ts` to an empty path. The `Router` supports
|
||
*empty path* routes, which we can use for grouping routes together without adding anything additional paths to the URL. Our
|
||
users will still visit `/admin` and our `AdminComponent` still serves as our *Routing Component* which contains
|
||
our child routes.
|
||
|
||
接下来,在`admin.routing.ts`中,把`admin`的**path**更改为空路径。路由器支持**空路径**路由,它可以在不必把别的路径添加到URL中的情况下,将多个路由组合到一起。用户还是可以访问`/crisis-center`,`CrisisCenterComponent`组件还是包含了子级路由的**路由组件**。
|
||
|
||
+makeTabs(
|
||
`router/ts/app/app-routing.module.5.ts,
|
||
router/ts/app/admin/admin-routing.module.ts`,
|
||
'lazy-load-admin,',
|
||
`app-routing.module.ts (load children),
|
||
app/admin/admin-routing.module.ts (empty path admin)
|
||
`)
|
||
|
||
:marked
|
||
The `loadChildren` property is used by the `Router` to map to our bundle we want to lazy-load, in this case being the `AdminModule`.
|
||
|
||
路由器用`loadChildren`属性来映射我们希望惰性加载的捆文件,这里是`AdminModule`。
|
||
|
||
If we look closer at the `loadChildren` string, we can see that it maps directly to our `admin.module.ts` file where we previously built
|
||
out our `Admin` feature area. After the path to the file we use a `#` to denote where our file path ends and to tell the `Router` the name
|
||
of our `AdminModule`. If we look in our `admin.module.ts` file, we can see it matches name of our exported module class.
|
||
|
||
仔细看`loadChildren`字符串,就会发现它直接映射到了我们以前在管理特性区构建的`admin.module.ts`文件。在文件路径后面,我们使用`#`来标记出文件路径的末尾,并告诉路由器`AdminModule`的名字。打开`admin.module.ts`文件,我们就会看到它正是我们所导出的模块类的名字。
|
||
|
||
+makeExcerpt('app/admin/admin.module.ts (export)', 'admin-module-export')
|
||
|
||
:marked
|
||
The `loadChildren` property is used by the `Router` to map to our bundle we want to lazy-load, in this case being the `AdminModule`.
|
||
The router will take our loadChildren string and dynamically load in our `AdminModule`, add its routes to our configuration *dynamically*
|
||
and then load the requested route. This will only happen when the route is **first** requested and the module will be immediately be available
|
||
for subsequent requests.
|
||
|
||
路由器用`loadChildren`属性来映射我们希望惰性加载的捆文件,这里是`AdminModule`。路由器将接收我们的`loadChildren`字符串,并把它动态加载进`AdminModule`,它的路由被*动态*合并到我们的配置中,然后加载所请求的路由。但只有在首次加载该路由时才会这样做,后续的请求都会立即完成。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Angular provides a built-in module loader that supports **`SystemJS`** to load modules asynchronously. If we were
|
||
using another bundling tool, such as **Webpack**, we would use the Webpack mechanism for asynchronously loading modules.
|
||
|
||
Angular提供一个内置模块加载器,支持**`SystemJS`**来异步加载模块。如果我们使用其它捆绑工具比如**Webpack**,则使用Webpack的机制来异步加载模块。
|
||
|
||
:marked
|
||
We've built our feature area, we've updated our route configuration to take advantage of lazy-loading, now we have to do the final step
|
||
to break our `AdminModule` into a completely separate module. In our `app.module.ts`, we'll remove our `AdminModule` from the
|
||
`imports` array since we'll be loading it on-demand an we'll remove the imported `AdminModule`.
|
||
|
||
我们构建了特性区,更新了路由配置来实现惰性加载,现在该做最后一步:将`AdminModule`分离到一个彻底独立的模块。因为现在按需加载`AdminModule`,所以在`app.module.ts`中,从`imports`数组中删除它。
|
||
|
||
+makeExcerpt('app/app.module.7.ts (async admin module)', '')
|
||
|
||
h3#can-load-guard <i>CanLoad Guard</i>: guarding against loading of feature modules
|
||
h3#can-load-guard <i>CanLoad守卫</i>: 保护特性模块的加载
|
||
:marked
|
||
We're already protecting our `AdminModule` with a `CanActivate` guard that prevents the user from
|
||
accessing the admin feature area unless authorized. We're currently loading the admin routing
|
||
asynchronously when requested, checking the user access and redirecting to the login page if not
|
||
authorized. Ideally, we only want to load the `AdminModule` if the user is logged in and prevent
|
||
the `AdminModule` and its routing from being loaded until then.
|
||
|
||
我们已经使用`CanAcitvate`保护`AdminModule`了,它会阻止对管理特性区的匿名访问。我们在请求时可以异步加载管理类路由,检查用户的访问权,如果用户未登录,则跳转到登陆页面。但更理想的是,我们只在用户已经登录的情况下加载`AdminModule`,并且直到加载完才放行到它的路由。
|
||
|
||
The **CanLoad** guard covers this scenario.
|
||
|
||
**CanLoad**守卫适用于这个场景。
|
||
|
||
We can use the `CanLoad` guard to only load the `AdminModule` once the user is logged in **and** attempts
|
||
to access the admin feature area. We'll update our existing `AuthGuard` to support the `CanLoad` guard. We'll import
|
||
the `CanLoad` interface and the `Route` the guard provides when called that contains the requested path.
|
||
|
||
我们可以用`CanLoad`守卫来保证只在用户已经登录并尝试访问管理特性区时才加载一次`AdminModule`。我们这就升级`AuthGuard`来支持`CanLoad`守卫。我们先导入`CanLoad`接口,它被调用时守卫会提供一个`Route`参数,其中包含所请求的路径。
|
||
|
||
We'll add the interface to our service, and then we'll implement the interface. Since our `AuthGuard` already
|
||
checks the user's logged in state, we can pass that access check to our `canLoad` method. The `Route` in
|
||
the `canLoad` method provides a **path** which comes from our route configuration.
|
||
|
||
我们还要把此接口加入到服务中,并实现它。由于我们的`AuthGuard`已经能检查用户的登录状态了,所以把`canLoad`方法的权限检查工作直接转给它。
|
||
`canLoad`方法中的`Route`参数提供了一个路径,它来自我们的路由配置。
|
||
|
||
+makeExcerpt('app/auth-guard.service.ts (can load guard)', '')
|
||
|
||
:marked
|
||
Next, we'll import the `AuthGuard` into our `app-routing.module.ts` and add the `AuthGuard` to the `canLoad` array for
|
||
our `admin` route. Now our `admin` feature area is only loaded when the proper access has been granted.
|
||
|
||
接下来,我们就把`AuthGuard`导入到`app.routing.ts`中,并把`AuthGuard`添加到`admin`路由的`canLoad`数组中。现在`admin`特性区就只有当获得访问授权时才会被加载了。
|
||
|
||
+makeExcerpt('app/app-routing.module.5.ts (can load guard)', 'can-load-guard')
|
||
|
||
h3#preloading <i>Pre-Loading</i>: background loading of feature areas
|
||
|
||
h3#preloading <i>预加载</i>: 在后台加载特征区域
|
||
|
||
:marked
|
||
We've learned how to load modules on-demand, but we can also take advantage of loading feature areas modules in *advance*. The *Router*
|
||
supports **pre-loading** of asynchronous feature areas prior to navigation to their respective URL. Pre-loading allows us to to load our initial route
|
||
quickly, while other feature modules are loaded in the background. Once we navigate to those areas, they will have already been loaded
|
||
as if they were included in our initial bundle.
|
||
|
||
我们已经学会了如何按需加载模块,我们还可以*预先*加载特征区域。*路由器*支持在导航到特征区域URL之前,异步*预加载*它们。
|
||
预加载允许我们在快速加载初始路由的同时,在后台加载其他特征模块。当导航到这些区域时,它们已经被加载了,就像它们被包含在初始包中一样。
|
||
|
||
Each time a **successful** navigation happens, the *Router* will look through our configuration for lazy loaded feature areas
|
||
and react based on the provided strategy.
|
||
|
||
每次导航**成功**发生时,*路由器*将查看惰性加载的特征区域的配置,并根据提供的策略作出反应。
|
||
|
||
The *Router* supports two pre-loading strategies by default:
|
||
|
||
*路由器*默认支持两种预加载策略:
|
||
|
||
* No pre-loading at all which is the default. Lazy loaded feature areas are still loaded on demand.
|
||
|
||
* 完全不预加载,这是默认值。惰性加载特征区域仍然按需加载。
|
||
|
||
* Pre-loading of all lazy loaded feature areas.
|
||
|
||
* 预加载所有惰性加载的特征区域。
|
||
|
||
The *Router* also supports [custom preloading strategies](#custom-preloading) for fine control over which modules to pre-load.
|
||
|
||
*路由器*还支持[自定义预加载策略](#custom-preloading),用来精细控制预加载。
|
||
|
||
We'll update our *CrisisCenterModule* to be loaded lazily by default and use the `PreloadAllModules` strategy
|
||
to load _all_ lazy loaded modules as soon as possible.
|
||
|
||
我们将更新*CrisisCenterModule*,让它默认惰性加载并使用`PreloadAllModules`策略来尽快加载_所有_惰性加载模块。
|
||
|
||
<a id="preload-canload"></a>
|
||
.l-sub-section
|
||
:marked
|
||
The **PreloadAllModules** strategy does not load feature areas protected by a [CanLoad](#can-load-guard) guard and this is by design.
|
||
The *CanLoad* guard blocks loading of feature module assets until authorized to do so. If you want to both preload a module and guard
|
||
against unauthorized access, use the [CanActivate](#can-activate-guard) guard instead.
|
||
|
||
**PreloadAllModules**策略不会加载被[CanLoad](#can-load-guard)守卫保护的特征区域,这是因为Angular是这样设计的。
|
||
*CanLoad*守卫阻挡加载特征模块资源,直到授权为止。如果你希望预加载一个模块并保护未授权访问,使用[CanActivate](#can-activate-guard)守卫。
|
||
|
||
:marked
|
||
We'll update our route configuration to lazy load the *CrisisCenterModule*. We follow the same process as we did when we loaded the *AdminModule* asynchronously.
|
||
In the *crisis-center-routing.module.ts*, we'll change the *crisis-center* path to an *empty path* route.
|
||
|
||
使用与加载异步*AdminModule*一样流程,我们将更新路由配置,来惰性加载*CrisisCenterModule*。
|
||
在*crisis-center-routing.module.ts*中,将*crisis-center*的路径修改为*空路径*。
|
||
|
||
We'll move our redirect and *crisis-center* route to our `AppRoutingModule` routes and use the `loadChildren` string to load the *CrisisCenterModule*.
|
||
The redirect is also changed to load the `/heroes` route on initial load.
|
||
|
||
接下来,将*redirect*和*crisis-center*路由移动到`AppRoutingModule`路由,并使用`loadChildren`字符串来加载*CrisisCenterModule*。
|
||
*redirect*也被修改为初始加载`/heroes`路由。
|
||
|
||
Once we're finished, we'll remove the `CrisisCenterModule` from our `AppModule`'s imports.
|
||
|
||
然后将`CrisisCenterModule`移到`AppModule`的`imports`中。
|
||
|
||
Here are the updated modules _before enabling preload_:
|
||
|
||
下面是打开预加载之前的模块修改版:
|
||
|
||
+makeTabs(
|
||
`router/ts/app/app.module.ts,
|
||
router/ts/app/app-routing.module.6.ts,
|
||
router/ts/app/crisis-center/crisis-center-routing.module.ts
|
||
`,
|
||
',preload-v1,',
|
||
`app.module.ts,
|
||
app-routing.module.ts,
|
||
crisis-center-routing.module.ts
|
||
`)
|
||
|
||
:marked
|
||
The second argument in the `RouterModule.forRoot` method takes an object for additional configuration options.
|
||
We import the `PreloadAllModules` token from the router package and set the configuration option's `preloadingStrategy` property
|
||
with this `PreloadAllModules` token.
|
||
This tells the built-in *Router* pre-loader to immediately load **all** [unguarded](#preload-canload) feature areas that use `loadChildren`.
|
||
|
||
`RouterModule.forRoot`方法的第二个参数接受一个附加配置选项对象。
|
||
我们从路由器包导入`PreloadAllModules`令牌,并将这个配置选项的`preloadingStrategy`属性设置为`PreloadAllModules`令牌。
|
||
这样,内置的*路由器*立刻预加载**所有**使用`loadChildren`的[未受保护](#preload-canload)的特征区域。
|
||
|
||
+makeExcerpt('app/app-routing.module.6.ts (preload all)', '')
|
||
|
||
:marked
|
||
Now when we visit `http://localhost:3000`, the `/heroes` route will load in the foreground, while the *CrisisCenterModule* and any other asynchronous feature
|
||
modules are _eagerly_ loaded in the background, waiting for us to navigate to them.
|
||
|
||
现在,访问 `http://localhost:3000` 时,`/heroes`路由将在前台加载,同时,*CrisisCenterModule*和其他异步特征模块将在后台被_主动_加载,等待我们导航到它们。
|
||
|
||
<a id="custom-preloading"></a>
|
||
:marked
|
||
### Custom Pre-Loading Strategy
|
||
|
||
### 自定义预加载策略
|
||
|
||
Pre-loading all modules works well in some situations, but in some cases we need more control over what gets loaded eagerly. This becomes more clear
|
||
as we load our application on a mobile device, or a low bandwidth connection. We may only want to preload certain feature modules based on user metrics
|
||
or other data points we gather over time. The *Router* lets us have more control with a **custom** preloading strategy.
|
||
|
||
在一些情况下,预加载所有模块很合适。但是在另外一些用例中,我们需要选择主动加载哪些模块。在移动设备上或者网速很低的情况下加载应用显得尤其明显。
|
||
根据用户指标或者逐步收集的数据,我们可能只想预加载某些特征模块。*路由器*通过*自定义*预加载策略为我们提供了更多控制。
|
||
|
||
We can define our own strategy the same way the **PreloadAllModules** modules strategy was provided to our *RouterModule.forRoot* configuration object.
|
||
|
||
使用将**PreloadAllModules**模块策略提供给*RouterModule.forRoot*配置对象一样方式,我们可以定义自己的策略。
|
||
|
||
Since we want to take advantage of this, we'll add a custom strategy that _only_ preloads the modules we select. We'll enable the preloading by using the *Route Data*,
|
||
which, as we learned, is an object to store arbitrary route data and and [resolve data](#resolve-guard).
|
||
|
||
因为想利用这点,我们将添加自定义策略,_只_预加载我们选择的模块。为了启用预加载,我们使用*Route Data*,正如我们学过的,它是储存的路由数据和[解析数据](#resolve-guard)对象。
|
||
|
||
We'll add a custom `preload` boolean to our `crisis-center` route data that we'll use with our custom strategy. To see it in action, we'll add
|
||
the `route.path` to the `preloadedModules` array in our custom strategy service. We'll also log a message
|
||
to the console for the preloaded module.
|
||
|
||
我们将自定义`preload`布尔值添加到`crisis-center`路由数据,自定义策略将使用它。然后在自定义策略服务中将`route.path`添加到`preloadedModules`数组。
|
||
我们还将在控制台中为预加载模块输出一条消息。
|
||
|
||
+makeExcerpt('app/app-routing.module.ts (route data preload)', 'preload-v2')
|
||
|
||
:marked
|
||
To create our custom strategy we'll need to implement the abstract `PreloadingStrategy` class and the `preload` method. The `preload` method is called for each route
|
||
that loads its feature module asynchronously and determines whether to preload it. The `preload` method takes two arguments, the first being the `Route` that provides
|
||
the route configuration and a function that preloads the feature module.
|
||
|
||
为了创建自定义策略,我们将需要实现抽象类`PreloadingStrategy`和`preload`方法。在异步加载特征模块和决定是否预加载它们时,路由器调用`preload`方法。
|
||
`preload`方法有两个参数,第一个参数`Route`提供路由配置,第二个参数是预加载特征模块的函数。
|
||
|
||
We'll name our strategy **PreloadSelectedModules** since we _only_ want to preload based on certain criteria. Our custom strategy looks for the **`preload`** boolean
|
||
value in our `Route Data` and if its true, it calls the `load` function provided by the built-in `Router` pre-loader that eagerly loads feature modules.
|
||
|
||
我们将命名自己的策略为**PreloadSelectedModules**,因为我们**只**想加载符合特定条件的模块。
|
||
自定义策略在`Route Data`中查询**`preload`**布尔值,如果它为`true`,就调用内置`Router`提供的`load`函数预主动加载这些特征模块。
|
||
|
||
+makeExcerpt('app/selective-preload-strategy.ts (preload selected modules)', '')
|
||
|
||
:marked
|
||
In order to use our custom preloading strategy, we import it into our `app-routing.module.ts` and replace the `PreloadAllModules` strategy. We also add
|
||
the `PreloadSelectedModules` strategy to the `AppRoutingModule` providers array. This allows the *Router* pre-loader to inject our custom strategy.
|
||
|
||
要使用我们的自定义预加载策略,将它导入到`app-routing.module.ts`并替换`PreloadAllModules`策略。
|
||
我们还将`PreloadSelectedModules`策略添加到`AppRoutingModule`的`providers`数组中。这样,*路由器*的预加载器可以注入我们的自定义策略。
|
||
|
||
To confirm our *CrisisCenterModule* is being pre-loaded, we'll display our `preloadedModules` in the `Admin` dashboard. We already know how to use
|
||
an *ngFor* loop, so we'll skip over the details here. Since the `PreloadSelectedModules` is just a service, we can inject it into the `AdminDashboardComponent`
|
||
and wire it up to our list.
|
||
|
||
要确认*CrisisCenterModule*是否被预加载,我们将在`Admin`管理控制台显示`preloadedModules`。
|
||
我们已经知道如何使用*ngFor*循环,所以在这里跳过了一些细节。因为`PreloadSelectedModules`只是一个服务,我们可以将其注入到`AdminDashboardComponent`并连接到列表中:
|
||
|
||
+makeExcerpt('app/admin/admin-dashboard.component.ts (preloaded modules)', '')
|
||
|
||
:marked
|
||
Once our application is loaded to our initial route, the *CrisisCenterModule* is loaded eagerly. We can verify this by logging in to the `Admin` feature area and
|
||
noting that the `crisis-center` is listed in the `Preloaded Modules` and logged to the console. We can continue to add feature modules to be selectively loaded eagerly.
|
||
|
||
一旦应用加载到初始路由,*CrisisCenterModule*被主动加载了。要验证它,登录到`Admin`特征区域,注意`crisis-center`被列到`Preloaded Modules`并输出到控制台。
|
||
我们可以继续添加特征区域,以便有选择的加载它们。
|
||
|
||
<a id="final-app"></a>
|
||
|
||
.l-main-section
|
||
|
||
:marked
|
||
## Wrap Up
|
||
|
||
## 总结
|
||
|
||
We've covered a lot of ground in this chapter and the application is too big to reprint here.
|
||
Please visit the <live-example></live-example> and
|
||
where you can download the final source code.
|
||
|
||
本章中涉及到了很多背景知识,而且本应用程序也太大了,所以没法在这里显示。请访问<live-example>在线例子</live-example>,在那里你可以下载最终的源码。
|
||
|
||
.l-main-section
|
||
|
||
:marked
|
||
## Appendices
|
||
|
||
## 附录
|
||
|
||
The balance of this chapter is a set of appendices that
|
||
elaborate some of the points we covered quickly above.
|
||
|
||
本章剩下的部分是一组附录,它详尽阐述了我们曾匆匆带过的一些知识点。
|
||
|
||
The appendix material isn't essential. Continued reading is for the curious.
|
||
|
||
该附件中的内容不是必须的,感兴趣的人才需要阅读它。
|
||
|
||
|
||
.l-main-section#link-parameters-array
|
||
:marked
|
||
## Appendix: Link Parameters Array
|
||
|
||
## 附录:链接参数数组
|
||
|
||
We've mentioned the *Link Parameters Array* several times. We've used it several times.
|
||
|
||
我们已经数次提及*链接参数数组*,也用过好几次了。
|
||
|
||
A link parameters array holds the ingredients for router navigation:
|
||
|
||
链接参数数组保存路由导航时所需的成分:
|
||
|
||
* the *path* of the route to the destination component
|
||
|
||
* 指向目标组件的那个路由的*路径(path)*
|
||
|
||
* required and optional route parameters that go into the route URL
|
||
|
||
* 必备路由参数和可选路由参数,它们将进入该路由的URL
|
||
|
||
We can bind the `RouterLink` directive to such an array like this:
|
||
|
||
我们可以把`RouterLink`指令绑定到一个数组,就像这样:
|
||
|
||
+makeExcerpt('app/app.component.3.ts', 'h-anchor', '')
|
||
|
||
:marked
|
||
We've written a two element array when specifying a route parameter like this
|
||
|
||
在指定路由参数时,我们写过一个双元素的数组,就像这样:
|
||
|
||
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'nav-to-detail', '')
|
||
|
||
:marked
|
||
We can provide optional route parameters in an object like this:
|
||
|
||
我们可以在对象中提供可选的路由参数,就像这样:
|
||
|
||
+makeExcerpt('app/app.component.3.ts', 'cc-query-params', '')
|
||
|
||
:marked
|
||
These three examples cover our needs for an app with one level routing.
|
||
The moment we add a child router, such as the *Crisis Center*, we create new link array possibilities.
|
||
|
||
这三个例子覆盖了我们在单级路由的应用中所需的一切。在添加一个像*危机中心*一样的子路由时,我们创建新链接数组组合。
|
||
|
||
Recall that we specified a default child route for *Crisis Center* so this simple `RouterLink` is fine.
|
||
|
||
回忆一下,我们曾为*危机中心*指定过一个默认的子路由,以便能使用这种简单的`RouterLink`。
|
||
|
||
+makeExcerpt('app/app.component.3.ts', 'cc-anchor-w-default', '')
|
||
|
||
:marked
|
||
Let's parse it out.
|
||
|
||
让我们把它分解出来:
|
||
|
||
* The first item in the array identifies the parent route ('/crisis-center').
|
||
|
||
* 数组中的第一个条目标记出了父路由('/crisis-center')。
|
||
|
||
* There are no parameters for this parent route so we're done with it.
|
||
|
||
* 这个父路由没有参数,因此这步已经完成了。
|
||
|
||
* There is no default for the child route so we need to pick one.
|
||
|
||
* 没有默认的子路由,因此我们得选取一个。
|
||
|
||
* We decide to go to the `CrisisListComponent` whose route path is '/' but we don't need to explicitly add it
|
||
|
||
* 我们决定跳转到`CrisisListComponent`,它的路由路径是'/',但我们不用显式的添加它。
|
||
|
||
* Voila! `['/crisis-center']`.
|
||
|
||
* 哇!`['/crisis-center']`。
|
||
|
||
Let's take it a step further.
|
||
This time we'll build a link parameters array that navigates from the root of the application
|
||
down to the "Dragon Crisis".
|
||
|
||
在下一步,我们会用到它。这次,我们要构建一个从根组件往下导航到“巨龙危机”时的链接参数数组:
|
||
|
||
* The first item in the array identifies the parent route ('/crisis-center').
|
||
|
||
* 数组中的第一个条目用来标记出父路由('/crisis-center')。
|
||
|
||
* There are no parameters for this parent route so we're done with it.
|
||
|
||
* 这个父路由没有参数,因此这步已经完成了。
|
||
|
||
* The second item identifies the child route for details about a particular crisis ('/:id').
|
||
|
||
* 数组中的第二个条目('/:id')用来标记出到指定危机的详情页的子路由。
|
||
|
||
* The details child route requires an `id` route parameter
|
||
|
||
* 详细的子路由需要一个`id`路由参数。
|
||
|
||
* We add `id` of the *Dragon Crisis* as the second item in the array (`1`)
|
||
|
||
* 我们把*巨龙危机*的`id`添加为该数组中的第二个条目(`1`)。
|
||
|
||
It looks like this!
|
||
|
||
看起来是这样的:
|
||
|
||
+makeExcerpt('app/app.component.3.ts', 'Dragon-anchor', '')
|
||
|
||
:marked
|
||
If we wanted to, we could redefine our `AppComponent` template with *Crisis Center* routes exclusively:
|
||
|
||
如果想,我们还能单独使用*危机中心*的路由来重定义`AppComponent`的模板。
|
||
|
||
+makeExcerpt('app/app.component.3.ts', 'template', '')
|
||
|
||
:marked
|
||
In sum, we can write applications with one, two or more levels of routing.
|
||
The link parameters array affords the flexibility to represent any routing depth and
|
||
any legal sequence of route paths, (required) router parameters and (optional) route parameter objects.
|
||
|
||
总结:我们可以用一级、两级或多级路由来写应用程序。
|
||
链接参数数组提供了用来表示任意深度路由的链接参数数组以及任意合法的路由参数序列、必须的路由器参数以及可选的路由参数对象。
|
||
|
||
.l-main-section#onInit
|
||
:marked
|
||
## Appendix: Why use an *ngOnInit* method
|
||
|
||
## 附录:为什么要使用*ngOnInit*方法
|
||
|
||
We implemented an `ngOnInit` method in many of our Component classes.
|
||
We did so, for example, in the [HeroDetailComponent](#hero-detail-ctor).
|
||
We might have put the `ngOnInit` logic inside the constructor instead. We didn't for a reason. The reason is *testability*.
|
||
|
||
我们在很多组件类中实现了`ngOnInit`方法。比如在[HeroDetailComponent](#hero-detail-ctor)中就这么用过。也可以把`ngOnInit`中的逻辑放在构造函数中,但为了一个理由而没那么做,这个理由就是*可测试性*。
|
||
|
||
A constructor that has major side-effects can be difficult to test because it starts doing things as soon as
|
||
we create a test instance. In this case, it might have made a request to a remote server, something it shouldn't
|
||
do under test. It may even be impossible to reach the server in the test environment.
|
||
|
||
有显著副作用的构造函数很难测试,因为它在创建测试实例时就开始做事了。这种情况下,它可能已经向远程服务器发起了请求,但有些事情在测试时没法做。可能在测试环境下无法访问服务器。
|
||
|
||
The better practice is to limit what the constructor can do. Mostly it should stash parameters in
|
||
local variables and perform simple instance configuration.
|
||
|
||
更好地实践是限制一下构造函数能做什么。通常它会把参数保存到局部变量中,以及执行简单的实例配置工作。
|
||
|
||
Yet we want an instance of this class to get the hero data from the `HeroService` soon after it is created.
|
||
How do we ensure that happens if not in the constructor?
|
||
|
||
然而我们还是需要该类的实例在创建完之后尽快从`HeroService`中获取英雄数据。如果不能放在构造函数中又该怎么办?
|
||
|
||
Angular detects when a component has certain lifecycle methods like
|
||
[ngOnInit](../api/core/index/OnInit-class.html) and
|
||
[ngOnDestroy](../api/core/index/OnDestroy-class.html) and calls
|
||
them
|
||
at the appropriate moment.
|
||
|
||
Angular会负责检测组件是否具有特定的生命周期方法,比如[ngOnInit](../api/core/OnInit-interface.html)和
|
||
[ngOnDestroy](../api/core/OnDestroy-interface.html),并在合适的时机调用它们。
|
||
|
||
Angular will call `ngOnInit` when we navigate to the `HeroDetailComponent`, we'll get the `id` from the `ActivatedRoute`
|
||
params and ask the server for the hero with that `id`.
|
||
|
||
Angular会在我们导航到`HeroDetailComponent`时调用`ngOnInit`,我们将从`ActivatedRoute`的路由参数中取得`id`,并向服务器请求具有这个`id`的英雄。
|
||
|
||
We too can call that `ngOnInit` method in our tests if we wish ... after taking control of the injected
|
||
`HeroService` and (perhaps) mocking it.
|
||
|
||
在获得了已注入的`HeroService`实例并(可能)做好模拟(Mock)之后,我们可以随时在测试中调用`ngOnInit`方法。
|
||
|
||
a#browser-url-styles
|
||
.l-main-section#location-strategy
|
||
:marked
|
||
## Appendix: *LocationStrategy* and browser URL styles
|
||
|
||
## 附录:*LocationStrategy*以及浏览器URL样式
|
||
|
||
When the router navigates to a new component view, it updates the browser's location and history
|
||
with a URL for that view.
|
||
This is a strictly local URL. The browser shouldn't send this URL to the server
|
||
and should not reload the page.
|
||
|
||
当路由器导航到一个新的组件视图时,它会用该视图的URL来更新浏览器的当前地址以及历史。
|
||
严格来说,这个URL其实是本地的,浏览器不会把该URL发给服务器,并且不会重新加载此页面。
|
||
|
||
Modern HTML 5 browsers support
|
||
[history.pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries),
|
||
a technique that changes a browser's location and history without triggering a server page request.
|
||
The router can compose a "natural" URL that is indistinguishable from
|
||
one that would otherwise require a page load.
|
||
|
||
现代HTML 5浏览器支持[history.pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries) API,
|
||
这是一项可以改变浏览器的当前地址和历史,却又不会触发服务端页面请求的技术。
|
||
路由器可以合成出一个“自然的”URL,它看起来和那些需要进行页面加载的URL没什么区别。
|
||
|
||
Here's the *Crisis Center* URL in this "HTML 5 pushState" style:
|
||
|
||
下面是*危机中心*的URL在“HTML 5 pushState”风格下的样子:
|
||
|
||
code-example(format=".", language="bash").
|
||
localhost:3002/crisis-center/
|
||
|
||
:marked
|
||
Older browsers send page requests to the server when the location URL changes ...
|
||
unless the change occurs after a "#" (called the "hash").
|
||
Routers can take advantage of this exception by composing in-application route
|
||
URLs with hashes. Here's a "hash URL" that routes to the *Crisis Center*
|
||
|
||
老旧的浏览器在当前地址的URL变化时总会往服务器发送页面请求……唯一的例外规则是:当这些变化位于“#”(被称为“hash”)后面时不会发送。通过把应用内的路由URL拼接在`#`之后,路由器可以获得这条“例外规则”带来的优点。下面是到*危机中心*路由的“hash URL”:
|
||
|
||
code-example(format=".", language="bash").
|
||
localhost:3002/src/#/crisis-center/
|
||
|
||
:marked
|
||
The Router supports both styles with two `LocationStrategy` providers:
|
||
|
||
路由器通过两种`LocationStrategy`提供商来支持所有这些风格:
|
||
|
||
1. `PathLocationStrategy` - the default "HTML 5 pushState" style.
|
||
|
||
1. `PathLocationStrategy` - 默认的策略,支持“HTML 5 pushState”风格。
|
||
|
||
1. `HashLocationStrategy` - the "hash URL" style.
|
||
|
||
1. `HashLocationStrategy` - 支持“hash URL”风格。
|
||
|
||
The `RouterModule.forRoot` function sets the `LocationStrategy` to the `PathLocationStrategy`,
|
||
making it the default strategy.
|
||
We can switch to the `HashLocationStrategy` with an override during the bootstrapping process if we prefer it.
|
||
|
||
`RouterModule.forRoot`函数把`LocationStrategy`设置成了`PathLocationStrategy`,使其成为了默认策略。
|
||
我们可以在启动过程中改写(override)它,来切换到`HashLocationStrategy`风格 —— 如果我们更喜欢这种。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
Learn about "providers" and the bootstrap process in the
|
||
[Dependency Injection chapter](dependency-injection.html#bootstrap)
|
||
|
||
要学习关于“提供商”和启动过程的更多知识,参见[依赖注入](dependency-injection.html#bootstrap)一章。
|
||
|
||
|
||
:marked
|
||
### Which Strategy is Best?
|
||
|
||
### 哪种策略更好?
|
||
|
||
We must choose a strategy and we need to make the right call early in the project.
|
||
It won't be easy to change later once the application is in production
|
||
and there are lots of application URL references in the wild.
|
||
|
||
我们必须选择一种策略,并且在项目的早期就这么干。一旦该应用进入了生产阶段,要改起来可就不容易了,因为外面已经有了大量对应用URL的引用。
|
||
|
||
Almost all Angular projects should use the default HTML 5 style.
|
||
It produces URLs that are easier for users to understand.
|
||
And it preserves the option to do **server-side rendering** later.
|
||
|
||
几乎所有的Angular项目都会使用默认的HTML 5风格。它生成的URL更易于被用户理解,它也为将来做**服务端渲染**预留了空间。
|
||
|
||
Rendering critical pages on the server is a technique that can greatly improve
|
||
perceived responsiveness when the app first loads.
|
||
An app that would otherwise take ten or more seconds to start
|
||
could be rendered on the server and delivered to the user's device
|
||
in less than a second.
|
||
|
||
在服务器端渲染指定的页面,是一项可以在该应用首次加载时大幅提升响应速度的技术。那些原本需要十秒甚至更长时间加载的应用,可以预先在服务端渲染好,并在少于一秒的时间内完整呈现在用户的设备上。
|
||
|
||
This option is only available if application URLs look like normal web URLs
|
||
without hashes (#) in the middle.
|
||
|
||
只有当应用的URL看起来像是标准的Web URL,中间没有hash(#)时,这个选项才能生效。
|
||
|
||
Stick with the default unless you have a compelling reason to
|
||
resort to hash routes.
|
||
|
||
除非你有强烈的理由不得不使用hash路由,否则就应该坚决使用默认的HTML 5路由风格。
|
||
|
||
### HTML 5 URLs and the *<base href>*
|
||
|
||
### HTML 5 URL与*<base href>*
|
||
|
||
While the router uses the "[HTML 5 pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries)"
|
||
style by default, we *must* configure that strategy with a **base href**
|
||
|
||
由于路由器默认使用“[HTML 5 pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries)”风格,所以我们*必须*用一个**base href**来配置该策略(Strategy)。
|
||
|
||
The preferred way to configure the strategy is to add a
|
||
[<base href> element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag
|
||
in the `<head>` of the `index.html`.
|
||
|
||
配置该策略的首选方式是往`index.html`的`<head>`中添加一个[<base href> element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)标签。
|
||
|
||
+makeExcerpt('index.1.html', 'base-href', '')
|
||
|
||
:marked
|
||
Without that tag, the browser may not be able to load resources
|
||
(images, css, scripts) when "deep linking" into the app.
|
||
Bad things could happen when someone pastes an application link into the
|
||
browser's address bar or clicks such a link in an email link.
|
||
|
||
如果没有此标签,当通过“深链接”进入该应用时,浏览器就不能加载资源(图片、CSS、脚本)。如果有人把应用的链接粘贴进浏览器的地址栏或从邮件中点击应用的链接时,这种问题就发生。
|
||
|
||
Some developers may not be able to add the `<base>` element, perhaps because they don't have
|
||
access to `<head>` or the `index.html`.
|
||
|
||
有些开发人员可能无法添加`<base>`元素,这可能是因为它们没有访问`<head>`或`index.html`的权限。
|
||
|
||
Those developers may still use HTML 5 URLs by taking two remedial steps:
|
||
|
||
它们仍然可以使用HTML 5格式的URL,但要采取两个步骤进行补救:
|
||
|
||
1. Provide the router with an appropriate `APP_BASE_HREF` value.
|
||
|
||
1. 用适当的`APP_BASE_HREF`值提供(provide)路由器。
|
||
|
||
1. Use **absolute URLs** for all web resources: css, images, scripts, and template html files.
|
||
|
||
1. 对所有Web资源使用**绝对地址**:CSS、图片、脚本、模板HTML。
|
||
|
||
.l-sub-section
|
||
|
||
:marked
|
||
Learn about the [APP_BASE_HREF](../api/common/index/APP_BASE_HREF-let.html)
|
||
in the API Guide.
|
||
|
||
你可以到API指南中学习关于[APP_BASE_HREF](../api/router/APP_BASE_HREF-let.html)的更多知识。
|
||
|
||
:marked
|
||
### *HashLocationStrategy*
|
||
|
||
### *HashLocationStrategy*
|
||
|
||
We can go old-school with the `HashLocationStrategy` by
|
||
providing the `useHash: true` in an object as the second argument of the `RouterModule.forRoot`
|
||
in our `AppModule`.
|
||
|
||
我们可以在根模块的`RouterModule.forRoot`的第二个参数中传入一个带有`useHash: true`的对象,以回到基于`HashLocationStrategy`的传统方式。
|
||
|
||
+makeExcerpt('app/app.module.6.ts (hash URL strategy)', '')
|