block includes include ../_util-fns :marked There are new requirements for the Tour of Heroes app: 我们收到了《英雄指南》的一些新需求: * Add a *Dashboard* view. 添加一个*仪表盘*视图。 * Add the ability to navigate between the *Heroes* and *Dashboard* views. 在*英雄列表*和*仪表盘*视图之间导航。 * When users click a hero name in either view, navigate to a detail view of the selected hero. 无论在哪个视图中点击一个英雄,都会导航到该英雄的详情页。 * When users click a *deep link* in an email, open the detail view for a particular hero. 在邮件中点击一个*深链接*,会直接打开一个特定英雄的详情视图。 When you’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 To satisfy these requirements, you'll add Angular’s router to the app. 我们将把 Angular *路由器*加入应用中,以满足这些需求。 (译注:硬件领域中的路由器是用来帮你找到另一台网络设备的,而这里的路由器用于帮你找到一个组件) .l-sub-section :marked For more information about the router, read the [Routing and Navigation](../guide/router.html) page. 更多信息,见[路由和导航](../guide/router.html)。 :marked When you're done with this page, the app should look like this . 完成本章之后,应用会变成这样:。 include ../../../_includes/_see-addr-bar .l-main-section :marked ## Where you left off ## 延续上一步教程 Before continuing with the Tour of Heroes, verify that you have the following structure. 在继续《英雄指南》之前,我们先验证一下目录结构: .filetree .file angular-tour-of-heroes .children .file src .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 mock-heroes.ts .file main.ts .file index.html .file styles.css .file systemjs.config.js .file tsconfig.json .file node_modules ... .file package.json :marked ## Keep the app transpiling and running ## 让应用代码保持转译和运行 Enter the following command in the terminal window: 打开终端/控制台窗口,运行如下命令: code-example(language="sh" class="code-shell"). npm start :marked This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes. The command simultaneously launches the app in a browser and refreshes the browser when the code changes. 这个命令会在“监听”模式下运行TypeScript编译器,当代码变化时,它会自动重新编译。 同时,该命令还会在浏览器中启动该应用,并且当代码变化时刷新浏览器。 :marked You can keep building the Tour of Heroes without pausing to recompile or refresh the browser. 在后续构建《英雄指南》过程中,应用能持续运行,而不用中断服务来编译或刷新浏览器。 ## Action plan ## 行动计划 Here's the 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* The current app loads `AppComponent` and immediately displays the list of heroes. 现在的应用会加载`AppComponent`组件,并且立刻显示出英雄列表。 The 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, so you'll 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 the code out of `AppComponent`, rename it to `HeroesComponent` and create a separate `AppComponent` shell. `AppComponent`的职责已经被移交给`HeroesComponent`了。 与其把`AppComponent`中所有的东西都搬过去,不如索性把它改名为`HeroesComponent`,然后单独创建一个新的`AppComponent`壳。 Do the following: 步骤如下: * Rename the app.component.ts file to heroes.component.ts. 把app.component.ts文件改名为heroes.component.ts。 * Rename the `AppComponent` class as `HeroesComponent` (rename locally, _only_ in this file). 把`AppComponent`类改名为`HeroesComponent`(注意,*只*在这一个文件中改名)。 * Rename the selector `my-app` as `my-heroes`. 把`my-app`选择器改名为`my-heroes`。 +makeExample('toh-5/ts/src/app/heroes.component.ts', 'renaming', 'src/app/heroes.component.ts (showing renamings only)') :marked ### Create *AppComponent* ### 创建 *AppComponent* The new `AppComponent` is the application shell. It will have some navigation links at the top and a display area below. 新的`AppComponent`将成为应用的“壳”。 它将在顶部放一些导航链接,并且把我们要导航到的页面放在下面的显示区中。 Perform these steps: 执行下列步骤: * add the supporting `import` statements. 添加支持性的`import`语句。 * Create the file src/app/app.component.ts. 创建一个名叫src/app/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`. `@Component`模板中的`

`标签,它包含了对`title`属性的绑定。 * Add a `` element to the app template just below the heading so you still see the heroes. 在模板的标题下面添加``标签,以便我们仍能看到英雄列表。 * Add `HeroesComponent` to the `declarations` array of `AppModule` so Angular recognizes the `` tags. 添加`HeroesComponent`组件到根模块的`declarations`数组中,以便 Angular 能认识``标签。 * Add `HeroService` to the `providers` array of `AppModule` because you'll need it in every other view. 添加`HeroService`到`AppModule`的`providers`数组中,因为我们的每一个视图都需要它。 * Remove `HeroService` from the `HeroesComponent` `providers` array since it was promoted. 从`HerosComponent`的`providers`数组中移除`HeroService`,因为它被提到模块了。 * Add the supporting `import` statements for `AppComponent`. 为`AppComponent`添加一些`import`语句。 The first draft looks like this: 我们的第一个草稿版就像这样: +makeTabs( `toh-5/ts/src/app/app.component.1.ts, toh-5/ts/src/app/app.module.1.ts`, ',', `src/app/app.component.ts (v1), src/app/app.module.ts (v1)`) :marked .callout.is-critical header Remove HeroService from the HeroesComponent providers header 从HeroesComponentproviders中移除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`*提升*到根`NgModule`中。 我们不希望在应用的两个不同层次上存在它的***两个副本***。 :marked The app still runs and displays heroes. 应用仍然在运行,并显示着英雄列表。 :marked ## Add routing ## 添加路由 Instead of displaying automatically, heroes should display after users click a button. In other words, users should be able to navigate to the list of heroes. 我们希望在用户点击按钮之后才显示英雄列表,而不是自动显示。 换句话说,我们希望用户能“导航”到英雄列表。 Use the Angular router to enable navigation. 我们要使用 Angular *路由器*进行导航。 :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`). You'll configure the routes first. Angular 路由器是一个可选的外部 Angular NgModule,名叫`RouterModule`。 路由器包含了多种服务(`RouterModule`)、多种指令(`RouterOutlet、RouterLink、RouterLinkActive`)、 和一套配置(`Routes`)。我们将先配置路由。 :marked ### *<base href>* Open `index.html` and ensure there is a `` element (or a script that dynamically sets this element) at the top of the `` section. 打开`index.html`,确保它的``区顶部有一个``元素(或动态生成该元素的脚本)。 +makeExample('toh-5/ts/src/index.html', 'base-href', 'src/index.html (base-href)') .callout.is-important header base href is essential header 基地址(base href)是必须的 :marked For more information, see the [Set the base href](../guide/router.html#!#base-href) section of the [Routing and Navigation](../guide/router.html) page. 要了解更多信息,请参见[路由与导航](../guide/router.html)章的[设置基地址(base href)](../guide/router.html#!#base-href)部分。 a#configure-routes :marked ### Configure routes ### 配置路由 Create a configuration file for the app 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 粘贴到浏览器地址栏时,应该显示哪个视图。 Define the first route as a route to the heroes component. 我们的第一个路由是指向英雄列表组件的。 +makeExample('toh-5/ts/src/app/app.module.2.ts', 'heroes', 'src/app/app.module.ts (heroes route)') :marked The `Routes` are an array of *route definitions*. 这个`Routes`是一个*路由定义*的数组。 此时,我们只有一个路由定义,但别急,后面还会添加更多。 This route definition has the following parts: *路由定义*包括以下部分: * *Path*: The router matches this route's path to the URL in the browser address bar (`heroes`). **Path**: 路由器会用它来匹配浏览器地址栏中的地址,如`heroes`。 * *Component*: The component that the router should create when navigating to this route (`HeroesComponent`). *Component*: 导航到此路由时,路由器需要创建的组件(`HeroesComponent`)。 .l-sub-section :marked Read more about defining routes with `Routes` in the [Routing & Navigation](../guide/router.html) page. 关于`Routes`定义的更多信息,见[路由与导航](../guide/router.html)一章。 :marked ### Make the router available ### 让路由器可用 Import the `RouterModule` and add it to the `AppModule` imports array. 导入`RouterModule`并添加到`AppModule`的`imports`数组中。 +makeExample('toh-5/ts/src/app/app.module.2.ts', '', 'src/app/app.module.ts (app routing)') .l-sub-section :marked The `forRoot()` method is called because a configured router is provided at the app's root. The `forRoot()` method supplies the Router service providers and directives needed for routing, and performs the initial navigation based on the current browser URL. 这里使用了`forRoot()`方法,因为我们是在应用*根部*提供配置好的路由器。 `forRoot()`方法提供了路由需要的路由服务提供商和指令,并基于当前浏览器 URL 初始化导航。 :marked ### Router outlet ### 路由出口(Outlet) If you paste the path, `/heroes`, into the browser address bar at the end of the URL, the router should match it to the `heroes` route and display the `HeroesComponent`. However, you have to tell the router where to display the component. To do this, you can add a `` element at the end of the template. `RouterOutlet` is one of the directives provided by the `RouterModule`. The router displays each component immediately below the `` as users navigate through the app. 如果我们把路径`/heroes`粘贴到浏览器的地址栏,路由器会匹配到`'Heroes'`路由,并显示`HeroesComponent`组件。 我们必须***告诉路由器它位置***,所以我们把``标签添加到模板的底部。 `RouterOutlet`是由`RouterModule`提供的指令之一。 当我们在应用中导航时,路由器就把激活的组件显示在``里面。 ### Router links ### 路由器链接 Users shouldn't have to paste a route URL into the address bar. Instead, add an anchor tag to the template that, when clicked, triggers navigation to the `HeroesComponent`. 我们当然不会真让用户往地址栏中粘贴路由的 URL, 而应该在模板中的什么地方添加一个锚标签。点击时,就会导航到`HeroesComponent`组件。 The revised template looks like this: 修改过的模板是这样的: +makeExample('toh-5/ts/src/app/app.component.1.ts', 'template-v2', 'src/app/app.component.ts (template-v2)') :marked Note the `routerLink` binding in the anchor tag. The `RouterLink` directive (another of the `RouterModule` directives) is bound to a string that tells the router where to navigate when the user clicks the link. 注意,锚标签中的`[routerLink]`绑定。 我们把`RouterLink`指令(`ROUTER_DIRECTIVES`中的另一个指令)绑定到一个字符串。 它将告诉路由器,当用户点击这个链接时,应该导航到哪里。 Since the link is not dynamic, a routing instructionis defined with a one-time binding to the route path. Looking back at the route configuration, you can confirm that `'/heroes'` is the path of the route to the `HeroesComponent`. 由于这个链接不是动态的,我们只要用**一次性绑定**的方式绑定到路由的**路径 (path) **就行了。 回来看路由配置表,我们清楚的看到,这个路径 —— `'/heroes'`就是指向`HeroesComponent`的那个路由的路径。 .l-sub-section :marked Read more about dynamic router links and the link parameters array in the [Appendix: Link Parameters Array](../guide/router.html#link-parameters-array) section of the [Routing & Navigation](../guide/router.html#) page. 关于动态路由器链接和*链接参数数组更多信息,见[路由与导航](../guide/router.html#)中的[附录:链接参数数组](../guide/router.html#link-parameters-array)部分。 :marked Refresh the browser. The browser displays the app title and heroes link, but not the heroes list. 刷新浏览器。我们只看到了应用标题和英雄链接。英雄列表到哪里去了? .l-sub-section :marked The browser's address bar shows `/`. The route path to `HeroesComponent` is `/heroes`, not `/`. Soon you'll add a route that matches the path `/`. 浏览器的地址栏显示的是`/`。而到`HeroesComponent`的路由中的路径是`/heroes`,不是`/`。 我们没有任何路由能匹配当前的路径`/`,所以,自然没啥可显示的。 接下来,我们就修复这个问题。 :marked Click the *Heroes* navigation link. The address bar updates to `/heroes` and the list of heroes displays. 我们点击“Heroes(英雄列表)”导航链接,浏览器地址栏更新为`/heroes`,并且看到了英雄列表。我们终于导航过去了! `AppComponent` now looks like this: 现在,`AppComponent`是这样的: +makeExample('toh-5/ts/src/app/app.component.1.ts', 'v2', 'src/app/app.component.ts (v2)') :marked The *AppComponent* is now attached to a router and displays routed views. For this reason, and to distinguish it from other kinds of components, this component type is called a *router component*. *AppComponent*现在加上了路由器,并能显示路由到的视图了。 因此,为了把它从其它种类的组件中区分出来,我们称这类组件为*路由器组件*。 :marked ## Add a dashboard ## 添加一个*仪表盘* Routing only makes sense when multiple views exist. To add another view, create a placeholder `DashboardComponent`, which users can navigate to and from. 当我们有多个视图的时候,路由才有意义。所以我们需要另一个视图。先创建一个`DashboardComponent`的占位符,让用户可以导航到它或从它导航出来。 +makeExample('toh-5/ts/src/app/dashboard.component.1.ts', '', 'src/app/dashboard.component.ts (v1)') :marked You'll make this component more useful later. 我们先不实现它,稍后,我们再回来,给这个组件一些实际用途。 ### Configure the dashboard route ### 配置仪表盘路由 To teach `app.module.ts` to navigate to the dashboard, import the dashboard component and add the following route definition to the `Routes` array of definitions. 要让`app.module.ts`能导航到仪表盘,就要先导入仪表盘组件,然后把下列路由定义添加到`Routes`数组中。 +makeExample('toh-5/ts/src/app/app.module.3.ts', 'dashboard', 'src/app/app.module.ts (Dashboard route)') :marked Also import and add `DashboardComponent` to the `AppModule`'s `declarations`. 然后还得把`DashboardComponent`添加到`AppModule`的`declarations`数组中。 +makeExample('toh-5/ts/src/app/app.module.ts', 'dashboard','src/app/app.module.ts(dashboard)') :marked ### Add a redirect route ### 添加重定向路由 Currently, the browser launches with `/` in the address bar. When the app starts, it should show the dashboard and display a `/dashboard` URL in the browser address bar. 浏览器启动时地址栏中的地址是`/`。 当应用启动时,它应该显示仪表盘,并且在浏览器的地址栏显示URL:`/dashboard` :marked To make this happen, use a redirect route. Add the following to the array of route definitions: 我们可以使用重定向路由来实现它。把下面的内容添加到路由定义数组中: +makeExample('toh-5/ts/src/app/app.module.3.ts','redirect', 'src/app/app.module.ts (redirect)') .l-sub-section :marked Read more about *redirects* in the [Redirecting routes](../guide/router.html#!#redirect) section of the [Routing & Navigation](../guide/router.html#) page. 关于*重定向*的更多信息,见[路由](../guide/router.html#redirect)。 :marked ### Add navigation to the template ### 添加导航到模版中 Add a dashboard navigation link to the template, just above the *Heroes* link. 在模板上添加一个到仪表盘的导航链接,就放在*Heroes(英雄列表)*链接的上方。 +makeExample('toh-5/ts/src/app/app.component.1.ts', 'template-v3', 'src/app/app.component.ts (template-v3)') .l-sub-section :marked The `