1594 lines
63 KiB
Plaintext
1594 lines
63 KiB
Plaintext
- var _example = 'toh-5';
|
||
|
||
block includes
|
||
include ../_util-fns
|
||
- var _appRoutingTsVsAppComp = 'app.module.ts'
|
||
- var _declsVsDirectives = 'declarations'
|
||
- var _RoutesVsAtRouteConfig = 'Routes'
|
||
- var _RouterModuleVsRouterDirectives = 'RouterModule'
|
||
- var _redirectTo = 'redirectTo'
|
||
|
||
:marked
|
||
# Routing Around the App
|
||
|
||
# 应用中的路由
|
||
|
||
We received new requirements for our Tour of Heroes application:
|
||
|
||
我们收到了一份《英雄指南》的新需求:
|
||
|
||
* Add a *Dashboard* view.
|
||
|
||
* 添加一个*仪表盘*视图。
|
||
|
||
* Navigate between the *Heroes* and *Dashboard* views.
|
||
|
||
* 在*英雄列表*和*仪表盘*视图之间导航。
|
||
|
||
* Clicking on a hero in either view navigates to a detail view of the selected hero.
|
||
|
||
* 无论在哪个视图中点击一个英雄,都会导航到该英雄的详情页。
|
||
|
||
* Clicking a *deep link* in an email opens the detail view for a particular hero;
|
||
|
||
* 在邮件中点击一个*深链接*,会直接打开一个特定英雄的详情视图。
|
||
|
||
When we’re done, users will be able to navigate the app like this:
|
||
|
||
完成时,用户就能像这样浏览一个应用:
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="查看导航")
|
||
:marked
|
||
We'll add Angular’s *Router* to our app to satisfy these requirements.
|
||
|
||
我们将把Angular*路由器*加入应用中,以满足这些需求。(译注:硬件领域中的路由器是用来帮你找到另一台网络设备的,而这里的路由器用于帮你找到一个组件)
|
||
|
||
.l-sub-section
|
||
:marked
|
||
The [Routing and Navigation](../guide/router.html) chapter covers the router
|
||
in more detail than we will in this tutorial.
|
||
|
||
[路由和导航](../guide/router.html)章节介绍了更多关于路由的细节。
|
||
|
||
:marked
|
||
Run the <live-example></live-example> for this part.
|
||
|
||
运行这部分的<live-example>在线例子</live-example>。
|
||
|
||
+ifDocsFor('ts|js')
|
||
.l-sub-section
|
||
img(src='/resources/images/devguide/plunker-separate-window-button.png' alt="弹出窗口" 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:
|
||
|
||
注意看浏览器地址栏中的URL变化,点击右上角的蓝色'X'按钮,弹出预览窗口。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Where We Left Off
|
||
|
||
## 我们在哪
|
||
|
||
Before we continue with our Tour of Heroes, let’s verify that we have the following structure after adding our hero service
|
||
and hero detail component. If not, we’ll need to go back and follow the previous chapters.
|
||
|
||
在继续《英雄指南》之前,先来检查一下,在添加了英雄服务和英雄详情组件之后,你是否已经有了如下目录结构。如果没有,你得先回上一章,再照做一遍。
|
||
|
||
block intro-file-tree
|
||
.filetree
|
||
.file angular-tour-of-heroes
|
||
.children
|
||
.file app
|
||
.children
|
||
.file app.component.ts
|
||
.file app.module.ts
|
||
.file hero.service.ts
|
||
.file hero.ts
|
||
.file hero-detail.component.ts
|
||
.file main.ts
|
||
.file mock-heroes.ts
|
||
.file node_modules ...
|
||
.file index.html
|
||
.file package.json
|
||
.file styles.css
|
||
.file systemjs.config.js
|
||
.file tsconfig.json
|
||
|
||
block keep-app-running
|
||
:marked
|
||
### Keep the app transpiling and running
|
||
|
||
### 让应用代码保持转译和运行
|
||
|
||
Open a terminal/console window and enter the following command to
|
||
start the TypeScript compiler, start the server, and watch for changes:
|
||
|
||
打开terminal/console窗口,运行下列命令启动TypeScript编译器,它会监视文件变更,并启动开发服务器:
|
||
|
||
code-example(language="bash").
|
||
npm start
|
||
|
||
:marked
|
||
The application runs and updates automatically as we continue to build the Tour of Heroes.
|
||
|
||
我们继续构建《英雄指南》,应用也会保持运行并自动更新。
|
||
|
||
## Action plan
|
||
|
||
## 行动计划
|
||
|
||
Here's our plan:
|
||
|
||
下面是我们的计划:
|
||
|
||
* Turn `AppComponent` into an application shell that only handles navigation
|
||
|
||
* 把`AppComponent`变成应用程序的“壳”,它只处理导航,
|
||
|
||
* Relocate the *Heroes* concerns within the current `AppComponent` to a separate `HeroesComponent`
|
||
|
||
* 把现在由`AppComponent`关注的*英雄们*移到一个独立的`HeroesComponent`中,
|
||
|
||
* Add routing
|
||
|
||
* 添加路由
|
||
|
||
* Create a new `DashboardComponent`
|
||
|
||
* 添加一个新的`DashboardComponent`组件
|
||
|
||
* Tie the *Dashboard* into the navigation structure
|
||
|
||
* 把*仪表盘*加入导航结构中。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
*Routing* is another name for *navigation*. The *router* is the mechanism for navigating from view to view.
|
||
|
||
*路由*是导航的另一个名字。*路由器*就是从一个视图导航到另一个视图的机制。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Splitting the *AppComponent*
|
||
|
||
## 拆分*AppComponent*
|
||
|
||
Our current app loads `AppComponent` and immediately displays the list of heroes.
|
||
|
||
现在的应用会加载`AppComponent`组件,并且立刻显示出英雄列表。
|
||
|
||
Our revised app should present a shell with a choice of views (*Dashboard* and *Heroes*) and then default to one of them.
|
||
|
||
我们修改后的应用将提供一个壳,它会选择*仪表盘*和*英雄列表*视图之一,然后默认显示它。
|
||
|
||
The `AppComponent` should only handle navigation.
|
||
Let's move the display of *Heroes* out of `AppComponent` and into its own `HeroesComponent`.
|
||
|
||
`AppComponent`组件应该只处理导航。
|
||
我们来把*英雄列表*的显示职责,从`AppComponent`移到`HeroesComponent`组件中。
|
||
|
||
### *HeroesComponent*
|
||
|
||
`AppComponent` is already dedicated to *Heroes*.
|
||
Instead of moving anything out of `AppComponent`, we'll just rename it `HeroesComponent`
|
||
and create a new `AppComponent` shell separately.
|
||
|
||
`AppComponent`的职责已经被移交给`HeroesComponent`了。
|
||
与其把`AppComponent`中所有的东西都搬过去,不如索性把它改名为`HeroesComponent`,然后单独创建一个新的`AppComponent`壳。
|
||
|
||
The steps are to rename:
|
||
|
||
改名的步骤如下:
|
||
|
||
* <span ngio-ex>app.component.ts</span> file to <span ngio-ex>heroes.component.ts</span>
|
||
|
||
* 把<span ngio-ex>app.component.ts</span>文件改名为<span ngio-ex>heroes.component.ts</span>
|
||
|
||
* `AppComponent` class to `HeroesComponent`
|
||
|
||
* 把`AppComponent`类改名为`HeroesComponent`
|
||
|
||
* Selector `my-app` to `my-heroes`
|
||
|
||
* 把`my-app`选择器改名为`my-heroes`
|
||
|
||
+makeExcerpt('app/heroes.component.ts (showing renamings only)', 'renaming')
|
||
|
||
:marked
|
||
## Create *AppComponent*
|
||
|
||
## 创建*AppComponent*
|
||
|
||
The new `AppComponent` will be the application shell.
|
||
It will have some navigation links at the top and a display area below for the pages we navigate to.
|
||
|
||
新的`AppComponent`将成为应用的“壳”。
|
||
它将在顶部放一些导航链接,并且把我们要导航到的页面放在下面的显示区中。
|
||
|
||
The initial steps are:
|
||
|
||
这些起始步骤是:
|
||
|
||
|
||
* add the supporting `import` statements.
|
||
|
||
* 添加支持性的`import`语句。
|
||
|
||
* Create the file <span ngio-ex>app/app.component.ts</span>.
|
||
|
||
* 创建一个名叫<span ngio-ex>app.component.ts</span>的新文件。
|
||
|
||
* Define an <span if-docs="ts">exported</span> `AppComponent` class.
|
||
|
||
* 定义一个<span if-docs="ts">导出的</span> ``AppComponent`类。
|
||
|
||
* Add an `@Component` !{_decorator} above the class with a `my-app` selector.
|
||
|
||
* 在类的上方添加`@Component`元数据装饰器,装饰器中带有`my-app`选择器。
|
||
|
||
* Move the following from `HeroesComponent` to `AppComponent`:
|
||
|
||
* 将下面的项目从`HeroesComponent`移到`AppComponent`:
|
||
* `title` class property
|
||
|
||
* `title`类属性
|
||
|
||
* `@Component` template `<h1>` element, which contains a binding to `title`
|
||
|
||
* 在模板中添加一个`<h1>`标签,包裹着到`title`属性的绑定。
|
||
|
||
* Add a `<my-heroes>` element to the app template just below the heading so we still see the heroes.
|
||
*
|
||
在模板中添加`<my-heroes>`标签,以便我们仍能看到英雄列表。
|
||
|
||
* Add `HeroesComponent` to the `!{_declsVsDirectives}` !{_array} of `!{_AppModuleVsAppComp}` so Angular recognizes the `<my-heroes>` tags.
|
||
|
||
* 添加`HeroesComponent`组件到根模块的`declarations`数组中,以便Angular能认识`<my-heroes>`标签。
|
||
|
||
* Add `HeroService` to the `providers` !{_array} of `!{_AppModuleVsAppComp}` because we'll need it in every other view.
|
||
|
||
* 添加`HeroService`到根模块的`providers`数组中,因为我们的每一个视图都需要它。
|
||
|
||
* Remove `HeroService` from the `HeroesComponent` `providers` !{_array} since it has been promoted.
|
||
|
||
* 从`HerosComponent`的`providers`数组中移除HeroService`,因为它被提到模块了。
|
||
|
||
* Add the supporting `import` statements for `AppComponent`.
|
||
|
||
* 导入`AppComponent`。
|
||
|
||
|
||
Our first draft looks like this:
|
||
|
||
我们的第一个草稿版就像这样:
|
||
|
||
block app-comp-v1
|
||
+makeTabs(
|
||
`toh-5/ts/app/app.component.1.ts,
|
||
toh-5/ts/app/app.module.1.ts`,
|
||
',',
|
||
`app/app.component.ts (v1),
|
||
app/app.module.ts (v1)`)
|
||
|
||
:marked
|
||
.callout.is-critical
|
||
header Remove <i>HeroService</i> from the <i>HeroesComponent</i> providers
|
||
header 从<i>HeroesComponent</i>的<code>providers</code>中移除<i>HeroService</i>
|
||
:marked
|
||
Go back to the `HeroesComponent` and **remove the `HeroService`** from its `providers` array.
|
||
We are *promoting* this service from the `HeroesComponent` to the root `NgModule`.
|
||
We ***do not want two copies*** of this service at two different levels of our app.
|
||
|
||
回到`HeroesComponent`,并从`providers`数组中**移除`HeroService`**。
|
||
我们要把它从`HeroesComponent`*提升*到根`NgModule`中。
|
||
我们可不希望在应用的两个不同层次上存在它的***两个副本***。
|
||
|
||
:marked
|
||
The app still runs and still displays heroes.
|
||
Our refactoring of `AppComponent` into a new `AppComponent` and a `HeroesComponent` worked!
|
||
We have done no harm.
|
||
|
||
应用仍然在运行,并显示着英雄列表。
|
||
我们把`AppComponent`重构成了一个新的`AppComponent`和`HeroesComponent`,它们工作得很好!
|
||
我们毫发无损的完成了这次重构。
|
||
|
||
:marked
|
||
## Add Routing
|
||
|
||
## 添加路由
|
||
|
||
We're ready to take the next step.
|
||
Instead of displaying heroes automatically, we'd like to show them *after* the user clicks a button.
|
||
In other words, we'd like to navigate to the list of heroes.
|
||
|
||
我们已准备好开始下一步。
|
||
与其自动显示英雄列表,我们更希望在用户点击按钮之后才显示它。
|
||
换句话说,我们希望“导航”到英雄列表。
|
||
|
||
We'll need the Angular *Router*.
|
||
|
||
我们需要Angular的*路由器*。
|
||
|
||
block angular-router
|
||
:marked
|
||
The Angular router is an external, optional Angular NgModule called `RouterModule`.
|
||
The router is a combination of multiple provided services (`RouterModule`),
|
||
multiple directives (`RouterOutlet, RouterLink, RouterLinkActive`),
|
||
and a configuration (`Routes`). We'll configure our routes first.
|
||
|
||
Angular路由器是一个可选的外部Angular NgModule,名叫`RouterModule`。
|
||
路由器包含了多种服务(`RouterModule`)、多种指令(`RouterOutlet, RouterLink, RouterLinkActive`)、
|
||
和一套配置(`Routes`)。我们将先配置路由。
|
||
|
||
:marked
|
||
### Add the base tag
|
||
|
||
### 设置base标签
|
||
|
||
Open the `index.html` and add `<base href="/">` at the top of the `<head>` section.
|
||
|
||
打开`index.html`并且在`<head>`区的顶部添加`<base href="/">`语句。
|
||
|
||
+makeExcerpt('index.html', 'base-href')
|
||
|
||
.callout.is-important
|
||
header base href is essential
|
||
header base href是不可或缺的
|
||
:marked
|
||
See the *base href* section of the [Router](../guide/router.html#!#base-href) chapter to learn why this matters.
|
||
|
||
查看[路由器](../guide/router-deprecated.html#!#base-href)一章的*base href*部分,了解为何如此。
|
||
a#configure-routes
|
||
block router-config-intro
|
||
:marked
|
||
### Configure routes
|
||
|
||
### 配置路由
|
||
|
||
Our application doesn't have any routes yet.
|
||
We'll start by creating a configuration for the application routes.
|
||
|
||
本应用还没有路由。我们来为应用的路由新建一个配置。
|
||
|
||
:marked
|
||
*Routes* tell the router which views to display when a user clicks a link or
|
||
pastes a URL into the browser address bar.
|
||
|
||
*路由*告诉路由器,当用户点击链接或者把URL粘贴到浏览器地址栏时,应该显示哪个视图。
|
||
|
||
Let's define our first route as a route to the heroes component:
|
||
|
||
我们的第一个路由是指向`HeroesComponent`的。
|
||
|
||
- var _file = _docsFor == 'dart' ? 'app.component.ts' : 'app.module.2.ts'
|
||
+makeExcerpt('app/' + _file + ' (heroes route)', 'heroes')
|
||
|
||
- var _are = _docsFor == 'dart' ? 'takes' : 'are'
|
||
- var _routePathPrefix = _docsFor == 'dart' ? '/' : ''
|
||
|
||
:marked
|
||
The `!{_RoutesVsAtRouteConfig}` !{_are} !{_an} !{_array} of *route definitions*.
|
||
We have only one route definition at the moment but rest assured, we'll add more.
|
||
|
||
这个`RouteConfig`是一个*路由定义*的数组。
|
||
此刻我们只有一个路由定义,但别急,后面还会添加更多。
|
||
|
||
This *route definition* has the following parts:
|
||
|
||
“路由定义”包括几个部分:
|
||
|
||
* **path**: the router matches this route's path to the URL in the browser address bar (`!{_routePathPrefix}/heroes`).
|
||
|
||
* **path**: 路由器会用它来匹配路由中指定的路径和浏览器地址栏中的当前路径,如`!{_routePathPrefix}/heroes`。
|
||
|
||
* **component**: the component that the router should create when navigating to this route (`HeroesComponent`).
|
||
|
||
* **component**: 导航到此路由时,路由器需要创建的组件,如`HeroesComponent`。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Learn more about defining routes with `!{_RoutesVsAtRouteConfig}` in the [Routing](../guide/router.html) chapter.
|
||
|
||
到[路由](../guide/router.html)章节学习更多关于使用路由库来定义路由的知识。
|
||
|
||
+ifDocsFor('ts|js')
|
||
:marked
|
||
### Make the router available
|
||
|
||
### 让路由器可用
|
||
|
||
We've setup the initial route configuration. Now we'll add it to our `AppModule`.
|
||
We'll add our configured `RouterModule` to the `AppModule` imports !{_array}.
|
||
|
||
我们设置了初始路由配置。现在把它添加到`AppModule`里。添加配置好的`RouterModule`到`AppModule`的`imports`数组中。
|
||
|
||
+makeExcerpt('app/app.module.2.ts (app routing)', '')
|
||
|
||
.l-sub-section
|
||
:marked
|
||
We use the `forRoot` method because we're providing a configured router at the _root_ of the application.
|
||
The `forRoot` method gives us the Router service providers and directives needed for routing, and
|
||
performs the initial navigation based on the current browser URL.
|
||
|
||
这里使用了`forRoot`方法,因为我们在应用根部提供配置的路由器。`forRoot`方法提供了路由需要的路由服务提供商和指令,并基于当前浏览器URL初始化导航。
|
||
|
||
- var _heroesRoute = _docsFor == 'dart' ? "'Heroes'" : 'heroes'
|
||
:marked
|
||
### Router Outlet
|
||
|
||
### 路由插座(Outlet)
|
||
|
||
If we paste the path, `/heroes`, into the browser address bar,
|
||
the router should match it to the `!{_heroesRoute}` route and display the `HeroesComponent`.
|
||
But where?
|
||
|
||
如果我们把路径`/heroes`粘贴到浏览器的地址栏,路由器会匹配到`'Heroes'`路由,并显示`HeroesComponent`组件。
|
||
但问题是,该把它显示在哪呢?
|
||
|
||
We have to ***tell it where*** by adding a `<router-outlet>` element to the bottom of the template.
|
||
`RouterOutlet` is one of the <span if-docs="ts">directives provided by</span> the `!{_RouterModuleVsRouterDirectives}`.
|
||
The router displays each component immediately below the `<router-outlet>` as we navigate through the application.
|
||
|
||
我们必须***告诉它位置***,所以我们把`<router-outlet>`标签添加到模板的底部。
|
||
`RouterOutlet`是`!{_RouterModuleVsRouterDirectives}`提供的<span if-docs="ts">指令之一</span>。
|
||
当我们在应用中导航时,路由器就把激活的组件显示在`<router-outlet>`里面。
|
||
|
||
### Router Links
|
||
|
||
### 路由器链接
|
||
|
||
We don't really expect users to paste a route URL into the address bar.
|
||
We add an anchor tag to the template which, when clicked, triggers navigation to the `HeroesComponent`.
|
||
|
||
我们当然不会真让用户往地址栏中粘贴路由的URL,而应该在模板中的什么地方添加一个a链接标签。点击时,就会导航到`HeroesComponent`组件。
|
||
|
||
The revised template looks like this:
|
||
|
||
修改过的模板是这样的:
|
||
|
||
+makeExcerpt('app/app.component.1.ts', 'template-v2')
|
||
|
||
block routerLink
|
||
:marked
|
||
Notice the `routerLink` binding in the anchor tag.
|
||
We bind the `RouterLink` directive (another of the `RouterModule` directives) to a string
|
||
that tells the router where to navigate when the user clicks the link.
|
||
|
||
注意,a标签中的`[routerLink]`绑定。我们把`RouterLink`指令(`ROUTER_DIRECTIVES`中的另一个指令)绑定到一个字符串。它将告诉路由器,当用户点击这个链接时,应该导航到哪里。
|
||
|
||
Since our link is not dynamic, we define a *routing instruction* with a **one-time binding** to our route **path**.
|
||
Looking back at the route configuration, we confirm that `'/heroes'` is the path of the route to the `HeroesComponent`.
|
||
|
||
由于这个链接不是动态的,我们只要用**一次性绑定**的方式绑定到路由的路径(path)就行了。
|
||
回来看路由配置表,我们清楚的看到,这个路径 —— `'/heroes'`就是指向`HeroesComponent`的那个路由的路径。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Learn more about dynamic router links and the *link parameters array*
|
||
in the [Routing](../guide/router.html#link-parameters-array) chapter.
|
||
|
||
参阅[路由](../guide/router.html#link-parameters-array)章学习更多动态路由器链接和*链接参数数组*的知识。
|
||
|
||
:marked
|
||
Refresh the browser. We see only the app title and heroes link. We don't see the heroes list.
|
||
|
||
刷新浏览器。我们只看到了应用标题和英雄链接。英雄列表到哪里去了?
|
||
|
||
.l-sub-section
|
||
:marked
|
||
The browser's address bar shows `/`.
|
||
The route path to `HeroesComponent` is `/heroes`, not `/`.
|
||
We don't have a route that matches the path `/`, so there is nothing to show.
|
||
That's something we'll want to fix.
|
||
|
||
浏览器的地址栏显示的是`/`。而到`HeroesComponent`的路由中的路径是`/heroes`,不是`/`。
|
||
我们没有任何路由能匹配当前的路径`/`,所以,自然没啥可显示的。
|
||
接下来,我们就修复这个问题。
|
||
|
||
:marked
|
||
We click the *Heroes* navigation link, the browser bar updates to `/heroes`,
|
||
and now we see the list of heroes. We are navigating at last!
|
||
|
||
我们点击“英雄列表(Heroes)”导航链接,浏览器地址栏更新为`/heroes`,并且看到了英雄列表。我们终于导航过去了!
|
||
|
||
At this stage, our `AppComponent` looks like this.
|
||
|
||
在这个阶段,`AppComponent`看起来是这样的:
|
||
|
||
+makeExample('app/app.component.1.ts', 'v2', 'app/app.component.ts (v2)')
|
||
:marked
|
||
The *AppComponent* is now attached to a router and displaying routed views.
|
||
For this reason and to distinguish it from other kinds of components,
|
||
we call this type of component a *Router Component*.
|
||
|
||
*AppComponent*现在加上了路由器,并能显示路由到的视图了。
|
||
因此,为了把它从其它种类的组件中区分出来,我们称这类组件为*路由器组件*。
|
||
|
||
:marked
|
||
## Add a *Dashboard*
|
||
|
||
## 添加一个*仪表盘*
|
||
|
||
Routing only makes sense when we have multiple views. We need another view.
|
||
|
||
当我们有多个视图的时候,路由才有意义。所以我们需要另一个视图。
|
||
|
||
Create a placeholder `DashboardComponent` that gives us something to navigate to and from.
|
||
|
||
先创建一个`DashboardComponent`的占位符,让我们可以导航到它或从它导航出来。
|
||
|
||
+makeExcerpt('app/dashboard.component.1.ts (v1)', '')
|
||
:marked
|
||
We’ll come back and make it more useful later.
|
||
|
||
我们先不实现它,稍后,我们再回来,给这个组件一些实际用途。
|
||
|
||
### Configure the dashboard route
|
||
|
||
### 配置仪表盘路由
|
||
|
||
Go back to `!{_appRoutingTsVsAppComp}` and teach it to navigate to the dashboard.
|
||
|
||
回到`!{_appRoutingTsVsAppComp}`,我们得教它如何导航到这个仪表盘。
|
||
|
||
Import the dashboard component and
|
||
add the following route definition to the `!{_RoutesVsAtRouteConfig}` !{_array} of definitions.
|
||
|
||
先导入`DashboardComponent`类,然后把下列路由的定义添加到`!{_RoutesVsAtRouteConfig}`数组中。
|
||
|
||
- var _file = _docsFor == 'dart' ? 'lib/app_component.dart' : 'app/app.module.3.ts'
|
||
+makeExcerpt(_file + ' (Dashboard route)', 'dashboard')
|
||
|
||
+ifDocsFor('ts|js')
|
||
:marked
|
||
Also import and add `DashboardComponent` to our `AppModule`'s `declarations`.
|
||
|
||
还得把`DashboardComponent`添加到根模块的`declarations`数组中。
|
||
|
||
|
||
+makeExcerpt('app/app.module.ts', 'dashboard')
|
||
|
||
:marked
|
||
#### !{_redirectTo}
|
||
|
||
#### !{_redirectTo}
|
||
|
||
We want the app to show the dashboard when it starts and
|
||
we want to see a nice URL in the browser address bar that says `/dashboard`.
|
||
Remember that the browser launches with `/` in the address bar.
|
||
|
||
我们希望在应用启动的时候就显示仪表盘,而且我们希望在浏览器的地址栏看到一个好看的URL,比如`/dashboard`。
|
||
记住,浏览器启动时,在地址栏中使用的路径是`/`。
|
||
我们可以使用一个重定向路由来达到目的。
|
||
|
||
block redirect-vs-use-as-default
|
||
:marked
|
||
We can use a redirect route to make this happen. Add the following
|
||
to our array of route definitions:
|
||
|
||
可以使用重定向路由来实现它。添加下面的内容到路由定义的数组中:
|
||
|
||
+makeExcerpt('app/app.module.3.ts','redirect')
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Learn about the *redirects* in the [Routing](../guide/router.html#!#redirect) chapter.
|
||
|
||
要学习关于*重定向*的更多知识,参见[路由与导航](../guide/router.html#!#redirect)一章。
|
||
|
||
:marked
|
||
#### Add navigation to the template
|
||
|
||
#### 添加导航到模版中
|
||
|
||
Finally, add a dashboard navigation link to the template, just above the *Heroes* link.
|
||
|
||
最后,在模板上添加一个到仪表盘的导航链接,就放在*英雄(Heroes)*链接的上方。
|
||
|
||
- var _vers = _docsFor == 'dart' ? '' : '.1'
|
||
+makeExcerpt('app/app.component' + _vers + '.ts', 'template-v3')
|
||
|
||
.l-sub-section
|
||
:marked
|
||
We nested the two links within `<nav>` tags.
|
||
They don't do anything yet but they'll be convenient when we style the links a little later in the chapter.
|
||
|
||
我们在`<nav>`标签中放了两个链接。
|
||
它们现在还没有作用,但稍后,当我们对这些链接添加样式时,会显得比较方便。
|
||
|
||
:marked
|
||
To see these changes in your browser, go to the application root (`/`) and reload.
|
||
The app displays the dashboard and we can navigate between the dashboard and the heroes.
|
||
|
||
刷新浏览器。应用显示出了仪表盘,并可以在仪表盘和英雄列表之间导航了。
|
||
## Dashboard Top Heroes
|
||
## 仪表盘上的顶级英雄
|
||
Let’s spice up the dashboard by displaying the top four heroes at a glance.
|
||
|
||
我们想让仪表盘更有趣,比如:一眼就能看到四个顶级英雄。
|
||
|
||
Replace the `template` metadata with a `templateUrl` property that points to a new
|
||
template file.
|
||
|
||
把元数据中的`template`属性替换为`templateUrl`属性,它将指向一个新的模板文件。
|
||
|
||
Set the `moduleId` property to `module.id` for module-relative loading of the `templateUrl`.
|
||
|
||
设置`moduleId`属性到`module.id`,相对模块加载`templateUrl`。
|
||
|
||
+makeExcerpt('app/dashboard.component.ts', 'metadata')
|
||
|
||
:marked
|
||
Create that file with this content:
|
||
|
||
使用下列内容创建文件:
|
||
|
||
+makeExcerpt('app/dashboard.component.html')
|
||
|
||
:marked
|
||
We use `*ngFor` once again to iterate over a list of heroes and display their names.
|
||
We added extra `<div>` elements to help with styling later in this chapter.
|
||
|
||
我们又一次使用`*ngFor`来在英雄列表上迭代,并显示它们的名字。
|
||
还添加了一个额外的`<div>`元素,来帮助稍后的美化工作。
|
||
|
||
There's a `(click)` binding to a `gotoDetail` method we haven't written yet and
|
||
we're displaying a list of heroes that we don't have.
|
||
We have work to do, starting with those heroes.
|
||
|
||
这里的`(click)`绑定到了`gotoDetail`方法,但我们还没实现它。而且我们也还没有要显示的英雄列表数据。
|
||
我们有活儿干了,就从这些英雄列表开始吧。
|
||
|
||
### Share the *HeroService*
|
||
|
||
### 共享*HeroService*
|
||
|
||
We'd like to re-use the `HeroService` to populate the component's `heroes` !{_array}.
|
||
|
||
我们想要复用`HeroService`来存放组件的`heroes`数组。
|
||
|
||
Recall earlier in the chapter that we removed the `HeroService` from the `providers` !{_array} of `HeroesComponent`
|
||
and added it to the `providers` !{_array} of `!{_AppModuleVsAppComp}`.
|
||
|
||
回忆一下,在前面的章节中,我们从`HeroesComponent`的`providers`数组中移除了`HeroService`服务,并把它添加到顶级组件`!{_AppModuleVsAppComp}`的`providers`数组中。
|
||
|
||
That move created a singleton `HeroService` instance, available to *all* components of the application.
|
||
Angular will inject `HeroService` and we'll use it here in the `DashboardComponent`.
|
||
|
||
这个改动创建了一个`HeroService`的单例对象,它对应用中的*所有*组件都有效。
|
||
在`DashboardComponent`组件中,Angular会把`HeroService`注入进来,我们就能在`DashboardComponent`中使用它了。
|
||
|
||
### Get heroes
|
||
|
||
### 获取英雄数组
|
||
|
||
Open <span ngio-ex>dashboard.component.ts</span> and add the requisite `import` statements.
|
||
|
||
打开<span ngio-ex>dashboard.component.ts</span>文件,并把必备的`import`语句加进去。
|
||
|
||
+makeExcerpt('app/dashboard.component.2.ts','imports')
|
||
|
||
:marked
|
||
We need `OnInit` interface because we'll initialize the heroes in the `ngOnInit` method as we've done before.
|
||
We need the `Hero` and `HeroService` symbols in order to reference those types.
|
||
|
||
我们得实现`OnInit`接口,因为我们要在`ngOnInit`方法中初始化英雄数组 —— 就像上次一样。
|
||
我们需要导入`Hero`类和`HeroService`类来引用它们的数据类型。
|
||
|
||
Now implement the `DashboardComponent` class like this:
|
||
|
||
我们现在就实现`DashboardComponent`类,像这样:
|
||
|
||
+makeExcerpt('app/dashboard.component.2.ts (class)', 'component')
|
||
|
||
:marked
|
||
We've seen this kind of logic before in the `HeroesComponent`:
|
||
|
||
在`HeroesComponent`之前,我们也看到过类似的逻辑:
|
||
|
||
* Define a `heroes` !{_array} property.
|
||
|
||
* 创建一个`heroes`数组属性
|
||
|
||
* Inject the `HeroService` in the constructor and hold it in a private `!{_priv}heroService` field.
|
||
|
||
* 把`HeroService`注入构造函数中,并且把它保存在一个私有的`!{_priv}heroService`字段中。
|
||
|
||
* Call the service to get heroes inside the Angular `ngOnInit` lifecycle hook.
|
||
|
||
* 在Angular的`ngOnInit`生命周期钩子里面调用服务来获得英雄列表。
|
||
|
||
The noteworthy differences: we cherry-pick four heroes (2nd, 3rd, 4th, and 5th)
|
||
and stub the `gotoDetail` method until we're ready to implement it.
|
||
|
||
值得注意的区别是:我们提取了四个英雄(第2、3、4、5个),暂时隔离`gotoDetail`方法直到时机成熟。
|
||
|
||
Refresh the browser and see four heroes in the new dashboard.
|
||
|
||
刷新浏览器,在这个新的仪表盘中就看到了四个英雄。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Navigate to Hero Details
|
||
|
||
## 导航到英雄详情
|
||
|
||
Although we display the details of a selected hero at the bottom of the `HeroesComponent`,
|
||
we don't yet *navigate* to the `HeroDetailComponent` in the three ways specified in our requirements:
|
||
|
||
虽然我们在`HeroesComponent`组件的底部显示了所选英雄的详情,但我们还从没有*导航*到`HeroDetailComponent`组件过 —— 我们曾在需求中指定过三种:
|
||
|
||
1. from the *Dashboard* to a selected hero.
|
||
|
||
1. 从*仪表盘(Dashboard)*导航到一个选定的英雄。
|
||
|
||
1. from the *Heroes* list to a selected hero.
|
||
|
||
1. 从*英雄列表(Heroes)*导航到一个选定的英雄。
|
||
|
||
1. from a "deep link" URL pasted into the browser address bar.
|
||
|
||
1. 把一个指向该英雄的“深链接”URL粘贴到浏览器的地址栏。
|
||
|
||
Adding a hero-detail route seems like an obvious place to start.
|
||
|
||
添加`hero-detail`路由,是一个显而易见的起点。
|
||
|
||
### Routing to a hero detail
|
||
|
||
### 路由到一个英雄详情
|
||
|
||
We'll add a route to the `HeroDetailComponent` in `!{_appRoutingTsVsAppComp}` where our other routes are configured.
|
||
|
||
我们将在`!{_appRoutingTsVsAppComp}`中添加一个到`HeroDetailComponent`的路由,也就是配置其它路由的地方。
|
||
|
||
The new route is a bit unusual in that we must tell the `HeroDetailComponent` *which hero to show*.
|
||
We didn't have to tell the `HeroesComponent` or the `DashboardComponent` anything.
|
||
|
||
新路由的不寻常之处在于,我们必须告诉`HeroDetailComponent`*该显示哪个英雄*。
|
||
以前的`HeroesComponent`组件和`DashboardComponent`组件还从未要求我们告诉它任何东西。
|
||
|
||
At the moment the parent `HeroesComponent` sets the component's `hero` property to a
|
||
hero object with a binding like this.
|
||
|
||
现在,父组件`HeroesComponent`通过数据绑定来把一个英雄对象设置为组件的`hero`属性。就像这样:
|
||
|
||
code-example(language="html").
|
||
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
|
||
|
||
:marked
|
||
That clearly won't work in any of our routing scenarios.
|
||
Certainly not the last one; we can't embed an entire hero object in the URL! Nor would we want to.
|
||
|
||
显然,在我们的任何一个路由场景中它都无法工作。
|
||
不仅如此,我们也没法把一个完整的hero对象嵌入到URL中!不过我们本来也不想这样。
|
||
|
||
### Parameterized route
|
||
|
||
### 参数化路由
|
||
|
||
We *can* add the hero's `id` to the URL. When routing to the hero whose `id` is 11,
|
||
we could expect to see an URL such as this:
|
||
|
||
我们*可以*把英雄的`id`添加到URL中。当导航到一个`id`为11的英雄时,我们期望的URL是这样的:
|
||
|
||
code-example(format='').
|
||
/detail/11
|
||
|
||
:marked
|
||
The `/detail/` part of that URL is constant. The trailing numeric `id` part changes from hero to hero.
|
||
We need to represent that variable part of the route with a *parameter* (or *token*) that stands for the hero's `id`.
|
||
|
||
URL中的`/detail/`部分是固定不变的,但结尾的数字`id`部分会随着英雄的不同而变化。
|
||
我们要把路由中可变的那部分表示成一个*参数(parameter)*或*Token*,用以获取英雄的`id`。
|
||
|
||
### Configure a Route with a Parameter
|
||
|
||
### 配置带参数的路由
|
||
|
||
Here's the *route definition* we'll use.
|
||
|
||
下面是我们将使用的*路由定义*。
|
||
|
||
- var _file = _docsFor == 'dart' ? 'app/app.component.ts' : 'app/app.module.3.ts'
|
||
+makeExcerpt(_file + ' (hero detail)','hero-detail')
|
||
|
||
:marked
|
||
The colon (:) in the path indicates that `:id` is a placeholder to be filled with a specific hero `id`
|
||
when navigating to the `HeroDetailComponent`.
|
||
|
||
路径中的冒号(:)表示`:id`是一个占位符,当导航到这个`HeroDetailComponent`组件时,它将被填入一个特定英雄的`id`。
|
||
|
||
+ifDocsFor('dart')
|
||
.l-sub-section
|
||
:marked
|
||
Remember to import the hero detail component before creating this route.
|
||
|
||
记得在创建这个路由前导入英雄详情组件。
|
||
|
||
:marked
|
||
We're finished with the application routes.
|
||
|
||
我们已经做好了该应用的路由。
|
||
|
||
|
||
We won't add a `'Hero Detail'` link to the template because users
|
||
don't click a navigation *link* to view a particular hero.
|
||
They click a *hero* whether that hero is displayed on the dashboard or in the heroes list.
|
||
|
||
我们没有往模板中添加一个`'英雄详情'`,这是因为用户不会直接点击导航栏中的链接去查看一个特定的英雄。
|
||
它们只会通过在英雄列表或者仪表盘中点击来显示一个英雄。
|
||
|
||
We'll get to those *hero* clicks later in the chapter.
|
||
There's no point in working on them until the `HeroDetailComponent`
|
||
is ready to be navigated *to*.
|
||
|
||
稍后我们会响应这些*英雄*的点击事件。
|
||
现在对它们做什么都还没有意义 —— 除非`HeroDetailComponent`已经做好了,并且能够被导航过去。
|
||
|
||
That will require an `HeroDetailComponent` overhaul.
|
||
|
||
那就需要对`HeroDetailComponent`做一次大修。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Revise the *HeroDetailComponent*
|
||
|
||
## 修改*HeroDetailComponent*
|
||
|
||
Before we rewrite the `HeroDetailComponent`, let's review what it looks like now:
|
||
|
||
在重写`HeroDetailComponent`之前,我们先看看它现在的样子:
|
||
|
||
+makeExample('toh-4/ts/app/hero-detail.component.ts', null, 'app/hero-detail.component.ts (current)')
|
||
|
||
:marked
|
||
The template won't change. We'll display a hero the same way.
|
||
The big changes are driven by how we get the hero.
|
||
|
||
模板不用修改,我们会用原来的方式显示英雄。导致这次大修的原因是如何获得这个英雄的数据。
|
||
|
||
block route-params
|
||
:marked
|
||
We will no longer receive the hero in a parent component property binding.
|
||
The new `HeroDetailComponent` should take the `id` parameter from the `params` observable
|
||
in the `ActivatedRoute` service and use the `HeroService` to fetch the hero with that `id`.
|
||
|
||
我们不会再从父组件的属性绑定中取得英雄数据。
|
||
新的`HeroDetailComponent`应该从`ActivatedRoute`服务的可观察对象`params`中取得`id`参数,并通过`HeroService`服务获取具有这个指定`id`的英雄数据。
|
||
|
||
:marked
|
||
First, add the requisite imports:
|
||
|
||
首先,添加需要的导入项目:
|
||
|
||
- var _vers = _docsFor == 'dart' ? '' : '.1'
|
||
+makeExcerpt('app/hero-detail.component' + _vers + '.ts', 'added-imports', '')
|
||
|
||
- var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute'
|
||
:marked
|
||
Let's have the `!{_ActivatedRoute}` service, the `HeroService` and the `Location` service injected
|
||
into the constructor, saving their values in private fields:
|
||
|
||
然后注入`!{_ActivatedRoute}`和`HeroService`服务到构造函数中,将它们的值保存到私有变量中:
|
||
|
||
+makeExcerpt('app/hero-detail.component.ts (constructor)', 'ctor')
|
||
|
||
:marked
|
||
We tell the class that we want to implement the `OnInit` interface.
|
||
|
||
我们告诉这个类,我们要实现`OnInit`接口。
|
||
|
||
+makeExcerpt('app/hero-detail.component.ts', 'implement', '')(format=".")
|
||
|
||
block ngOnInit
|
||
:marked
|
||
Inside the `ngOnInit` lifecycle hook, we use the `params` observable to
|
||
extract the `id` parameter value from the `ActivatedRoute` service
|
||
and use the `HeroService` to fetch the hero with that `id`.
|
||
|
||
在`ngOnInit`生命周期钩子中,从`RouteParams`服务中提取`id`参数,并且使用`HeroService`来获得具有这个`id`的英雄数据。
|
||
|
||
+makeExcerpt('app/hero-detail.component.ts', 'ngOnInit')
|
||
|
||
block extract-id
|
||
:marked
|
||
Notice how we extract the `id` by calling the `forEach` method
|
||
which will deliver our !{_array} of route parameters.
|
||
|
||
注意我们提取`id`的方法:调用`forEach`方法,它会提供一个路由参数的数组。
|
||
- var _str2int = _docsFor == 'dart' ? '<code>int.parse</code> static method' : 'JavaScript (+) operator'
|
||
|
||
:marked
|
||
The hero `id` is a number. Route parameters are *always strings*.
|
||
So we convert the route parameter value to a number with the !{_str2int}.
|
||
|
||
英雄的`id`是数字,而路由参数的值*总是字符串*。
|
||
所以我们需要通过JavaScript的(+)操作符把路由参数的值转成数字。
|
||
|
||
### Add *HeroService.getHero*
|
||
|
||
### 添加*HeroService.getHero*
|
||
|
||
The problem with this bit of code is that `HeroService` doesn't have a `getHero` method!
|
||
We better fix that quickly before someone notices that we broke the app.
|
||
|
||
这段代码的问题在于`HeroService`并没有一个叫`getHero`的方法,我们最好在别人报告应用出问题之前赶快修复它。
|
||
|
||
Open `HeroService` and add a `getHero` method that filters the heroes list from `getHeroes` by `id`:
|
||
|
||
打开`HeroService`,并添加一个`getHero`方法,用来通过`id`从`getHeros`过滤英雄列表:
|
||
|
||
+makeExcerpt('app/hero.service.ts', 'getHero')
|
||
|
||
:marked
|
||
Let's return to the `HeroDetailComponent` to clean up loose ends.
|
||
|
||
回到`HeroDetailComponent`来完成收尾工作。
|
||
|
||
### Find our way back
|
||
|
||
### 回到原路
|
||
|
||
We can navigate *to* the `HeroDetailComponent` in several ways.
|
||
How do we navigate somewhere else when we're done?
|
||
|
||
我们能用多种方式导航*到*`HeroDetailComponent`。
|
||
但当我们完工时,我们该导航到那里呢?
|
||
|
||
The user could click one of the two links in the `AppComponent`. Or click the browser's back button.
|
||
We'll add a third option, a `goBack` method that navigates backward one step in the browser's history stack
|
||
using the `Location` service we injected previously.
|
||
|
||
现在用户可以点击`AppComponent`中的两个链接,或点击浏览器的“后退”按钮。
|
||
我们来添加第三个选项:一个`goBack`方法,来根据浏览器的历史堆栈,后退一步。
|
||
|
||
+makeExcerpt('app/hero-detail.component.ts', 'goBack')
|
||
|
||
- var _CanDeactivateGuard = _docsFor == 'dart' ? '<em>routerCanDeactivate</em> hook' : '<em>CanDeactivate</em> guard'
|
||
- var _CanDeactivateGuardUri = _docsFor == 'dart' ? 'angular2.router/CanDeactivate-class' : 'router/index/CanDeactivate-interface'
|
||
.l-sub-section
|
||
:marked
|
||
Going back too far could take us out of the application.
|
||
That's acceptable in a demo. We'd guard against it in a real application,
|
||
perhaps with the [!{_CanDeactivateGuard}](../api/!{_CanDeactivateGuardUri}.html).
|
||
|
||
回退太多步会跑出我们的应用。
|
||
在Demo中,这算不上问题。但在真实的应用中,我们需要对此进行防范。
|
||
也许你该用[!{_CanDeactivateGuard}](../api/!{_CanDeactivateGuardUri}.html).。
|
||
|
||
:marked
|
||
Then we wire this method with an event binding to a *Back* button that we add to the bottom of the component template.
|
||
|
||
然后,我们通过一个事件绑定把此方法绑定到模板底部的*后退(Back)*按钮上。
|
||
|
||
+makeExcerpt('app/hero-detail.component.html', 'back-button', '')
|
||
|
||
:marked
|
||
Modifying the template to add this button spurs us to take one more
|
||
incremental improvement and migrate the template to its own file,
|
||
called <span ngio-ex>hero-detail.component.html</span>:
|
||
|
||
修改模板,添加这个按钮以提醒我们还要做更多的改进,
|
||
并把模板移到独立的<span ngio-ex>hero-detail.component.html</span>文件中去。
|
||
|
||
|
||
+makeExample('app/hero-detail.component.html')
|
||
|
||
:marked
|
||
We update the component metadata with a `moduleId` and a `templateUrl` pointing to the template file that we just created.
|
||
|
||
然后更新组件的元数据,用一个`templateUrl`属性指向我们刚刚创建的模板文件。
|
||
|
||
+makeExcerpt('app/hero-detail.component.ts', 'metadata')
|
||
|
||
:marked
|
||
Here's the (nearly) finished `HeroDetailComponent`:
|
||
|
||
下面是(几乎)完成的`HeroDetailComponent`:
|
||
|
||
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'v2', 'app/hero-detail.component.ts (latest)')(format=".")
|
||
|
||
:marked
|
||
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Select a *Dashboard* Hero
|
||
|
||
## 选择一个*仪表盘*中的英雄
|
||
|
||
When a user selects a hero in the dashboard, the app should navigate to the `HeroDetailComponent` to view and edit the selected hero.
|
||
|
||
当用户从仪表盘中选择了一位英雄时,本应用要导航到`HeroDetailComponent`以查看和编辑所选的英雄。
|
||
|
||
In the dashboard template we bound each hero's click event to the `gotoDetail` method, passing along the selected `hero` entity.
|
||
|
||
在仪表盘模板中,我们把每个英雄的click事件都绑定成`gotoDetail`方法,并且传入选中的这个`hero`实体对象。
|
||
|
||
+makeExample('toh-5/ts/app/dashboard.component.html','click', 'app/dashboard.component.html (click绑定)')(format=".")
|
||
|
||
:marked
|
||
We stubbed the `gotoDetail` method when we rewrote the `DashboardComponent`.
|
||
Now we give it a real implementation.
|
||
|
||
当初我们重写`DashboardComponent`的时候,`gotoDetail`还是一个“桩方法”。
|
||
现在,我们给它一个真正的实现。
|
||
|
||
+makeExcerpt('app/dashboard.component.ts','gotoDetail')
|
||
|
||
:marked
|
||
The `gotoDetail` method navigates in two steps:
|
||
|
||
`gotoDetail`方法分两步完成导航:
|
||
|
||
1. Set a route *link parameters !{_array}*
|
||
|
||
1. 生成路由的 *链接参数数组*
|
||
|
||
1. Pass the !{_array} to the router's navigate method
|
||
|
||
1. 把这个数组传给路由器的navigate方法。
|
||
|
||
For navigation, we wrote router links <span if-docs="dart">as *link
|
||
parameters !{_array}s*</span> in the [`AppComponent`
|
||
template](#router-links). Those link<span if-docs="dart"> parameters
|
||
!{_array}</span>s had only one element, the !{_pathVsName} of the
|
||
destination route.
|
||
|
||
我们为导航在[`AppComponent`的模板](#router-links)中生成导航链接<span if-docs="dart">as *link
|
||
parameters !{_array}s*</span>。这些链接<span if-docs="dart"> parameters
|
||
!{_array}</span>只有一个元素:目标路由的路径。
|
||
|
||
This link parameters !{_array} has two elements, the ***!{_pathVsName}*** of
|
||
the destination route and a ***route parameter*** <span if-docs="dart">with
|
||
an `id` field</span> set to the value of the selected hero's `id`.
|
||
|
||
这个链接参数数组有两个元素:目标路由的***路径(path)***和一个***路由参数对象***,其中包括一个`id`字段,它的取值是所选英雄的`id`。
|
||
|
||
The two !{_array} items align with the ***!{_pathVsName}*** and ***:id***
|
||
token in the parameterized hero detail route definition we added to
|
||
`!{_appRoutingTsVsAppComp}` earlier in the chapter:
|
||
|
||
这两个数组项目与我们之前在`!{_appRoutingTsVsAppComp}`中添加的***path***和***:id***为代号被参数化的英雄详情路由的配置对象对应。
|
||
|
||
- var _file = _docsFor == 'dart' ? 'app/app.component.ts' : 'app/app.module.3.ts'
|
||
+makeExcerpt(_file + ' (hero detail)', 'hero-detail')
|
||
|
||
:marked
|
||
The `DashboardComponent` doesn't have the router yet. We obtain it in the usual way:
|
||
import the `router` reference and inject it in the constructor (along with the `HeroService`):
|
||
|
||
`DashboardComponent`还没有路由器。我们使用常规的方式为它加上路由:
|
||
`import` `router`对象的引用,并且把它注入到构造函数中(就像`HeroService`那样):
|
||
|
||
+makeExcerpt('app/dashboard.component.ts ()','import-router', '')
|
||
|
||
+makeExcerpt('app/dashboard.component.ts', 'ctor', '')
|
||
|
||
:marked
|
||
Refresh the browser and select a hero from the dashboard; the app should navigate directly to that hero’s details.
|
||
|
||
刷新浏览器,并从仪表盘中选择一位英雄,应用就会直接导航到英雄的详情。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Refactor routes to a _Routing Module_
|
||
|
||
## 重构路由为一个**路由模块**
|
||
|
||
Almost 20 lines of `AppModule` are devoted to configuring four routes.
|
||
Most application have many more routes and they [add guard services](../guide/router.html#guards)
|
||
to protect against unwanted or unauthorized navigations.
|
||
Routing considerations could quickly dominate this module and obscure its primary purpose which is to
|
||
establish key facts about the entire app for the Angular compiler.
|
||
|
||
`AppModule`中有将近20行代码是用来配置四个路由的。
|
||
绝大多数应用有更多路由,并且它们还有[守卫服务](../guide/router.html#guards)来保护不希望或未授权的导航。
|
||
路由的配置可能迅速占领这个模块,并掩盖其主要目的,即为Angular编译器设置整个应用的关键配置。
|
||
|
||
We should refactor the routing configuration into its own class.
|
||
What kind of class?
|
||
The current `RouterModule.forRoot()` produces an Angular `ModuleWithProviders` which suggests that a
|
||
class dedicated to routing should be some kind of module.
|
||
It should be a [_Routing Module_](../guide/router.html#routing-module).
|
||
|
||
我们应该重构路由配置到它自己的类。
|
||
什么样的类呢?
|
||
当前的`RouterModule.forRoot()`产生一个Angular `ModuleWithProviders`,所以这个路由类应该是一种模块类。
|
||
它应该是一个[**路由模块**](../guide/router.htm#routing-module)。
|
||
|
||
By convention the name of a _Routing Module_ contains the word "Routing" and
|
||
aligns with the name of the module that declares the components navigated to".
|
||
|
||
按约定,**路由模块**的名字应该包含“Routing”,并与导航到的组件所在的模块的名称看齐。
|
||
|
||
Create an `app-routing.module.ts` file as a sibling to `app.module.ts`. Give it the following contents extracted from the `AppModule` class:
|
||
|
||
在`app.module.ts`所在目录创建`app-routing.module.ts`文件。将下面从`AppModule`类提取出来的代码拷贝进去:
|
||
|
||
+makeExample('app/app-routing.module.ts')
|
||
:marked
|
||
Noteworthy points, typical of _Routing Modules_:
|
||
|
||
典型**路由模块**值得注意的有:
|
||
|
||
* Pull the routes into a variable. You might export it in future and it clarifies the _Routing Module_ pattern.
|
||
|
||
* 将路由抽出到一个变量中。你可能将来会导出它。而且它让**路由模块**模式更加明确。
|
||
|
||
* Add `RouterModule.forRoot(routes)` to `imports`.
|
||
|
||
* 添加`RouterModule.forRoot(routes)`到`imports`.
|
||
|
||
* Add `RouterModule` to `exports` so that the components in the companion module have access to Router declarables
|
||
such as `RouterLink` and `RouterOutlet`.
|
||
|
||
* 添加`RouterModule`到`exports`,这样关联模块的组件可以访问路由的声明,比如`RouterLink`和`RouterOutlet`。
|
||
|
||
* No `declarations`! Declarations are the responsibility of the companion module.
|
||
|
||
* 无`Declarations`!声明是关联模块的任务。
|
||
|
||
* Add module `providers` for guard services if you have them; there are none in this example.
|
||
|
||
* 如果你有守卫服务,添加模块`providers`;本例子无守卫服务。
|
||
|
||
### Update _AppModule_
|
||
|
||
### 更新_AppModule_
|
||
|
||
Now delete the routing configuration from `AppModule` and import the `AppRoutingModule`
|
||
(_both_ with an ES `import` statement _and_ by adding it to the `NgModule.imports` list).
|
||
|
||
现在,删除`AppModule`中的路由配置,并导入`AppRoutingModule`(**同时**用ES导入语句和将它添加到`NgModule.imports`数组)。
|
||
|
||
Here is the revised `AppModule`, compared to its pre-refactor state:
|
||
|
||
下面是修改后的`AppModule`,与重构前的对比:
|
||
+makeTabs(
|
||
`toh-5/ts/app/app.module.ts, toh-5/ts/app/app.module.3.ts`,
|
||
null,
|
||
`app/app.module.ts (after), app/app.module.ts (before)`)
|
||
:marked
|
||
It's simpler and focused on indentifying the key pieces of the application.
|
||
|
||
它更简单,专注于确定应用的关键部分。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Select a Hero in the *HeroesComponent*
|
||
|
||
## 在*HeroesComponent*中选择一位英雄
|
||
|
||
Earlier we added the ability to select a hero from the dashboard.
|
||
We'll do something similar in the `HeroesComponent`.
|
||
|
||
之前我们添加了从控制台选择一个英雄的功能。
|
||
我们现在要做的事和`HeroesComponent`中很像。
|
||
|
||
The `HeroesComponent` template exhibits a "master/detail" style with the list of heroes
|
||
at the top and details of the selected hero below.
|
||
|
||
那个组件的当前模板展示了一个主从风格的界面:上方是英雄列表,底下是所选英雄的详情。
|
||
|
||
+makeExample('toh-4/ts/app/app.component.ts','template', 'app/heroes.component.ts (当前的模板)')(format=".")
|
||
|
||
:marked
|
||
Our goal is to move the detail to its own view and navigate to it when the user decides to edit a selected hero.
|
||
|
||
我们要做的是将详情组建移动到它自己的视图,并在用户决定编辑一个英雄时导航到它。
|
||
|
||
Delete the `<h1>` at the top (forgot about it during the `AppComponent`-to-`HeroesComponent` conversion).
|
||
|
||
删除顶部的`<h1>`(在从`AppComponent`转到`HeroesComponent`期间可以先忘掉它)。
|
||
|
||
Delete the last line of the template with the `<my-hero-detail>` tags.
|
||
|
||
删除模板最后带有`<my-hero-detail>`标签的那一行。
|
||
|
||
We'll no longer show the full `HeroDetailComponent` here.
|
||
We're going to display the hero detail on its own page and route to it as we did in the dashboard.
|
||
|
||
这里我们不再展示完整的`HeroDetailComponent`了。
|
||
我们要在它自己的页面中显示英雄详情,并像我们在仪表盘中所做的那样路由到它。
|
||
|
||
But we'll throw in a small twist for variety.
|
||
We are keeping the "master/detail" style but shrinking the detail to a "mini", read-only version.
|
||
When the user selects a hero from the list, we *don't* go to the detail page.
|
||
We show a *mini-detail* on *this* page instead and make the user click a button to navigate to the *full detail *page.
|
||
|
||
但是,我们要做一点小小的改动。
|
||
当用户从这个列表中选择一个英雄时,我们*不会*再跳转到详情页。
|
||
而是在本页中显示一个*Mini版英雄详情*,并等用户点击一个按钮时,才导航到*完整版英雄详情*页。
|
||
|
||
### Add the *mini-detail*
|
||
|
||
### 添加*Mini版英雄详情*
|
||
|
||
Add the following HTML fragment at the bottom of the template where the `<my-hero-detail>` used to be:
|
||
|
||
在模板底部原来放`<my-hero-detail>`的地方添加下列HTML片段:
|
||
|
||
+makeExcerpt('app/heroes.component.html', 'mini-detail', '')
|
||
|
||
:marked
|
||
After clicking a hero, the user should see something like this below the hero list:
|
||
|
||
点击一个英雄,用户将会在英雄列表的下方看到这些:
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/toh/mini-hero-detail.png' alt="Mini版英雄详情" height="70")
|
||
:marked
|
||
### Format with the *uppercase* pipe
|
||
|
||
### 使用*UpperCasePipe*格式化
|
||
|
||
Notice that the hero's name is displayed in CAPITAL LETTERS. That's the effect of the `uppercase` pipe
|
||
that we slipped into the interpolation binding. Look for it right after the pipe operator ( | ).
|
||
|
||
注意,英雄的名字全被显示成大写字母。那是 `uppercase`管道的效果,借助它,我们能插手“插值表达式绑定”的过程。去管道操作符 ( | ) 后面找它。
|
||
|
||
+makeExcerpt('app/heroes.component.html', 'pipe', '')
|
||
|
||
:marked
|
||
Pipes are a good way to format strings, currency amounts, dates and other display data.
|
||
Angular ships with several pipes and we can write our own.
|
||
|
||
管道擅长做下列工作:格式化字符串、金额、日期和其它显示数据。
|
||
Angular自带了好几个管道,而且我们还可以写自己的管道。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Learn about pipes in the [Pipes](../guide/pipes.html) chapter.
|
||
|
||
要学习关于管道的更多知识,参见[管道](../guide/pipes.html)一章。
|
||
|
||
:marked
|
||
### Move content out of the component file
|
||
|
||
### 把内容移出组件文件
|
||
|
||
We are not done. We still have to update the component class to support navigation to the
|
||
`HeroDetailComponent` when the user clicks the *View Details* button.
|
||
|
||
这还没完。当用户点击*查看详情*按钮时,要让它能导航到`HeroDetailComponent`,我们仍然不得不修改组件类。
|
||
|
||
This component file is really big. Most of it is either template or CSS styles.
|
||
It's difficult to find the component logic amidst the noise of HTML and CSS.
|
||
|
||
这个组件文件太大了。它大部分都是模板或css样式。
|
||
要想在HTML和CSS的噪音中看清组件的工作逻辑太难了。
|
||
|
||
Let's migrate the template and the styles to their own files before we make any more changes:
|
||
|
||
在做更多修改之前,我们先把模板和样式移到它们自己的文件中去:
|
||
|
||
1. *Cut-and-paste* the template contents into a new <span ngio-ex>heroes.component.html</span> file.
|
||
|
||
1. 把模板内容*剪切并粘贴*到新的<span ngio-ex>heroes.component.html</span>文件。
|
||
|
||
1. *Cut-and-paste* the styles contents into a new <span ngio-ex>heroes.component.css</span> file.
|
||
|
||
1. 把样式内容*剪切并粘贴*到新的<span ngio-ex>heroes.component.css</span>文件。
|
||
|
||
1. *Set* the component metadata's `templateUrl` and `styleUrls` properties to refer to both files.
|
||
|
||
1. *设置*组件元数据的`templateUrl`和`styleUrls`属性,来分别引用这两个文件。
|
||
|
||
1. *Set* the `moduleId` property to `module.id` so that `templateUrl` and `styleUrls` are relative to the component.
|
||
|
||
1. *设置*`moduleId`属性为`module.id`,将`templateUrl`和`styleUrls`路径设置为相对组件的路径。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
The `styleUrls` property is !{_an} !{_array} of style file names (with paths).
|
||
We could list multiple style files from different locations if we needed them.
|
||
|
||
`styleUrls`属性是一个由样式文件的文件名(包括路径)组成的数组。
|
||
我们还可以列出来自多个不同位置的样式文件。
|
||
|
||
block heroes-component-cleanup
|
||
//- Only relevant for Dart.
|
||
|
||
+makeExcerpt('app/heroes.component.ts (revised metadata)', 'metadata')
|
||
|
||
:marked
|
||
Now we can see what's going on as we update the component class along the same lines as the dashboard:
|
||
|
||
现在,我们一下就明白该怎么像仪表盘中那样更新组件类了:
|
||
|
||
1. Import the `router`
|
||
|
||
1. 导入`router`
|
||
|
||
1. Inject the `router` in the constructor (along with the `HeroService`)
|
||
|
||
1. 把`router`注入到构造函数中(就像`HeroService`那样)
|
||
|
||
1. Implement the `gotoDetail` method by calling the `router.navigate` method with a two-part hero-detail link parameters !{_array}.
|
||
|
||
1. 实现`gotoDetail`方法:以`HeroDetail`和*链接参数数组*为参数调用`router.navigate`方法。
|
||
|
||
Here's the revised component class:
|
||
|
||
下面是修改过的组件类:
|
||
|
||
+makeExcerpt('app/heroes.component.ts', 'class')
|
||
|
||
:marked
|
||
Refresh the browser and start clicking.
|
||
We can navigate around the app, from the dashboard to hero details and back,
|
||
for heroes list to the mini-detail to the hero details and back to the heroes again.
|
||
We can jump back and forth between the dashboard and the heroes.
|
||
|
||
刷新浏览器,并开始点击。
|
||
我们能在应用中导航:从仪表盘到英雄详情再回来,从英雄列表到Mini版英雄详情到英雄详情,再回到英雄列表。
|
||
我们可以在仪表盘和英雄列表之间跳来跳去。
|
||
|
||
We've met all of the navigational requirements that propelled this chapter.
|
||
|
||
我们已经满足了在本章开头设定的所有导航需求。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Styling the App
|
||
|
||
## 美化本应用
|
||
|
||
The app is functional but pretty ugly.
|
||
Our creative designer team provided some CSS files to make it look better.
|
||
|
||
应用在功能上已经正常了,但还太丑。
|
||
我们富有创意的设计师团队提供了一些CSS文件,能让它变得好看一些。
|
||
|
||
### A Dashboard with Style
|
||
|
||
### 具有样式的仪表盘
|
||
|
||
The designers think we should display the dashboard heroes in a row of rectangles.
|
||
They've given us ~60 lines of CSS for this purpose including some simple media queries for responsive design.
|
||
|
||
设计师认为我们应该把仪表盘的英雄们显示在一排方块中。
|
||
它们给了我们大约60行CSS来实现它,包括一些简单的媒体查询语句来实现响应式设计。
|
||
|
||
If we paste these ~60 lines into the component `styles` metadata,
|
||
they'll completely obscure the component logic.
|
||
Let's not do that. It's easier to edit CSS in a separate `*.css` file anyway.
|
||
|
||
如果我们把这60来行CSS粘贴到组件元数据的`styles`中,它们会完全淹没组件的工作逻辑。
|
||
不能这么做。在一个独立的`*.css`文件中编辑CSS当然会更简单。
|
||
|
||
Add a <span ngio-ex>dashboard.component.css</span> file to the `!{_appDir}` folder and reference
|
||
that file in the component metadata's `styleUrls` !{_array} property like this:
|
||
|
||
把<span ngio-ex>dashboard.component.css</span>文件添加到`!{_appDir}`目录下,并在组件元数据的`styleUrls`数组属性中引用它。就像这样:
|
||
|
||
+makeExcerpt('app/dashboard.component.ts (styleUrls)', 'css')
|
||
|
||
:marked
|
||
### Stylish Hero Details
|
||
|
||
### 美化英雄详情
|
||
|
||
The designers also gave us CSS styles specifically for the `HeroDetailComponent`.
|
||
|
||
设计师还给了我们`HeroDetailComponent`特有的CSS风格。
|
||
|
||
Add a <span ngio-ex>hero-detail.component.css</span> to the `!{_appDir}`
|
||
folder and refer to that file inside
|
||
the `styleUrls` !{_array} as we did for `DashboardComponent`.
|
||
Let's also remove the `hero` property `@Input` !{_decorator}
|
||
<span if-docs="ts">and its import</span>
|
||
while we are at it.
|
||
|
||
在`!{_appDir}`目录下添加<span ngio-ex>hero-detail.component.css</span>文件,
|
||
并且在`styleUrls`数组中引用它 —— 就像当初在`DashboardComponent`中做过的那样。
|
||
同时删除`hero``@Input`装饰器属性<span if-docs="ts">和它的导入语句</span>。
|
||
|
||
Here's the content for the aforementioned component CSS files.
|
||
|
||
上述组件的CSS文件内容如下:
|
||
|
||
block css-files
|
||
+makeTabs(
|
||
`toh-5/ts/app/hero-detail.component.css,
|
||
toh-5/ts/app/dashboard.component.css`,
|
||
null,
|
||
`app/hero-detail.component.css,
|
||
app/dashboard.component.css`)
|
||
|
||
:marked
|
||
### Style the Navigation Links
|
||
|
||
### 美化导航链接
|
||
|
||
The designers gave us CSS to make the navigation links in our `AppComponent` look more like selectable buttons.
|
||
We cooperated by surrounding those links in `<nav>` tags.
|
||
|
||
设计师还给了我们一些CSS,用于让`AppComponent`中的导航链接看起来更像可被选择的按钮。
|
||
要让它们协同工作,我们得把那些链接包含在`<nav>`标签中。
|
||
|
||
Add a <span ngio-ex>app.component.css</span> file to the `!{_appDir}` folder with the following content.
|
||
|
||
在`!{_appDir}`目录下添加一个<span ngio-ex>app.component.css</span>文件,内容如下:
|
||
|
||
+makeExcerpt('app/app.component.css (navigation styles)', '')
|
||
|
||
.l-sub-section
|
||
block router-link-active
|
||
:marked
|
||
**The *routerLinkActive* directive**
|
||
|
||
***routerLinkActive*指令**
|
||
|
||
The Angular Router provides a `routerLinkActive` directive we can use to
|
||
add a class to the HTML navigation element whose route matches the active route.
|
||
All we have to do is define the style for it. Sweet!
|
||
|
||
Angular路由器提供了`routerLinkActive`指令,我们可以用它来为匹配了活动路由的HTML导航元素自动添加一个CSS类。
|
||
我们唯一要做的就是为它定义样式。真好!
|
||
|
||
+makeExcerpt('app/app.component.ts (active router links)', 'template')
|
||
|
||
:marked
|
||
Set the `AppComponent`’s `styleUrls` property to this CSS file.
|
||
|
||
设置`AppComponent`的`styleUrls`属性,指向这个CSS文件。
|
||
|
||
+makeExcerpt('app/app.component.ts','styleUrls')
|
||
|
||
:marked
|
||
### Global application styles
|
||
|
||
### 应用的全局样式
|
||
|
||
When we add styles to a component, we're keeping everything a component needs
|
||
— HTML, the CSS, the code — together in one convenient place.
|
||
It's pretty easy to package it all up and re-use the component somewhere else.
|
||
|
||
当我们把样式添加到组件中时,我们假定组件所需的一切 —— HTML、CSS、程序代码 —— 都在紧邻的地方。
|
||
这样,无论是把它们打包在一起还是在别的组件中复用它都会很容易。
|
||
|
||
We can also create styles at the *application level* outside of any component.
|
||
|
||
我们也可以在所有组件之外创建*应用级*样式。
|
||
|
||
Our designers provided some basic styles to apply to elements across the entire app.
|
||
These correspond to the full set of master styles that we
|
||
introduced earlier (see
|
||
[QuickStart, "Add some style"](../quickstart.html#!#add-some-style)).
|
||
Here is an excerpt:
|
||
|
||
我们的设计师提供了一组基础样式,这些样式应用到的元素横跨整个应用。
|
||
这些和我们前面引入的主样式全集是一样的(参见
|
||
[快速起步, "添加一些样式"](../quickstart.html#!#add-some-style))。
|
||
下面是摘录:
|
||
|
||
+makeExcerpt('styles.css (excerpt)', 'toh')
|
||
|
||
- var styles_css = 'https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/_boilerplate/styles.css'
|
||
|
||
:marked
|
||
Create the file <span ngio-ex>styles.css</span>, if it doesn't exist already.
|
||
Ensure that it contains the [master styles given here](!{styles_css}).
|
||
|
||
如果在根目录下没有一个名叫`styles.css`的文件,就添加它。
|
||
确保它包含[这里给出的主样式](!{styles_css})。
|
||
|
||
If necessary, also edit <span ngio-ex>index.html</span> to refer to this stylesheet.
|
||
|
||
如有必要,也可以编辑<span ngio-ex>index.html</span>来引用这个样式表。
|
||
|
||
+makeExcerpt('index.html (link ref)', 'css')
|
||
|
||
:marked
|
||
Look at the app now. Our dashboard, heroes, and navigation links are styling!
|
||
|
||
看看现在的应用!我们的仪表盘、英雄列表和导航链接都漂亮多了!
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/toh/dashboard-top-heroes.png' alt="查看导航栏")
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Application structure and code
|
||
|
||
## 应用结构和代码
|
||
|
||
Review the sample source code in the <live-example></live-example> for this chapter.
|
||
Verify that we have the following structure:
|
||
|
||
在<live-example>在线例子</live-example>中回顾本章的范例代码。
|
||
验证我们是否已经得到了如下结构:
|
||
|
||
block file-tree-end
|
||
.filetree
|
||
.file angular-tour-of-heroes
|
||
.children
|
||
.file app
|
||
.children
|
||
.file app.component.css
|
||
.file app.component.ts
|
||
.file app.module.ts
|
||
.file app-routing.module.ts
|
||
.file dashboard.component.css
|
||
.file dashboard.component.html
|
||
.file dashboard.component.ts
|
||
.file hero.service.ts
|
||
.file hero.ts
|
||
.file hero-detail.component.css
|
||
.file hero-detail.component.html
|
||
.file hero-detail.component.ts
|
||
.file heroes.component.css
|
||
.file heroes.component.html
|
||
.file heroes.component.ts
|
||
.file main.ts
|
||
.file mock-heroes.ts
|
||
.file node_modules ...
|
||
.file index.html
|
||
.file package.json
|
||
.file styles.css
|
||
.file systemjs.config.js
|
||
.file tsconfig.json
|
||
|
||
.l-main-section
|
||
:marked
|
||
## Recap
|
||
|
||
## 总结
|
||
|
||
### The Road Behind
|
||
|
||
### 走过的路
|
||
|
||
We travelled a great distance in this chapter
|
||
|
||
在本章中,我们往前走了很远:
|
||
|
||
- We added the Angular *Router* to navigate among different components.
|
||
|
||
- 添加了Angular*路由器*在各个不同组件之间导航。
|
||
|
||
- We learned how to create router links to represent navigation menu items.
|
||
|
||
- 学会了如何创建路由链接来表示导航栏的菜单项。
|
||
|
||
- We used router link parameters to navigate to the details of user selected hero.
|
||
|
||
- 使用路由链接参数来导航到用户所选的英雄详情。
|
||
|
||
- We shared the `HeroService` among multiple components.
|
||
|
||
- 在多个组件之间共享了`HeroService`服务。
|
||
|
||
- We moved HTML and CSS out of the component file and into their own files.
|
||
|
||
- 把HTML和CSS从组件中移出来,放到了它们自己的文件中。
|
||
|
||
- We added the `uppercase` pipe to format data.
|
||
|
||
- 添加了`uppercase`管道,来格式化数据
|
||
|
||
- We refactored routes into a `Routing Module` that we import.
|
||
|
||
- 将路由重构为路由模块,并导入它。
|
||
|
||
### The Road Ahead
|
||
|
||
### 前方的路
|
||
|
||
We have much of the foundation we need to build an application.
|
||
We're still missing a key piece: remote data access.
|
||
|
||
我们有了很多用于构建应用的基石。
|
||
但我们仍然缺少很关键的一块:远程数据存取。
|
||
|
||
In the next chapter,
|
||
we’ll replace our mock data with data retrieved from a server using http.
|
||
|
||
在下一章,我们将从硬编码模拟数据改为使用http服务从服务器获取数据。
|
||
|