- var _example = 'toh-5';
block includes
include ../_util-fns
- var _appRoutingTsVsAppComp = 'app.routing.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 *Component 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 for this part.
运行这部分的在线例子。
+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 typings ...
.file index.html
.file package.json
.file styles.css
.file systemjs.config.js
.file tsconfig.json
.file typings.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:
改名的步骤如下:
* app.component.ts file to heroes.component.ts
* 把app.component.ts文件改名为heroes.component.ts
* `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 app/app.component.ts.
* 创建一个名叫app.component.ts的新文件。
* Define an exported `AppComponent` class.
* 定义一个导出的 ``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 `
` element, which contains a binding to `title`
* 在模板中添加一个`
`标签,包裹着到`title`属性的绑定。
* Add a `` element to the app template just below the heading so we still see the heroes.
*
在模板中添加``标签,以便我们仍能看到英雄列表。
* Add `HeroesComponent` to the `!{_declsVsDirectives}` !{_array} of `!{_AppModuleVsAppComp}` so Angular recognizes the `` tags.
* 添加`HeroesComponent`组件到根模块的`declarations`数组中,以便Angular能认识``标签。
* 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 HeroService from the HeroesComponent providers
header 从HeroesComponent的providers中移除HeroService
: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`*提升*到`AppComponent`中。
我们可不希望在应用的两个不同层次上存在它的***两个副本***。
: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 *Component 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 `` at the top of the `` section.
打开`index.html`并且在``区的顶部添加``语句。
+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 file 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.routing.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
We'll export a `routing` constant initialized using the `RouterModule.forRoot` method applied to our !{_array} of routes.
This method returns a **configured router module** that we'll add to our root NgModule, `AppModule`.
使用`RouterModule.forRoot`方法,导出包含了路由数组的`routing`常量。它返回一个**配置好的路由模块**,它将被加入到根NgModule - `AppModule`中。
+makeExcerpt('app/app.routing.1.ts (excerpt)', 'routing-export')
.l-sub-section
:marked
We call 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.
调用`forRoot`方法是因为要在应用程序根部提供配置好的路由。
`forRoot`方法为我们提供了路由需要的服务提供商和指令。
:marked
### Make the router available.
### 让路由器生效
We've setup initial routes in the `app.routing.ts` file. Now we'll add it to our root NgModule.
我们已经在`app.routing.ts`文件中设置好了初始路由,接着把它加到根模块(NgModule)中。
Import the `routing` constant from `app.routing.ts` and add it the `imports` !{_array} of `AppModule`.
我们要从`app.routing.ts`中导入`routing`常量,并把它加入根模块的`imports`数组中。
+makeExcerpt('app/app.module.ts', 'routing')
- 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 `` element to the bottom of the template.
`RouterOutlet` is one of the directives provided by the `!{_RouterModuleVsRouterDirectives}`.
The router displays each component immediately below the `` as we navigate through the application.
我们必须***告诉它位置***,所以我们把``标签添加到模板的底部。
`RouterOutlet`是`!{_RouterModuleVsRouterDirectives}`提供的指令之一。
当我们在应用中导航时,路由器就把激活的组件显示在``里面。
### 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.routing.ts'
+makeExcerpt(_file + ' (Dashboard route)', 'dashboard')
+ifDocsFor('ts|js')
:marked
Also import and add `DashboardComponent` to our root NgModule'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.routing.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 nestled the two links within `