天天看点

Angular2官网项目 (4)--路由

行动计划

把<code>AppComponent</code>变成应用程序的“壳”,它只'处理导航'

把现在由<code>AppComponent</code>关注的英雄们移到一个独立的<code>GeneralComponent</code>中

添加路由

创建一个新的<code>DashboardComponent</code>组件

把仪表盘加入导航结构中

路由是导航的另一个名字。路由器就是从一个视图导航到另一个视图的机制。

拆分Appcomponent.

        现在的应用会加载<code>AppComponent</code>组件,并且立刻显示出英雄列表。

        我们修改后的应用将提供一个壳,它会选择仪表盘和英雄列表视图之一,然后默认显示它。

<code>  AppComponent</code>组件应该只处理导航。 我们来把英雄列表的显示职责,从<code>AppComponent</code>移到<code>GeneralComponent</code>组件中。

GeneralComponent组件

<code>AppComponent</code>的职责已经被移交给<code>GeneralComponent</code>了。 与其把<code>AppComponent</code>中所有的东西都搬过去,不如索性把它改名为<code>GeneralComponent</code>,然后单独创建一个新的<code>AppComponent</code>壳。

        步骤:

                1.把app.component.ts 和app.component.html 和app.component.scss移入到General文件夹下。

                2.app改为generals

                3.类名AppComponent改为GeneralsComponent

                4.选择器名称app-root改为my-general

创建AppComponent

新的<code>AppComponent</code>将成为应用的“壳”。 它将在顶部放一些导航链接,并且把我们要导航到的页面放在下面的显示区中。

                     在./app下创建app.component.ts

添加支持性的<code>import</code>语句。

定义一个导出的 <code>AppComponent</code>类。

在类的上方添加<code>@Component</code>元数据装饰器,装饰器带有app-root选择器。

将下面的项目从<code>HeroesComponent</code>移到<code>AppComponent</code>:

<code>title</code>类属性

<code>@Component</code>模板中的<code>&lt;h1&gt;</code>标签,它包含了对<code>title</code>属性的绑定。

在模板的标题下面添加<code>&lt;my-heroes&gt;</code>标签,以便我们仍能看到英雄列表。

添加<code><code>GeneralComponent</code></code>组件到根模块的<code>declarations</code>数组中,以便 Angular 能认识<code>&lt;my-generals&gt;</code>标签。

添加<code>GeneralService</code>到<code>AppModule</code>的<code>providers</code>数组中,因为我们的每一个视图都需要它。

从<code><code><code>GeneralComponent</code></code></code>的<code>providers</code>数组中移除<code>GeneralService</code>,因为它被提到模块了。

为<code>AppComponent</code>添加一些<code>import</code>语句。

./app/app.component.ts

import { Component } from '@angular/core';

@Component({

  selector: 'app-root',

  template: `

    &lt;my-generals&gt;&lt;/my-generals&gt;

  `

})

export class AppComponent {

}

./app/app.module.ts

import { NgModule } from '@angular/core';

import { BrowserModule } from '@angular/platform-browser';

import { FormsModule }   from '@angular/forms';

import {GeneralService} from'./general.service';

//注册指令---&gt;用法类似于组件

import {HighlightDirective} from '../directive/drag.directive';

//测试管道所用的组件

import {HeroBirthdayComponent} from "../Pipes/hero-birthday1.component";

import {ExponentialStrengthPipe} from "../Pipes/exponential-strength.pipe"

import {GeneralsComponent} from './general/generals.component';

import { AppComponent } from './app.component';

import {GeneralDetailComponent} from './general/general-detail.component';

@NgModule({

  //列出程序中的组件

  declarations: [

    AppComponent,

    GeneralDetailComponent,

      GeneralsComponent,

    // HighlightDirective,

    // HeroBirthdayComponent,

    // ExponentialStrengthPipe

  ],

  //导入模块

  imports: [

  /*BrowserModule,每个运行在浏览器中的应用都必须导入它。

       BrowserModule注册了一些关键的应用“服务”提供商。 它还包括了一些通用的指令,

       例如NgIf和NgFor,所以这些指令在该模块的任何组件模板中都是可用的。 

   */

    BrowserModule,

  //双向数据绑定依赖的模块

    FormsModule

  providers: [GeneralService],

  /*@NgModule.bootstrap属性把这个AppComponent标记为引导 (bootstrap) 组件。 

    当 Angular 引导应用时,它会在 DOM 中渲染AppComponent,并把结果放进index.html的&lt;app-root&gt;元素标记内部。  

  */

  bootstrap: [AppComponent]

export class AppModule { }

./app/general/generals.component.ts

import { Component,OnInit} from '@angular/core';

import {General}  from "../../bean/General";

import {GeneralService} from'../general.service';

  selector: 'my-generals',

  templateUrl: './generals.component.html',

  styleUrls: ['./generals.component.scss']

export class GeneralsComponent implements OnInit {

  title = 'MY General';

  // generals:General[]=Generals;

   color="pink";

   constructor(private generalService:GeneralService ){

   }

 selectGeneral:General;

  generals:General[];

  getGenerals():void{

    this.generalService.getGenerals()

    .then(generals=&gt;this.generals=generals);

  }

  ngOnInit(){

      this.getGenerals();

  oSelect(item:General):void{

   this.selectGeneral=item;

我们希望在用户点击按钮之后才显示英雄列表,而不是自动显示。 换句话说,我们希望用户能“导航”到英雄列表。

我们要使用 Angular 路由器进行导航。

    打开src/index.html

            确保它的<code>&lt;head&gt;</code>区顶部有一个<code>&lt;base href="..."&gt;</code>元素(或动态生成该元素的脚本)

            a.导入路由所需要的模块

                    import { RouterModule }   from '@angular/router';

            b.在imports下写入       

     RouterModule.forRoot([

            {

              path: 'generals',

              component: GeneralsComponent

            }

          ])

路由定义包括以下部分:

Path: 路由器会用它来匹配浏览器地址栏中的地址,如<code>generals</code>。

Component: 导航到此路由时,路由器需要创建的组件(<code>GeneralsComponent</code>)。

这里使用了<code>forRoot()</code>方法,因为我们是在应用根部提供配置好的路由器。 <code>forRoot()</code>方法提供了路由需要的“”路由服务提供商和指令”,并基于当前浏览器 URL 初始化导航。

我们当然不会真让用户往地址栏中粘贴路由的 URL, 而应该在模板中的什么地方添加一个锚标签。点击时,就会导航到<code>GeneralsComponent</code>组件。

  &lt;a routerLink="/generals"&gt;Generals&lt;/a&gt;

由于这个链接不是动态的,我们只要用一次性绑定的方式绑定到路由的路径 (path) 就行了。 回来看路由配置表,我们清楚的看到,这个路径 —— <code>'/heroes'</code>就是指向<code>HeroesComponent</code>的那个路由的路径。

现在./app/app.component.ts

   &lt;a routerLink="/generals"&gt;Generals&lt;/a&gt;

   &lt;router-outlet&gt;&lt;/router-outlet&gt;

AppComponent现在加上了路由器,并能显示路由到的视图了。 因此,为了把它从其它种类的组件中区分出来,我们称这类组件为路由器组件。

当我们有多个视图的时候,路由才有意义。所以我们需要另一个视图。先创建一个<code>DashboardComponent</code>的占位符,让用户可以导航到它或从它导航出来。

    在./src/app下创建dashboard文件夹

            在这下面创建dashboard.component.ts

  selector: 'my-dashboard',

  template: '&lt;h3&gt;My Dashboard&lt;/h3&gt;'

export class DashboardComponent { }

我们先不实现他

 {

          path: 'dashboard',

          component: DashboardComponent

        },

然后还得把<code>DashboardComponent</code>添加到<code>AppModule</code>的<code>declarations</code>数组中。

  DashboardComponent

<code></code>

  {

          path: '',

          redirectTo: '/dashboard',

          pathMatch: 'full'

在模板上添加一个到仪表盘的导航链接,就放在Generals(英雄列表)链接的上方。

    &lt;a routerLink="/dashboard"&gt;Dashboard&lt;/a&gt;

刷新浏览器。应用显示出了仪表盘,并可以在仪表盘和英雄列表之间导航了。

       让仪表盘变得有趣。

把元数据中的<code>template</code>属性替换为<code>templateUrl</code>属性,它将指向一个新的模板文件。

templateUrl: './dashboard.component.html'

增加styleUrls:['./dashboard.component.scss']

dashboard.component.htm  

&lt;h3&gt;Top Generals&lt;/h3&gt;

&lt;div&gt;

  &lt;div *ngFor="let general of generals" class="col-1-4"&gt;

    &lt;div&gt;

      &lt;h4&gt;`general`.`name`&lt;/h4&gt;

    &lt;/div&gt;

  &lt;/div&gt;

&lt;/div&gt;

我们再次使用<code>*ngFor</code>来在英雄列表上迭代,并显示它们的名字。 还添加了一个额外的<code>&lt;div&gt;</code>元素,来帮助稍后的美化工作。

dashboard.component.ts

  templateUrl: './dashboard.component.html',

  styleUrls:['./dashboard.component.scss']

export class DashboardComponent implements OnInit { 

generals:General[];

constructor(private generalService:GeneralService){}

ngOnInit(){

this.generalService.getGenerals().then(generals=&gt;this.generals=generals.slice(1,5));

我们在之前的<code>GeneralsComponent</code>中也看到过类似的逻辑:

创建一个<code>generals</code>数组属性。

在构造函数中注入<code>GeneralService</code>,并且把它保存在一个私有的<code>generalService</code>字段中。

在 Angular 的<code>ngOnInit</code>生命周期钩子里面调用服务来获得英雄数据。

在仪表盘中我们用<code>Array.slice</code>方法提取了四个英雄(第2、3、4、5个)。

刷新浏览器,在这个新的仪表盘中就看到了四个英雄。

虽然我们在<code>HeroesComponent</code>组件的底部显示了所选英雄的详情, 但用户还没法导航到<code>GeneralDetailComponent</code>组件。我们可以用下列方式导航到<code>GeneralDetailComponent</code>:

从Dashboard(仪表盘)导航到一个选定的英雄。

从Generals(英雄列表)导航到一个选定的英雄。

把一个指向该英雄的“深链接” URL 粘贴到浏览器的地址栏。

我们将在<code>app.module.ts</code>中添加一个到<code>GeneralDetailComponent</code>的路由,也就是配置其它路由的地方。

这个新路由的不寻常之处在于,我们必须告诉<code>GeneralDetailComponent</code>该显示哪个英雄。 之前,我们不需要告诉<code>GeneralsComponent</code>组件和<code>DashboardComponent</code>组件任何东西

我们可以把英雄的<code>id</code>添加到 URL 中。当导航到一个<code>id</code>为 11 的英雄时,我们期望的 URL 是这样的:

    /detail/11

URL中的<code>/detail/</code>部分是固定不变的,但结尾的数字<code>id</code>部分会随着英雄的不同而变化。 我们要把路由中可变的那部分表示成一个参数 (parameter) 或令牌 (token) ,代表英雄的<code>id</code>。

          path: 'detail/:id',

          component: GeneralDetailComponent

路径中的冒号 (:) 表示<code>:id</code>是一个占位符,当导航到这个<code>GeneralDetailComponent</code>组件时,它将被填入一个特定英雄的<code>id</code>。

模板不用修改,我们会用原来的方式显示英雄。导致这次大修的原因是如何获得这个英雄的数据。

先添加下列导入语句:

import { Component, Input, OnInit } from '@angular/core';

import { ActivatedRoute, ParamMap } from '@angular/router';

import { Location }                 from '@angular/common';

import 'rxjs/add/operator/switchMap';

  constructor( private generalService: GeneralService,

  private route: ActivatedRoute,

  private location: Location){}

 ngOnInit(){

    this.route.paramMap

    .switchMap((params: ParamMap) =&gt; this.generalService.getGeneral(+params.get('id')))

    .subscribe(general =&gt; this.general = general);

在前面的代码片段中GeneralService没有<code>getGeneral()</code>方法。要解决这个问题,请打开<code>GeneralService</code>并添加一个<code><code>getGenera</code>()</code>方法,它会根据<code>id</code>从<code><code>getGenerals</code>()</code>中过滤英雄列表。

etGeneral(id: number): Promise&lt;General&gt; {

   return this.getGenerals()

             .then(generals =&gt; generals.find(general =&gt; general.id === id));

 goBack(): void {

   this.location.back();

然后,我们通过一个事件绑定把此方法绑定到模板底部的 Back(后退)按钮上。

&lt;button (click)="goBack()"&gt;Back&lt;/button&gt;

当用户从仪表盘中选择了一位英雄时,本应用要导航到<code>GeneralDetailComponent</code>以查看和编辑所选的英雄。

虽然仪表盘英雄被显示为像按钮一样的方块,但是它们的行为应该像锚标签一样。 当鼠标移动到一个英雄方块上时,目标 URL 应该显示在浏览器的状态条上,用户应该能拷贝链接或者在新的浏览器标签页中打开英雄详情视图。

要达到这个效果,再次打开<code>dashboard.component.html</code>,将用来迭代的<code>&lt;div *ngFor...&gt;</code>替换为<code>&lt;a&gt;</code>,就像这样:

刷新浏览器,并从仪表盘中选择一位英雄,应用就会直接导航到英雄的详情。

重构路由为一个模块

<code>AppModule</code>中有将近 20 行代码是用来配置四个路由的。 绝大多数应用有更多路由,并且它们还有守卫服务来保护不希望或未授权的导航。  路由的配置可能迅速占领这个模块,并掩盖其主要目的,即为 Angular 编译器设置整个应用的关键配置。

按约定,路由模块的名字应该包含 “Routing”,并与导航到的组件所在的模块的名称看齐。

在<code>app.module.ts</code>所在目录创建<code>app-routing.module.ts</code>文件。将下面从<code>AppModule</code>类提取出来的代码拷贝进去:

import { NgModule }             from '@angular/core';

import { RouterModule, Routes } from '@angular/router';

import {DashboardComponent} from './dashboard/dashboard.component';

const routes: Routes = [

  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },

  { path: 'dashboard',  component: DashboardComponent },

  { path: 'detail/:id', component: GeneralDetailComponent },

  { path: 'generals',     component: GeneralsComponent }

];

  imports: [ RouterModule.forRoot(routes) ],

  exports: [ RouterModule ]

export class AppRoutingModule {}

将路由抽出到一个变量中。如果你将来要导出这个模块,这种 "路由模块" 的模式也会更加明确。

添加<code>RouterModule.forRoot(routes)</code>到<code>imports</code>。

无<code>declarations</code>!声明是关联模块的任务。

如果有守卫服务,把它们添加到本模块的<code>providers</code>中(本例子中没有守卫服务)。

删除<code>AppModule</code>中的路由配置,并导入<code>AppRoutingModule</code> (使用 ES <code>import</code>语句导入,并将它添加到<code>NgModule.imports</code>列表)。

//路由所需要的核心模块

import { RouterModule }   from '@angular/router';

//路由模块

import {AppRoutingModule} from './app-routing.module'

      DashboardComponent

    FormsModule,

   //路由模块

    AppRoutingModule

  在&lt;div class="controller"&gt;  添加如下代码 

    &lt;div class="row"&gt;

     &lt;div *ngIf="selectGeneral"&gt;

       &lt;h2&gt;

         {{selectGeneral.name | uppercase}} is my hero

       &lt;/h2&gt;

       &lt;button (click)="gotoDetail()"&gt;View Details&lt;/button&gt;

     &lt;/div&gt;

     &lt;/div&gt;

点击按钮时,GeneralsComponent导航到<code>GeneralDetailComponent</code>。 该按钮的点击事件绑定到了<code>gotoDetail()</code>方法,它使用命令式的导航,告诉路由器去哪儿。

该方法需要对组件类做一些修改:

实现<code>gotoDetail()</code>,调用路由器的<code>navigate()</code>方法

具体代码

import { Router } from '@angular/router';

   constructor(private generalService:GeneralService,private router: Router){

  gotoDetail(): void {

    this.router.navigate(['/detail', this.selectGeneral.id]);

刷新浏览器,并开始点击。 我们能在应用中导航:从仪表盘到英雄详情再回来,从英雄列表到 mini 版英雄详情到英雄详情,再回到英雄列表。 我们可以在仪表盘和英雄列表之间跳来跳去。

本文转自 沉迷学习中 51CTO博客,原文链接:http://blog.51cto.com/12907581/1967223,如需转载请自行联系原作者