网站首页 » 前端开发 » Angular 2 » Angular 4.x 路由示例(Routing)
上一篇:
下一篇:

Angular 4.x 路由示例(Routing)

现在应用有了新的需求:

1、添加一个 Dashboard 页面

2、添加导航功能

3、用户不管在任意一个页面中点击都能进入详情

4、当用户点击一个深度链接可以打开指定英雄详情

当你完成了以上这些,用户就可以像下面这样进行导航(页面跳转):

Angular 2 路由示例(Routing)

为了能达到上面的这些需求你得给应用添加一个路由。

准备

请确保你的项目结构目录如下:

Angular 2 路由

启动服务

ng serve --open

上面的命令行就是启动服务并运行 应用。这个命令行有几个作用:1.启动应用服务;2.监听文件变化。3.重建APP。

实现计划

计划如下:

1、把 AppComponent 改成一个只用来处理导航的应用壳

2、把 AppComponent 中的 Heroes 内容放到一个新文件 HeroesComponent  中

3、添加路由

4、新建一个  DashboardComponent

5、把 Dashboard 添加到导航结构中

分离 AppComponent

当应用加载  AppComponent 就马上显示 heroes 列表

改进后的应用应该可以选择视图(Dashboard 或者 Heroes),并且默认打开其中一个

AppComponent 应用只处理导航,所以你现在需要把显示heroes 的内容单独出来放到一个文件里。

HeroesComponent

AppComponent 已经用于Heroes 了,所以你就没必需移动代码了,直接把 AppComponent 改名为 HeroesComponent ,然后再新建一个 AppComponent 壳。

做以下三件事:

把 heroes.component.ts 文件重命名为 app.component.ts

把 AppComponent 改名为 HeroesComponent

把 my-app 改名为 my-heroes

src/app/heroes.component.ts (showing renamings only)
@Component({
  selector: 'my-heroes',
})
export class HeroesComponent implements OnInit {
}

创建 AppComponent

新建的 AppComponent 是一个全新的应用壳,它里面放一些导航链接。

执行下面的步骤:

创建 src/app/app.component.ts 文件

暴露 AppComponent  类

添加一个 selector 为 my-app 的 @Component 装饰器

把下面内容(title 属性.、装饰器的模板)从 HeroesComponent 中搬到 AppComponent 中

添加 <my-heroes> 元素

添加 HeroesComponent  到 AppModule 的 declarations 数组中,以便于让 Angular 能够识别 <my-heroes> 标签

添加 HeroService  到 AppModule 的 providers 数组中,因为你在很多地方会用到它

从 HeroesComponent 里把 providers 数组中的 HeroService  删掉

添加 AppComponent  声明

完成后代码就像下面这样

src/app/app.component.ts
import { Component } from '@angular/core';
 
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <my-heroes></my-heroes>
  `
})
export class AppComponent {
  title = 'Tour of Heroes';
}
src/app/app.module.ts
import { NgModule }       from '@angular/core';
import { BrowserModule }  from '@angular/platform-browser';
import { FormsModule }    from '@angular/forms';
 
import { AppComponent }        from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent }     from './heroes.component';
import { HeroService }         from './hero.service';
 
@NgModule({
  imports: [
    BrowserModule,
    FormsModule
  ],
  declarations: [
    AppComponent,
    HeroDetailComponent,
    HeroesComponent
  ],
  providers: [
    HeroService
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {
}

应用又起死回生了有没有。

添加路由

现在实现的需求是不让 heroes 自动显示,而是当用户点击按钮时才显示它。换句话说,用户能够通过导航进入 heroes 的列表页。

我们用 Angular 的路由实现导航功能。

Angular 默认是没有改入路由功能的,它是一个可选的 Angular NgModule 叫做 RouterModule。

router 由多重服务(RouterModule),多重指令(RouterOutlet,RouterLink,RouterLinkActive),以及一个配置(Routes)组成。

添加< base href >

打开 index.html 确保页面的<head>的最上方有 <base href=”…”> 元素(或者有动态添加此元素)。

src/index.html (base-href)
<head>
  <base href="/">

配置路由

创建一个应用路由文件。

路由规则会告诉路由当用户点击或者直接在浏览器地址栏中输入链接后,将要显示哪一个页面。

定义一个路由指向 heroes 组件

src/app/app.module.ts (heroes route)
import { RouterModule }   from '@angular/router';

RouterModule.forRoot([
  {
    path: 'heroes',
    component: HeroesComponent
  }
])

Routers 参数是一个由路由定义组成的一个数组。

路由定义分如下两部分:

Path:这个是用来匹配浏览器地址中的URL。

Component:路由对应的组件。

让路由可用

引入 RouterModule,即把 RouterModule 添加到 AppModule 的imports 数组中。

src/app/app.module.ts (app routing)
import { NgModule }       from '@angular/core';
import { BrowserModule }  from '@angular/platform-browser';
import { FormsModule }    from '@angular/forms';
import { RouterModule }   from '@angular/router';
 
import { AppComponent }        from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent }     from './heroes.component';
import { HeroService }         from './hero.service';
 
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    RouterModule.forRoot([
      {
        path: 'heroes',
        component: HeroesComponent
      }
    ])
  ],
  declarations: [
    AppComponent,
    HeroDetailComponent,
    HeroesComponent
  ],
  providers: [
    HeroService
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {
}

Router outlet

如果你在浏览器的地址栏中的地址最后面添加 /heroes ,路由本应该会匹配地址,并显示 HeroesComponent 。然而事实并非如此。因为你还没告诉路由要在哪里显示这个组件。为了实现这个,你可以在模板的最后添加 <router-outlet> 元素,<router-outlet> 是 RouterModule 中的其中一个指令。

Router links

用户不必把路由地址粘贴到地址栏中,因为你可以在模板中添加锚元素。实现这个功能:当用点击时自动跳转到 HeroesComponent 。

改进后的模板如下:

<h1>{{title}}</h1>
   <a routerLink="/heroes">Heroes</a>
   <router-outlet></router-outlet>

注意:路由链接是绑定到锚标签的。 RouterLink 是一个指令(RouterModule 中的一个指令),当用户点击时,告诉路由应显示什么内容。

因为这个链接不是动态的,所以我们才显式的直接绑定到路径路径中。

此时浏览器显示的就是一个标题和一个链接,而不是一个heroes 列表。

点击 heroes 导航链接,地址栏就会自动更新到 /heroes ,并显示 heroes 列表。

AppComponent 的代码如下:

src/app/app.component.ts
import { Component } from '@angular/core';
 
@Component({
  selector: 'my-app',
  template: `
     <h1>{{title}}</h1>
     <a routerLink="/heroes">Heroes</a>
     <router-outlet></router-outlet>
   `
})
export class AppComponent {
  title = 'Tour of Heroes';
}

AppComponent 现在已经有了自己对应的路由并会显示对应的视图。为此,它跟其它组件是有区别的。因为现在它是一个路由组件了。

添加一个 dashboard

路由只会在多视图时才是有意义的。因为我们需要多添加一个视图。创建一个 DashboardComponent ,让用户可以实现来回切换。

src/app/dashboard.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'my-dashboard',
  template: '<h3>My Dashboard</h3>'
})
export class DashboardComponent { }

后面将会完善这个组件。

配置 dashboard 路由

要想教会 app.module.ts 导航到 dashboard ,我们需要把 dashboard 组件和相应的路由添加到Routes 数组中。

src/app/app.module.ts (Dashboard route)
{
  path: 'dashboard',
  component: DashboardComponent
},

同时我们也要把 DashboardComponent  添加到 AppModule 的 declarations 数组中。

src/app/app.module.ts (dashboard)
declarations: [
  AppComponent,
  DashboardComponent,
  HeroDetailComponent,
  HeroesComponent
],

添加一个跳转路由

现在,应用启动时的路径为根目录 / 。而我们要把它转到 /dashboard URL 链接。

为了实现这个,我们可以用一个转向路由,添加如下代码到路由配置中:

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

添加导航到模板

在模板中添加dashboard 导航,就像前面我们的 Heroes 链接一样。

<h1>{{title}}</h1>
   <nav>
     <a routerLink="/dashboard">Dashboard</a>
     <a routerLink="/heroes">Heroes</a>
   </nav>
   <router-outlet></router-outlet>

现在刷新页面,就可以看到 dashboard 并且你可以在 dashboard  和 heroes 中来回切换。

在 dashboard 中添加 heroes

为了让视图更有趣,你可以显示前四名英雄。

把 template 属性改成 templateUrl 属性,以便于我们引入模板。

src/app/dashboard.component.ts (metadata)
@Component({
  selector: 'my-dashboard',
  templateUrl: './dashboard.component.html',
})

创建一个文件

src/app/dashboard.component.html
<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <div *ngFor="let hero of heroes" class="col-1-4">
    <div class="module hero">
      <h4>{{hero.name}}</h4>
    </div>
  </div>
</div>

*ngFor 再一次用来迭代 heroes 。外面 <div> 将用于样式控制。

共享 HeroService

放到组件的 heroes 数组中,你就可以重复使用 HeroService 了

之前 你从HeroesComponent中的 providers  数组里删除了 HeroService ,并把它添加到 AppModule 的 providers  数组里。这样就生成了一个  HeroService 实例所以组件都可以使用这个服务 , Angular 注入 HeroService  ,你可以在 DashboardComponent 使用它。

获取 heroes

在 dashboard.component.ts 中添加如下引入声明

src/app/dashboard.component.ts (imports)
import { Component, OnInit } from '@angular/core';

import { Hero } from './hero';
import { HeroService } from './hero.service';

像下面这样创建 DashboardComponent  类

src/app/dashboard.component.ts (class)
export class DashboardComponent implements OnInit {

  heroes: Hero[] = [];

  constructor(private heroService: HeroService) { }

  ngOnInit(): void {
    this.heroService.getHeroes()
      .then(heroes => this.heroes = heroes.slice(1, 5));
  }
}

在 HeroesComponent 中也可以使用这种逻辑:

1、定义一个 heroes 数组属性

2、在构造器(constructor )中注入 HeroService 并把它保存在一个私有的字段中。

3、通过Angular 的 ngOnInit 生命勾子调用服务来获取 heroes 数据。

在 dashboard 中我们通过 Array.slice 方法声明了四个 heroes(2nd, 3rd, 4th, and 5th),刷新页面就可以看到了。

导航到 heroes 详情页

用户除了可以在选中的 hero 下方看到其详情外,也应该能够有其它的方式来查看 hero 详情。

1、在 dashboard  中选中的 hero

2、在 hero 列表中选中的 hero

3、通过深度链接访问

路由到 heroes 详情

你可以在 app.module.ts 中追加 HeroDetailComponent 的路由。

不过现在这个新的路由不有异常的,因为你必需告诉 HeroDetailComponent 要显示哪一个 hero ,但你不过告诉 HeroesComponent 或者 DashboardComponent 任何东西。

HeroesComponent 可以像下面这样把选中的 hero 绑定到一个 hero 对象中。

<hero-detail [hero]="selectedHero"></hero-detail>

但这个绑定的数组还不能用到任何一个路由场景。

参数化路由

你可以把 hero 添加到 URL 中,比如:当路由到 一个 id 为11 的 hero 时,你将会在地址栏的 URL 中看到这个参数

/detail/11

URL 中的 /detail/就是一个常量,后面的数字就是实现在不同的 hero 之间切换的。你需要用一个代表 hero 身份的参数(或令牌)来表示路由的参数部分。

配置有参数的路由

src/app/app.module.ts (hero detail)

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

路径中的冒号(:)表示 :id 是一个特定的 hero id 的占位符,请确保在创建这个路由之前已经引入了 hero 的详情组件。

现在你不需要把 ‘Hero Detail’ 链接添加到模板中,因为用户现在不需要点击导航链接进 特定的 hero 详情了,而是可以选择在 hero 列表中显示,还是在 dashboard 显示。

在 HeroDetailComponent 还没得到改进和准备好之前,你不必添加 hero 点击。

改进的 HeroDetailComponent

src/app/hero-detail.component.ts (current)
import { Component, Input } from '@angular/core';
import { Hero } from './hero';
 
@Component({
  selector: 'hero-detail',
  template: `
    <div *ngIf="hero">
      <h2>{{hero.name}} details!</h2>
      <div>
        <label>id: </label>{{hero.id}}
      </div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="hero.name" placeholder="name"/>
      </div>
    </div>
  `
})
export class HeroDetailComponent {
  @Input() hero: Hero;
}

模板不会有变化,hero 的名字将会以相同的方式显示。主要的变化是你获取 heroes 的方式不同。

你将不再需要通过父组件属性绑定来来获取 hero 数据。新的 HeroDetailComponent 在 ActivatedRoute 服务中的 paramMap Observable 中获取 id 参数并通过 HeroService 来获取对应 id 的 hero 。

添加如下 imports:

src/app/hero-detail.component.ts
// Keep the Input import for now, you'll remove it later:
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Location }                 from '@angular/common';

import { HeroService } from './hero.service';

在构造器(constructor)中注入 ActivatedRoute 、HeroService 和 Location 服务,把它们保存到一个私有变量中。

src/app/hero-detail.component.ts (constructor)

constructor(
  private heroService: HeroService,
  private route: ActivatedRoute,
  private location: Location
) {}

Import switchMap

src/app/hero-detail.component.ts (switchMap import)
import 'rxjs/add/operator/switchMap';

继承 OnInit 接口

src/app/hero-detail.component.ts
export class HeroDetailComponent implements OnInit {

在 ngOnInit 的生命周期勾子中,使用 paramMap Observable 从 ActivatedRoute  服务中获取 HeroService 中的对应 id 的 hero 。

src/app/hero-detail.component.ts
ngOnInit(): void {
  this.route.paramMap
    .switchMap((params: ParamMap) => this.heroService.getHero(+params.get('id')))
    .subscribe(hero => this.hero = hero);
}

switchMap运算符将Observable路由参数中的id映射到一个新的Observable,即HeroService.getHero()方法的结果。

如果用户再一次请求这个组件,而第一次还没完成时,switchMap  会取消旧的请求,然后再次调用  HeroService.getHero() 。

hero 的 id 参数是一个 number 类型,而 Route 的参数总是字符串类型,所以路由参数会被自动转化成字符串。

添加 HeroService.getHero()

在先前的代码中,HeroService 并没有 getHero() 方法,所以我们要给它添加此方法,打开 HeroService 文件,添加 getHero() 方法,此方法可以通过 id 来过滤 hero 。

src/app/hero.service.ts (getHero)
getHero(id: number): Promise<Hero> {
  return this.getHeroes()
             .then(heroes => heroes.find(hero => hero.id === id));
}

添加返回

现在用户有多种方式导航到 HeroDetailComponent

要导航到其它位置,用户可以单击 AppComponent  中的链接之一,或者点击返回按钮,现在我们来添加第三项,gobakc()方法,用于返回上一页。

src/app/hero-detail.component.ts (goBack)
goBack(): void {
  this.location.back();
}

你还需要在模板中添加一个绑定了此方法的按钮

<button (click)="goBack()">Back</button>

把模板代码称到 hero-detail.component.html

src/app/hero-detail.component.html
<div *ngIf="hero">
  <h2>{{hero.name}} details!</h2>
  <div>
    <label>id: </label>{{hero.id}}</div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="hero.name" placeholder="name" />
  </div>
  <button (click)="goBack()">Back</button>
</div>

更新组件元数据(template  改成 templateUrl)

src/app/hero-detail.component.ts (metadata)
@Component({
  selector: 'hero-detail',
  templateUrl: './hero-detail.component.html',
})

选择一个 dashboard hero

当你在 dashboard 中选中了一个 hero 后,应用会跳转到对应的 HeroDetailComponent 详情页,你除了可以在这个页面中看到选中 hero 的详情外,你还可以对这些信息进行编辑。

虽然 dashboard  中的 heroes 呈现为按钮的形式,但它们应该像锚标签一样。当悬停在 hero 区块上时,目标网址应显示在浏览器状态栏中,用户应该能够复制链接或在新选项卡中打开英雄详细信息视图。

要想达到这个效果,打开 dashboard.component.html 用<a> 标签替换 <div *ngFor…>

src/app/dashboard.component.html (repeated <a> tag)
<a *ngFor="let hero of heroes"  [routerLink]="['/detail', hero.id]"  class="col-1-4">

注意[routerLink]绑定。 如本页的路由器链接部分所述,AppComponent 模板中的顶级导航将路由器链接设置为目标路,“/ dashboard”和“/ heroes”的固定路径。

这一次,你绑定到一个包含链接参数数组的表达式。 该数组有两个元素:目标路由的路径和路由参数,参数为当前英雄的id的值。

数组中的两个元素分别对应你在 app.module.ts 中初始化的路由中的 path 和 id。

src/app/app.module.ts (hero detail)
{
  path: 'detail/:id',
  component: HeroDetailComponent
},

刷新浏览器,在 dashboard 中选中一个 hero ,应用就会导航到对应的 hero 详情了。

重组路由到路由模块

AppModule 中20多行代码用于配置四个路由,大多数应用程序都有更多的路由,并且会增加保护服务,以防止不必要的或未经授权的导航。

将路由配置重构到自己的类中是个好主意。 当前的RouterModule.forRoot() 生成一个Angular ModuleWithProviders,专用于路由的类应该是一个路由模块。

按照惯例,路由模块名称包含单词“Routing”,并与声明组件导航到的模块的名称对应。

创建一个 app-routing.module.ts 文件,内容如下:

src/app/app-routing.module.ts
import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
 
import { DashboardComponent }   from './dashboard.component';
import { HeroesComponent }      from './heroes.component';
import { HeroDetailComponent }  from './hero-detail.component';
 
const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard',  component: DashboardComponent },
  { path: 'detail/:id', component: HeroDetailComponent },
  { path: 'heroes',     component: HeroesComponent }
];
 
@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ]
})
export class AppRoutingModule {}

更新 AppModule

删除 AppModule  中的路由配置,并把 AppRoutingModule 添加到 NgModule.imports 列表中。下面就是改进后的 AppModule:

import { NgModule }       from '@angular/core';
import { BrowserModule }  from '@angular/platform-browser';
import { FormsModule }    from '@angular/forms';
 
import { AppComponent }         from './app.component';
import { DashboardComponent }   from './dashboard.component';
import { HeroDetailComponent }  from './hero-detail.component';
import { HeroesComponent }      from './heroes.component';
import { HeroService }          from './hero.service';
 
import { AppRoutingModule }     from './app-routing.module';
 
@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent
  ],
  providers: [ HeroService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

在 HeroesComponent 选择 hero

在HeroesComponent中,当前模板展示了一个“主/细”风格,顶部的英雄列表以及下面所选英雄的细节。

src/app/heroes.component.ts (current template)
template: `
  <h1>{{title}}</h1>
  <h2>My Heroes</h2>
  <ul class="heroes">
    <li *ngFor="let hero of heroes"
      [class.selected]="hero === selectedHero"
      (click)="onSelect(hero)">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
    </li>
  </ul>
  <hero-detail [hero]="selectedHero"></hero-detail>
`,

删除<h1> 标签,删除 <hero-detail> 标签,你不再这里显示完整的 HeroDetailComponent ,而是在一个单独的页面中显示。

然后当用户点击了一个 hero 后,会在 hero 列表下方出现简短的信息,你必需点击按钮后才会跳转到详情页。

添加微详情

src/app/heroes.component.ts
<div *ngIf="selectedHero">
  <h2>
    {{selectedHero.name | uppercase}} is my hero
  </h2>
  <button (click)="gotoDetail()">View Details</button>
</div>

当你点击了一个 hero 后,你就会在 hero 列表下方看到如下图所示的信息:

大小字管道格式化数据

通过管道,格式化 hero 的名字为大写字母

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

管道是格式化字符串,货币金额,日期和其他显示数据的好方法。Angular 为我们提供了很多这样的管道,您也可以自己编写。

将内容移出组件文件

当用户单击“查看详细信息”按钮时,您仍然必须更新组件类以支持导航到HeroDetailComponent。

组件文件很大。 在HTML和CSS的噪音中找到组件逻辑是很困难的。

在进行任何更改之前,请将模板和样式迁移到自己的文件中。

把 模板代码放到从 heroes.component.ts 移到新建文件 heroes.component.html 中。样式移到 heroes.component.css 中。

src/app/heroes.component.html
<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes"
    [class.selected]="hero === selectedHero"
    (click)="onSelect(hero)">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>
<div *ngIf="selectedHero">
  <h2>
    {{selectedHero.name | uppercase}} is my hero
  </h2>
  <button (click)="gotoDetail()">View Details</button>
</div>
src/app/heroes.component.css
.selected {
  background-color: #CFD8DC !important;
  color: white;
}
.heroes {
  margin: 0 0 2em 0;
  list-style-type: none;
  padding: 0;
  width: 15em;
}
.heroes li {
  cursor: pointer;
  position: relative;
  left: 0;
  background-color: #EEE;
  margin: .5em;
  padding: .3em 0;
  height: 1.6em;
  border-radius: 4px;
}
.heroes li:hover {
  color: #607D8B;
  background-color: #DDD;
  left: .1em;
}
.heroes li.selected:hover {
  background-color: #BBD8DC !important;
  color: white;
}
.heroes .text {
  position: relative;
  top: -3px;
}
.heroes .badge {
  display: inline-block;
  font-size: small;
  color: white;
  padding: 0.8em 0.7em 0 0.7em;
  background-color: #607D8B;
  line-height: 1em;
  position: relative;
  left: -1px;
  top: -4px;
  height: 1.8em;
  margin-right: .8em;
  border-radius: 4px 0 0 4px;
}
button {
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  cursor: hand;
}
button:hover {
  background-color: #cfd8dc;
}

回到 heroes.component.ts 修改元数据的两个属性,把template 和 styles 改成 templateUrl 和 tyleUrls 属性,并改入相应文件。

src/app/heroes.component.ts (revised metadata)
@Component({
  selector: 'my-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: [ './heroes.component.css' ]
})

更新 HeroesComponent 类

通过按钮我们可以从 HeroesComponent 导航到 HeroesDetailComponent 按钮点击事件绑定了 gotoDetail() 方法。

component 的变化如下:

1、引入 Router

2、在 constructor 中注入 Router

3、通过调用 router navigate() 方法实现 gotoDetail() 方法。

src/app/heroes.component.ts (gotoDetail)
gotoDetail(): void {
  this.router.navigate(['/detail', this.selectedHero.id]);
}
src/app/heroes.component.ts (class)
export class HeroesComponent implements OnInit {
  heroes: Hero[];
  selectedHero: Hero;
 
  constructor(
    private router: Router,
    private heroService: HeroService) { }
 
  getHeroes(): void {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }
 
  ngOnInit(): void {
    this.getHeroes();
  }
 
  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
 
  gotoDetail(): void {
    this.router.navigate(['/detail', this.selectedHero.id]);
  }
}

添加样式

新建 dashboard.component.css 文件,修改 dashboard.component.ts 中的元数据中的 styleUrls 。

src/app/dashboard.component.ts (styleUrls)
styleUrls: [ './dashboard.component.css' ]

美化 hero details

添加 hero-detail.component.css 样式文件

src/app/hero-detail.component.css
label {
  display: inline-block;
  width: 3em;
  margin: .5em 0;
  color: #607D8B;
  font-weight: bold;
}
input {
  height: 2em;
  font-size: 1em;
  padding-left: .4em;
}
button {
  margin-top: 20px;
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer; cursor: hand;
}
button:hover {
  background-color: #cfd8dc;
}
button:disabled {
  background-color: #eee;
  color: #ccc; 
  cursor: auto;
}
src/app/dashboard.component.css
[class*='col-'] {
  float: left;
  padding-right: 20px;
  padding-bottom: 20px;
}
[class*='col-']:last-of-type {
  padding-right: 0;
}
a {
  text-decoration: none;
}
*, *:after, *:before {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}
h3 {
  text-align: center; margin-bottom: 0;
}
h4 {
  position: relative;
}
.grid {
  margin: 0;
}
.col-1-4 {
  width: 25%;
}
.module {
  padding: 20px;
  text-align: center;
  color: #eee;
  max-height: 120px;
  min-width: 120px;
  background-color: #607D8B;
  border-radius: 2px;
}
.module:hover {
  background-color: #EEE;
  cursor: pointer;
  color: #607d8b;
}
.grid-pad {
  padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
  padding-right: 20px;
}
@media (max-width: 600px) {
  .module {
    font-size: 10px;
    max-height: 75px; }
}
@media (max-width: 1024px) {
  .grid {
    margin: 0;
  }
  .module {
    min-width: 60px;
  }
}

美化 navigation links

新建 app.component.css

src/app/app.component.css (navigation styles)
h1 {
  font-size: 1.2em;
  color: #999;
  margin-bottom: 0;
}
h2 {
  font-size: 2em;
  margin-top: 0;
  padding-top: 0;
}
nav a {
  padding: 5px 10px;
  text-decoration: none;
  margin-top: 10px;
  display: inline-block;
  background-color: #eee;
  border-radius: 4px;
}
nav a:visited, a:link {
  color: #607D8B;
}
nav a:hover {
  color: #039be5;
  background-color: #CFD8DC;
}
nav a.active {
  color: #039be5;
}
src/app/app.component.ts (active router links)
template: `
  <h1>{{title}}</h1>
  <nav>
    <a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
    <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
  </nav>
  <router-outlet></router-outlet>
`,

添加 styleUrls 属性,如下:

src/app/app.component.ts
styleUrls: ['./app.component.css'],

全局样式

src/styles.css (excerpt)
/* Master Styles */
h1 {
  color: #369;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 250%;
}
h2, h3 {
  color: #444;
  font-family: Arial, Helvetica, sans-serif;
  font-weight: lighter;
}
body {
  margin: 2em;
}
body, input[text], button {
  color: #888;
  font-family: Cambria, Georgia;
}
/* everywhere else */
* {
  font-family: Arial, Helvetica, sans-serif;
}
src/index.html (link ref)
<link rel="stylesheet" href="styles.css">

Angular 2 路由

应用结构

Angular 2 路由

源代码

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; // <-- NgModel lives here
import { AppComponent } from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent } from './heroes.component';
import { DashboardComponent } from './dashboard.component';
import { HeroService } from './hero.service';
import { AppRoutingModule } from './app-routing.module';

// 用于定义模块用的装饰器
@NgModule({
  // 导入模块依赖的组件、指令等;
  declarations: [
    AppComponent,
    HeroesComponent,
    HeroDetailComponent,
    DashboardComponent
  ],
  // 用来导入当前模块所需的其他模块;
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ],

  // 导入相关的服务
  providers: [HeroService],
  // 标记出引导组件,在 Angular 启动应用时,将被标记的组件渲染到模板中。
  bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})

export class AppComponent {
  title = 'Tour of Heroes';
}
app.component.html
<h1>{{title}}</h1>
<nav>
  <a routerLink="/dashboard">Dashboard</a>
  <a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
app.component.css
h1 {
  font-size: 1.2em;
  color: #999;
  margin-bottom: 0;
}

h2 {
  font-size: 2em;
  margin-top: 0;
  padding-top: 0;
}

nav a {
  padding: 5px 10px;
  text-decoration: none;
  margin-top: 10px;
  display: inline-block;
  background-color: #eee;
  border-radius: 4px;
}

nav a:visited,
a:link {
  color: #607D8B;
}

nav a:hover {
  color: #039be5;
  background-color: #CFD8DC;
}

nav a.active {
  color: #039be5;
}
dashboard.component.ts
import { Component, OnInit } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
  selector: 'my-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {

  heroes: Hero[] = [];

  constructor(private heroService: HeroService) { }

  ngOnInit(): void {
    this.heroService.getHeroes()
      .then(heroes => this.heroes = heroes.slice(1, 5));
  }
}
dashboard.component.html
<h3>Top Heroes</h3>
<div class="grid grid-pad">
  <a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4">
    <div class="module hero">
      <h4>{{hero.name}}</h4>
    </div>
  </a>
</div>
dashboard.component.css
[class*='col-'] {
  float: left;
  padding-right: 20px;
  padding-bottom: 20px;
}

[class*='col-']:last-of-type {
  padding-right: 0;
}

a {
  text-decoration: none;
}

*,
*:after,
*:before {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

h3 {
  text-align: center;
  margin-bottom: 0;
}

h4 {
  position: relative;
}

.grid {
  margin: 0;
}

.col-1-4 {
  width: 25%;
}

.module {
  padding: 20px;
  text-align: center;
  color: #eee;
  max-height: 120px;
  min-width: 120px;
  background-color: #607D8B;
  border-radius: 2px;
}

.module:hover {
  background-color: #EEE;
  cursor: pointer;
  color: #607d8b;
}

.grid-pad {
  padding: 10px 0;
}

.grid-pad>[class*='col-']:last-of-type {
  padding-right: 20px;
}

@media (max-width: 600px) {
  .module {
    font-size: 10px;
    max-height: 75px;
  }
}

@media (max-width: 1024px) {
  .grid {
    margin: 0;
  }
  .module {
    min-width: 60px;
  }
}
heroes.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Hero } from './hero';
import { HeroService } from './hero.service';

// 创建一个HEROES 常量(数组类型,数组的每一项为一个 Hero 实例子)
const HEROES: Hero[] = [
  { id: 11, name: 'Mr. Nice' },
  { id: 12, name: 'Narco' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];

@Component({
  selector: 'my-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css'],
  //声明服务,当前组件及子组件可用共用此服务实例
  providers: [HeroService]
})

export class HeroesComponent implements OnInit {

  heroes: Hero[];

  selectedHero: Hero;

  // 注入服务
  constructor(
    private heroService: HeroService,
    private router: Router,
  ) { };
  // 获取数据方法定义
  getHeroes(): void {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }
  //选中处理函数
  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
  // 通过生命周期勾子调用函数获取数据
  ngOnInit(): void {
    this.getHeroes();
  }

  gotoDetail(): void {
    this.router.navigate(['/detail', this.selectedHero.id]);
  }

}
heroes.component.html
<h1>{{title}}</h1>
<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)">
    <span class="badge">{{hero.id}}</span> {{hero.name}}
  </li>
</ul>
<hero-detail [hero]="selectedHero"></hero-detail>
<div *ngIf="selectedHero">
  <h2>
    {{selectedHero.name | uppercase}} is my hero
  </h2>
  <button (click)="gotoDetail()">View Details</button>
</div>
heroes.component.css
.selected {
  background-color: #CFD8DC !important;
  color: white;
}

.heroes {
  margin: 0 0 2em 0;
  list-style-type: none;
  padding: 0;
  width: 15em;
}

.heroes li {
  cursor: pointer;
  position: relative;
  left: 0;
  background-color: #EEE;
  margin: .5em;
  padding: .3em 0;
  height: 1.6em;
  border-radius: 4px;
}

.heroes li:hover {
  color: #607D8B;
  background-color: #DDD;
  left: .1em;
}

.heroes li.selected:hover {
  background-color: #BBD8DC !important;
  color: white;
}

.heroes .text {
  position: relative;
  top: -3px;
}

.heroes .badge {
  display: inline-block;
  font-size: small;
  color: white;
  padding: 0.8em 0.7em 0 0.7em;
  background-color: #607D8B;
  line-height: 1em;
  position: relative;
  left: -1px;
  top: -4px;
  height: 1.8em;
  margin-right: .8em;
  border-radius: 4px 0 0 4px;
}

button {
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  cursor: hand;
}

button:hover {
  background-color: #cfd8dc;
}
hero-detail.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { Hero } from './hero';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Location } from '@angular/common';
import { HeroService } from './hero.service';
// 用于提取参数
import 'rxjs/add/operator/switchMap';

@Component({
    selector: 'hero-detail',
    templateUrl: './hero-detail.component.html',
    styleUrls: ['./hero-detail.component.css']
})

export class HeroDetailComponent implements OnInit {
    @Input() hero: Hero;

    constructor(
        private heroService: HeroService,
        private route: ActivatedRoute,
        private location: Location
    ) { }


    ngOnInit(): void {
        this.route.paramMap
            .switchMap((params: ParamMap) => this.heroService.getHero(+params.get('id')))
            .subscribe(hero => this.hero = hero);
    }

    goBack(): void {
        this.location.back();
    }
}
hero-detail.component.html
<div *ngIf="hero">
  <h2>{{hero.name}} details!</h2>
  <div>
    <label>id: </label>{{hero.id}}
  </div>
  <div>
    <label>name: </label>
    <input [(ngModel)]="hero.name" placeholder="name" />
  </div>
  <button (click)="goBack()">Back</button>
</div>
hero-detail.component.css
label {
  display: inline-block;
  width: 3em;
  margin: .5em 0;
  color: #607D8B;
  font-weight: bold;
}

input {
  height: 2em;
  font-size: 1em;
  padding-left: .4em;
}

button {
  margin-top: 20px;
  font-family: Arial;
  background-color: #eee;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
  cursor: hand;
}

button:hover {
  background-color: #cfd8dc;
}

button:disabled {
  background-color: #eee;
  color: #ccc;
  cursor: auto;
}
hero.service.ts
import { Injectable } from '@angular/core';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable()
export class HeroService {
    getHeroes(): Promise<Hero[]> {
        return Promise.resolve(HEROES);
    }

    getHero(id: number): Promise<Hero> {
        return this.getHeroes()
            .then(heroes => heroes.find(hero => hero.id === id));
    }
}
mack-heroes.ts
import { Hero } from './hero';

export const HEROES: Hero[] = [
    { id: 11, name: 'Mr. Nice' },
    { id: 12, name: 'Narco' },
    { id: 13, name: 'Bombasto' },
    { id: 14, name: 'Celeritas' },
    { id: 15, name: 'Magneta' },
    { id: 16, name: 'RubberMan' },
    { id: 17, name: 'Dynama' },
    { id: 18, name: 'Dr IQ' },
    { id: 19, name: 'Magma' },
    { id: 20, name: 'Tornado' }
];
hero.ts
export class Hero {
    id: number;
    name: string;
}

Angular 4.x 的路由功能就分享到这里。前面说那么多,不如看一下源代码来得爽快。如果觉得乱,那么建议把源代码都拷贝到自己的项目中运行起来,这样就可以更好的研究它个路由功能的实现过程,并且会非常清楚。千万不能走马观花,这是程序员的大忌。

  • 微信扫一扫,赏我

  • 支付宝扫一扫,赏我

声明

原创文章,不经本站同意,不得以任何形式转载,如有不便,请多多包涵!

本文永久链接:http://yunkus.com/angular-tutorial-part5/

评论1
  1. gmlan 2017年10月2日 at pm1:13 回复

    写的太好了,一篇一篇看下来,终于可以入门了。
    有个问题:
    dashboard.component.ts中定义的 selector: ‘my-dashboard’,
    heroes.component.ts中定义的selector: ‘my-heroes’,

    没有看到有什么用呀?!

Leave a Reply

Your email address will not be published. Required fields are marked *

评论 END