Hero guide 添加应用内导航

2020-07-01 10:55 更新

假想

假设有一些“英雄指南”的新需求:

  • 添加一个仪表盘视图。

  • 添加在英雄列表和仪表盘视图之间导航的能力。

  • 无论在哪个视图中点击一个英雄,都会导航到该英雄的详情页。

  • 在邮件中点击一个深链接,会直接打开一个特定英雄的详情视图。

完成效果:

添加 AppRoutingModule

在 Angular 中,最好在一个独立的顶层模块中加载和配置路由器,它专注于路由功能,然后由根模块 AppModule 导入它。

按照惯例,这个模块类的名字叫做 AppRoutingModule,并且位于 "src/app" 下的 "app-routing.module.ts" 文件中。

使用 CLI 生成它。

ng generate module app-routing --flat --module=app

注:
- --flat 把这个文件放进了 src/app 中,而不是单独的目录中。

  • --module=app 告诉 CLI 把它注册到 AppModule 的 imports 数组中。

生成文件是这样的:

Path:"src/app/app-routing.module.ts (generated)"

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


@NgModule({
  imports: [
    CommonModule
  ],
  declarations: []
})
export class AppRoutingModule { }

把它替换如下:

Path:"src/app/app-routing.module.ts (updated)"

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroesComponent } from './heroes/heroes.component';


const routes: Routes = [
  { path: 'heroes', component: HeroesComponent }
];


@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

首先,AppRoutingModule 会导入 RouterModule 和 Routes,以便该应用具有路由功能。配置好路由后,接着导入 HeroesComponent,它将告诉路由器要去什么地方。

注意,对 CommonModule 的引用和 declarations 数组不是必要的,因此它们不再是 AppRoutingModule 的一部分。以下各节将详细介绍 AppRoutingModule 的其余部分。

路由

该文件的下一部分是你的路由配置。 Routes 告诉路由器,当用户单击链接或将 URL 粘贴进浏览器地址栏时要显示哪个视图。

由于 AppRoutingModule 已经导入了 HeroesComponent,因此你可以直接在 routes 数组中使用它:

Path:"src/app/app-routing.module.ts"

const routes: Routes = [
  { path: 'heroes', component: HeroesComponent }
];

典型的 Angular Route 具有两个属性:

path: 用来匹配浏览器地址栏中 URL 的字符串。

component: 导航到该路由时,路由器应该创建的组件。

这会告诉路由器把该 URL 与 path:'heroes' 匹配。 如果网址类似于 "localhost:4200/heroes" 就显示 HeroesComponent。

RouterModule.forRoot()

@NgModule 元数据会初始化路由器,并开始监听浏览器地址的变化。

下面的代码行将 RouterModule 添加到 AppRoutingModule 的 imports 数组中,同时通过调用 RouterModule.forRoot() 来用这些 routes 配置它:

Path:"src/app/app-routing.module.ts"

imports: [ RouterModule.forRoot(routes) ],

注:
- 这个方法之所以叫 forRoot(),是因为你要在应用的顶层配置这个路由器。 forRoot() 方法会提供路由所需的服务提供者和指令,还会基于浏览器的当前 URL 执行首次导航。

接下来,AppRoutingModule 导出 RouterModule,以便它在整个应用程序中生效。

Path:"src/app/app-routing.module.ts (exports array)"

exports: [ RouterModule ]

添加路由出口 RouterOutlet

打开 AppComponent 的模板,把 <app-heroes> 元素替换为 <router-outlet> 元素。

Path:"src/app/app.component.html (router-outlet)"

<h1>{{title}}</h1>
<router-outlet></router-outlet>
<app-messages></app-messages>

AppComponent 的模板不再需要 <app-heroes>,因为只有当用户导航到这里时,才需要显示 HeroesComponent。

<router-outlet> 会告诉路由器要在哪里显示路由的视图。

注:
- 能在 AppComponent 中使用 RouterOutlet,是因为 AppModule 导入了 AppRoutingModule,而 AppRoutingModule 中导出了 RouterModule。 在本教程开始时你运行的那个 ng generate 命令添加了这个导入,是因为 --module=app 标志。如果你手动创建 app-routing.module.ts 或使用了 CLI 之外的工具,你就要把 AppRoutingModule 导入到 app.module.ts 中,并且把它添加到 NgModule 的 imports 数组中。

此时浏览器应该刷新,并显示应用标题,但是没有显示英雄列表。 看看浏览器的地址栏。 URL 是以 / 结尾的。 而到 HeroesComponent 的路由路径是 /heroes

在地址栏中把 /heroes 追加到 URL 后面。你应该能看到熟悉的主从结构的英雄显示界面。

添加路由链接 (routerLink)

理想情况下,用户应该能通过点击链接进行导航,而不用被迫把路由的 URL 粘贴到地址栏。

添加一个 <nav> 元素,并在其中放一个链接 <a> 元素,当点击它时,就会触发一个到 HeroesComponent 的导航。 修改过的 AppComponent 模板如下:

Path:"src/app/app.component.html (heroes RouterLink)"

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

routerLink 属性的值为 "/heroes",路由器会用它来匹配出指向 HeroesComponent 的路由。 routerLinkRouterLink 指令的选择器,它会把用户的点击转换为路由器的导航操作。 它是 RouterModule 中的另一个公共指令。

刷新浏览器,显示出了应用的标题和指向英雄列表的链接,但并没有显示英雄列表。

点击这个链接。地址栏变成了 /heroes,并且显示出了英雄列表。

注:
- 从下面的 最终代码中把私有 CSS 样式添加到 app.component.css 中,可以让导航链接变得更好看一点。

添加仪表盘视图

当有多个视图时,路由会更有价值。不过目前还只有一个英雄列表视图。

使用 CLI 添加一个 DashboardComponent:

ng generate component dashboard

CLI 生成了 DashboardComponent 的相关文件,并把它声明到 AppModule 中。

把这三个文件中的内容改成这样:

  1. Path:"src/app/dashboard/dashboard.component.html"

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

  1. Path:"src/app/dashboard/dashboard.component.ts"

import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';


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


  constructor(private heroService: HeroService) { }


  ngOnInit() {
    this.getHeroes();
  }


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

  1. Path:"src/app/dashboard/dashboard.component.css"

/* DashboardComponent's private CSS styles */
[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: #3f525c;
  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;
  }
}

这个模板用来表示由英雄名字链接组成的一个阵列。

*ngFor 复写器为组件的 heroes 数组中的每个条目创建了一个链接。

这些链接被 dashboard.component.css 中的样式格式化成了一些色块。

这些链接还没有指向任何地方,但很快就会了。

这个类和 HeroesComponent 类很像。

它定义了一个 heroes 数组属性。

它的构造函数希望 Angular 把 HeroService 注入到私有的 heroService 属性中。

ngOnInit() 生命周期钩子中调用 getHeroes()

这个 getHeroes() 函数会截取第 2 到 第 5 位英雄,也就是说只返回四个顶层英雄(第二,第三,第四和第五)。

Path:"src/app/dashboard/dashboard.component.ts"

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

添加仪表盘路由

要导航到仪表盘,路由器中就需要一个相应的路由。

把 DashboardComponent 导入到 AppRoutingModule 中。

Path:"src/app/app-routing.module.ts (import DashboardComponent)"

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

把一个指向 DashboardComponent 的路由添加到 AppRoutingModule.routes 数组中。

Path:"src/app/app-routing.module.ts"

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

添加默认路由

当应用启动时,浏览器的地址栏指向了网站的根路径。 它没有匹配到任何现存路由,因此路由器也不会导航到任何地方。 <router-outlet> 下方是空白的。

要让应用自动导航到这个仪表盘,请把下列路由添加到 AppRoutingModule.Routes 数组中。

Path:"src/app/app-routing.module.ts"

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

这个路由会把一个与空路径“完全匹配”的 URL 重定向到路径为 '/dashboard' 的路由。

浏览器刷新之后,路由器加载了 DashboardComponent,并且浏览器的地址栏会显示出 /dashboard 这个 URL。

把仪表盘链接添加到壳组件中

应该允许用户通过点击页面顶部导航区的各个链接在 DashboardComponent 和 HeroesComponent 之间来回导航。

把仪表盘的导航链接添加到壳组件 AppComponent 的模板中,就放在 Heroes 链接的前面。

Path:"src/app/app.component.html"

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

刷新浏览器,你就能通过点击这些链接在这两个视图之间自由导航了。

导航到英雄详情

HeroDetailComponent 可以显示所选英雄的详情。 此刻,HeroDetailsComponent 只能在 HeroesComponent 的底部看到。

用户应该能通过三种途径看到这些详情。

通过在仪表盘中点击某个英雄。

通过在英雄列表中点击某个英雄。

通过把一个“深链接” URL 粘贴到浏览器的地址栏中来指定要显示的英雄。

在这一节,你将能导航到 HeroDetailComponent,并把它从 HeroesComponent 中解放出来。

从 HeroesComponent 中删除英雄详情

当用户在 HeroesComponent 中点击某个英雄条目时,应用应该能导航到 HeroDetailComponent,从英雄列表视图切换到英雄详情视图。 英雄列表视图将不再显示,而英雄详情视图要显示出来。

打开 HeroesComponent 的模板文件(heroes/heroes.component.html),并从底部删除 <app-hero-detail> 元素。

目前,点击某个英雄条目还没有反应。不过当你启用了到 HeroDetailComponent 的路由之后,很快就能修复它。

添加英雄详情视图

要导航到 id 为 11 的英雄的详情视图,类似于 ~/detail/11 的 URL 将是一个不错的 URL。

打开 AppRoutingModule 并导入 HeroDetailComponent

Path:"src/app/app-routing.module.ts (import HeroDetailComponent)"

import { HeroDetailComponent }  from './hero-detail/hero-detail.component';

然后把一个参数化路由添加到 AppRoutingModule.routes 数组中,它要匹配指向英雄详情视图的路径。

Path:"src/app/app-routing.module.ts"

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

path 中的冒号(:)表示 :id 是一个占位符,它表示某个特定英雄的 id

此刻,应用中的所有路由都就绪了。

Path:"src/app/app-routing.module.ts (all routes)"

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard', component: DashboardComponent },
  { path: 'detail/:id', component: HeroDetailComponent },
  { path: 'heroes', component: HeroesComponent }
];

DashboardComponent 中的英雄链接

此刻,DashboardComponent 中的英雄连接还没有反应。

路由器已经有一个指向 HeroDetailComponent 的路由了, 修改仪表盘中的英雄连接,让它们通过参数化的英雄详情路由进行导航。

Path:"src/app/dashboard/dashboard.component.html (hero links))"

<a *ngFor="let hero of heroes" class="col-1-4"
    routerLink="/detail/{{hero.id}}">
  <div class="module hero">
    <h4>{{hero.name}}</h4>
  </div>
</a>

你正在 *ngFor 复写器中使用 Angular 的插值绑定来把当前迭代的 hero.id 插入到每个 routerLink 中。

HeroesComponent 中的英雄链接

HeroesComponent 中的这些英雄条目都是 <li> 元素,它们的点击事件都绑定到了组件的 onSelect() 方法中。

Path:"src/app/heroes/heroes.component.html (list with onSelect)"

<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>

清理 <li>,只保留它的 *ngFor,把徽章(<badge>)和名字包裹进一个 <a> 元素中, 并且像仪表盘的模板中那样为这个 <a> 元素添加一个 routerLink 属性。

Path:"src/app/heroes/heroes.component.html (list with links)"

<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <a routerLink="/detail/{{hero.id}}">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
    </a>
  </li>
</ul>

你还要修改私有样式表(heroes.component.css),让列表恢复到以前的外观。 修改后的样式表参见本指南底部的最终代码。

移除死代码(可选)

虽然 HeroesComponent 类仍然能正常工作,但 onSelect() 方法和 selectedHero 属性已经没用了。

最好清理掉它们,将来你会体会到这么做的好处。 下面是删除了死代码之后的类。

Path:"src/app/heroes/heroes.component.ts (cleaned up)"

export class HeroesComponent implements OnInit {
  heroes: Hero[];


  constructor(private heroService: HeroService) { }


  ngOnInit() {
    this.getHeroes();
  }


  getHeroes(): void {
    this.heroService.getHeroes()
    .subscribe(heroes => this.heroes = heroes);
  }
}

支持路由的 HeroDetailComponent

以前,父组件 HeroesComponent 会设置 HeroDetailComponent.hero 属性,然后 HeroDetailComponent 就会显示这个英雄。

HeroesComponent 已经不会再那么做了。 现在,当路由器会在响应形如 ~/detail/11 的 URL 时创建 HeroDetailComponent。

HeroDetailComponent 需要从一种新的途径获取要显示的英雄。 本节会讲解如下操作:

  • 获取创建本组件的路由。

  • 从这个路由中提取出 id

  • 通过 HeroService 从服务器上获取具有这个 id 的英雄数据。

先添加下列导入语句:

Path:"src/app/hero-detail/hero-detail.component.ts"

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


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

然后把 ActivatedRoute、HeroService 和 Location 服务注入到构造函数中,将它们的值保存到私有变量里:

Path:"src/app/hero-detail/hero-detail.component.ts"

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

ActivatedRoute 保存着到这个 HeroDetailComponent 实例的路由信息。 这个组件对从 URL 中提取的路由参数感兴趣。 其中的 id 参数就是要显示的英雄的 id

HeroService 从远端服务器获取英雄数据,本组件将使用它来获取要显示的英雄。

location 是一个 Angular 的服务,用来与浏览器打交道。 稍后,你就会使用它来导航回上一个视图。

从路由参数中提取 id

ngOnInit() 生命周期钩子 中调用 getHero(),代码如下:

Path:"src/app/hero-detail/hero-detail.component.ts"

ngOnInit(): void {
  this.getHero();
}


getHero(): void {
  const id = +this.route.snapshot.paramMap.get('id');
  this.heroService.getHero(id)
    .subscribe(hero => this.hero = hero);
}

route.snapshot 是一个路由信息的静态快照,抓取自组件刚刚创建完毕之后。

paramMap 是一个从 URL 中提取的路由参数值的字典。 "id" 对应的值就是要获取的英雄的 id。

路由参数总会是字符串。 JavaScript 的 (+) 操作符会把字符串转换成数字,英雄的 id 就是数字类型。

刷新浏览器,应用挂了。出现一个编译错误,因为 HeroService 没有一个名叫 getHero() 的方法。 这就添加它。

添加 HeroService.getHero()

添加 HeroService,并在 getHeroes() 后面添加如下的 getHero() 方法,它接收 id 参数:

Path:"src/app/hero.service.ts (getHero) "

getHero(id: number): Observable<Hero> {
  // TODO: send the message _after_ fetching the hero
  this.messageService.add(`HeroService: fetched hero id=${id}`);
  return of(HEROES.find(hero => hero.id === id));
}

注:
- 反引号 ( ` ) 用于定义 JavaScript 的 模板字符串字面量,以便嵌入 id。

getHeroes() 一样,getHero() 也有一个异步函数签名。 它用 RxJS 的 of() 函数返回一个 Observable 形式的模拟英雄数据。

你将来可以用一个真实的 Http 请求来重新实现 getHero(),而不用修改调用了它的 HeroDetailComponent。

此时刷新浏览器,应用再次恢复如常。你可以在仪表盘或英雄列表中点击一个英雄来导航到该英雄的详情视图。

如果你在浏览器的地址栏中粘贴了 "localhost:4200/detail/11",路由器也会导航到 id: 11 的英雄("Dr. Nice")的详情视图。

回到原路

通过点击浏览器的后退按钮,你可以回到英雄列表或仪表盘视图,这取决于你从哪里进入的详情视图。

如果能在 HeroDetail 视图中也有这么一个按钮就更好了。

把一个后退按钮添加到组件模板的底部,并且把它绑定到组件的 goBack() 方法。

Path:"src/app/hero-detail/hero-detail.component.html (back button)"

<button (click)="goBack()">go back</button>

在组件类中添加一个 goBack() 方法,利用你以前注入的 Location 服务在浏览器的历史栈中后退一步。

Path:"src/app/hero-detail/hero-detail.component.ts (goBack)"

goBack(): void {
  this.location.back();
}

刷新浏览器,并开始点击。 用户能在应用中导航:从仪表盘到英雄详情再回来,从英雄列表到 mini 版英雄详情到英雄详情,再回到英雄列表。

查看最终代码

AppRoutingModule

Path:"src/app/app-routing.module.ts"

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


    import { DashboardComponent }   from './dashboard/dashboard.component';
    import { HeroesComponent }      from './heroes/heroes.component';
    import { HeroDetailComponent }  from './hero-detail/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

Path:"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 { DashboardComponent }   from './dashboard/dashboard.component';
    import { HeroDetailComponent }  from './hero-detail/hero-detail.component';
    import { HeroesComponent }      from './heroes/heroes.component';
    import { MessagesComponent }    from './messages/messages.component';


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


    @NgModule({
      imports: [
        BrowserModule,
        FormsModule,
        AppRoutingModule
      ],
      declarations: [
        AppComponent,
        DashboardComponent,
        HeroesComponent,
        HeroDetailComponent,
        MessagesComponent
      ],
      bootstrap: [ AppComponent ]
    })
    export class AppModule { }

HeroService

Path:"src/app/hero.service.ts"

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


    import { Observable, of } from 'rxjs';


    import { Hero } from './hero';
    import { HEROES } from './mock-heroes';
    import { MessageService } from './message.service';


    @Injectable({ providedIn: 'root' })
    export class HeroService {


      constructor(private messageService: MessageService) { }


      getHeroes(): Observable<Hero[]> {
        // TODO: send the message _after_ fetching the heroes
        this.messageService.add('HeroService: fetched heroes');
        return of(HEROES);
      }


      getHero(id: number): Observable<Hero> {
        // TODO: send the message _after_ fetching the hero
        this.messageService.add(`HeroService: fetched hero id=${id}`);
        return of(HEROES.find(hero => hero.id === id));
      }
    }

AppComponent

  • Path:"src/app/app.component.html"
    
    <h1>{{title}}</h1>
    <nav>
    <a routerLink="/dashboard">Dashboard</a>
    <a routerLink="/heroes">Heroes</a>
    </nav>
    <router-outlet></router-outlet>
    <app-messages></app-messages>

  • Path:"src/app/app.component.css"
    /* AppComponent's private CSS styles */
    h1 {
      font-size: 1.2em;
      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: #334953;
    }
    nav a:hover {
      color: #039be5;
      background-color: #cfd8dc;
    }
    nav a.active {
      color: #039be5;
    }

DashboardComponent

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

  • Path:"src/app/dashboard/dashboard.component.css"

    import { Component, OnInit } from '@angular/core';
    import { Hero } from '../hero';
    import { HeroService } from '../hero.service';


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


      constructor(private heroService: HeroService) { }


      ngOnInit() {
        this.getHeroes();
      }


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

  • Path:"src/app/dashboard/dashboard.component.css"

    /* DashboardComponent's private CSS styles */
    [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: #3f525c;
      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;
      }
    }

HeroesComponent

  • Path:"src/app/heroes/heroes.component.html"

    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes">
        <a routerLink="/detail/{{hero.id}}">
          <span class="badge">{{hero.id}}</span> {{hero.name}}
        </a>
      </li>
    </ul>

  • Path:"src/app/heroes/heroes.component.ts"

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


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


    @Component({
      selector: 'app-heroes',
      templateUrl: './heroes.component.html',
      styleUrls: ['./heroes.component.css']
    })
    export class HeroesComponent implements OnInit {
      heroes: Hero[];


      constructor(private heroService: HeroService) { }


      ngOnInit() {
        this.getHeroes();
      }


      getHeroes(): void {
        this.heroService.getHeroes()
        .subscribe(heroes => this.heroes = heroes);
      }
    }

  • Path:"src/app/heroes/heroes.component.css"

    /* DashboardComponent's private CSS styles */
    [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: #3f525c;
      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;
      }
    }

HeroesComponent

  • Path:"src/app/heroes/heroes.component.html"

<h2>My Heroes</h2>
<ul class="heroes">
  <li *ngFor="let hero of heroes">
    <a routerLink="/detail/{{hero.id}}">
      <span class="badge">{{hero.id}}</span> {{hero.name}}
    </a>
  </li>
</ul>

  • Path:"ssrc/app/heroes/heroes.component.ts"

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


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


@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
  heroes: Hero[];


  constructor(private heroService: HeroService) { }


  ngOnInit() {
    this.getHeroes();
  }


  getHeroes(): void {
    this.heroService.getHeroes()
    .subscribe(heroes => this.heroes = heroes);
  }
}

  • Path:"src/app/heroes/heroes.component.css"

/* HeroesComponent's private CSS styles */
.heroes {
  margin: 0 0 2em 0;
  list-style-type: none;
  padding: 0;
  width: 15em;
}
.heroes li {
  position: relative;
  cursor: pointer;
  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 a {
  color: #333;
  text-decoration: none;
  position: relative;
  display: block;
  width: 250px;
}


.heroes a:hover {
  color:#607D8B;
}


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

HeroDetailComponent

  • Path:"src/app/hero-detail/hero-detail.component.html"

<div *ngIf="hero">
  <h2>{{hero.name | uppercase}} Details</h2>
  <div><span>id: </span>{{hero.id}}</div>
  <div>
    <label>name:
      <input [(ngModel)]="hero.name" placeholder="name"/>
    </label>
  </div>
  <button (click)="goBack()">go back</button>
</div>

  • Path:"src/app/hero-detail/hero-detail.component.ts"

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';


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


@Component({
  selector: 'app-hero-detail',
  templateUrl: './hero-detail.component.html',
  styleUrls: [ './hero-detail.component.css' ]
})
export class HeroDetailComponent implements OnInit {
  hero: Hero;


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


  ngOnInit(): void {
    this.getHero();
  }


  getHero(): void {
    const id = +this.route.snapshot.paramMap.get('id');
    this.heroService.getHero(id)
      .subscribe(hero => this.hero = hero);
  }


  goBack(): void {
    this.location.back();
  }
}

  • Path:"src/app/hero-detail/hero-detail.component.css"

/* DashboardComponent's private CSS styles */
[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: #3f525c;
  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;
  }
}
HeroesComponent
src/app/heroes/heroes.component.html
src/app/heroes/heroes.component.ts
src/app/heroes/heroes.component.css
content_copy
/* HeroesComponent's private CSS styles */
.heroes {
  margin: 0 0 2em 0;
  list-style-type: none;
  padding: 0;
  width: 15em;
}
.heroes li {
  position: relative;
  cursor: pointer;
  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 a {
  color: #333;
  text-decoration: none;
  position: relative;
  display: block;
  width: 250px;
}


.heroes a:hover {
  color:#607D8B;
}


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

总结

  • 添加了 Angular 路由器在各个不同组件之间导航。

  • 您使用一些 <a> 链接和一个 <router-outlet> 把 AppComponent 转换成了一个导航用的壳组件。

  • 您在 AppRoutingModule 中配置了路由器。

  • 您定义了一些简单路由、一个重定向路由和一个参数化路由。

  • 您在 <a> 元素中使用了 routerLink 指令。

  • 您把一个紧耦合的主从视图重构成了带路由的详情视图。

  • 您使用路由链接参数来导航到所选英雄的详情视图。

  • 在多个组件之间共享了 HeroService 服务。
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号