添加应用内导航
本文参考自Angular中文网,原文链接:https://angular.cn/tutorial/toh-pt5
路由
有一些《英雄之旅》的新需求:
- 添加一个仪表盘视图。
- 添加在英雄列表和仪表盘视图之间导航的能力。
- 无论在哪个视图中点击一个英雄,都会导航到该英雄的详情页。
- 在邮件中点击一个深链接,会直接打开一个特定英雄的详情视图。
完成时,用户就能像这样在应用中导航:
添加 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
数组中。
执行结果如下,我这里报错是因为已经有 AppRoutingModule
了,应该是创建项目的时候VS Code自动帮我创建了。
生成的文件是这样的:
src/app/app-routing.module.ts (generated)
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [
CommonModule
],
declarations: []
})
export class AppRoutingModule { }
把它替换为如下代码:
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
数组中使用它:
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
配置它:
src/app/app-routing.module.ts
imports: [ RouterModule.forRoot(routes) ],
这个方法之所以叫 forRoot()
,是因为你要在应用的顶层配置这个路由器。 forRoot()
方法会提供路由所需的服务提供者和指令,还会基于浏览器的当前 URL 执行首次导航。
接下来,AppRoutingModule
导出 RouterModule
,以便它在整个应用程序中生效。
src/app/app-routing.module.ts (exports array)
exports: [ RouterModule ]
添加路由出口 RouterOutlet
打开 AppComponent
的模板,把 <app-heroes>
元素替换为 <router-outlet>
元素。
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
数组中。
试试看
你的 CLI 命令应该仍在运行吧。
ng serve
浏览器应该刷新,并显示着应用的标题,但是没有显示英雄列表。
看看浏览器的地址栏。 URL 是以 / 结尾的。 而到 HeroesComponent
的路由路径是 /heroes
。
在地址栏中把 /heroes
追加到 URL 后面。你应该能看到熟悉的主从结构的英雄显示界面。
添加路由链接 (routerLink)
理想情况下,用户应该能通过点击链接进行导航,而不用被迫把路由的 URL 粘贴到地址栏。
添加一个 <nav>
元素,并在其中放一个链接 <a>
元素,当点击它时,就会触发一个到 HeroesComponent
的导航。 修改过的 AppComponent
模板如下:
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
的路由。 routerLink
是 RouterLink
指令的选择器,它会把用户的点击转换为路由器的导航操作。 它是 RouterModule
中的另一个公共指令。
刷新浏览器,显示出了应用的标题和指向英雄列表的链接,但并没有显示英雄列表。
点击这个链接。地址栏变成了 /heroes
,并且显示出了英雄列表。
从本文最下面的最终代码中把私有 CSS 样式添加到 app.component.css
中,可以让导航链接变得更好看一点。
添加仪表盘视图
当有多个视图时,路由会更有价值。不过目前还只有一个英雄列表视图。
使用 CLI 添加一个 DashboardComponent
:
ng generate component dashboard
CLI 生成了 DashboardComponent
的相关文件,并把它声明到 AppModule
中。
把这三个文件中的内容改成这样:
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>
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));
}
}
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 位英雄,也就是说只返回四个顶层英雄(第二,第三,第四和第五)。
src/app/dashboard/dashboard.component.ts
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes.slice(1, 5));
}
添加仪表盘路由
要导航到仪表盘,路由器中就需要一个相应的路由。
把 DashboardComponent
导入到 AppRoutingModule
中。
src/app/app-routing.module.ts (import DashboardComponent)
import { DashboardComponent } from './dashboard/dashboard.component';
把一个指向 DashboardComponent
的路由添加到 AppRoutingModule.routes
数组中。
src/app/app-routing.module.ts
{ path: 'dashboard', component: DashboardComponent },
添加默认路由
当应用启动时,浏览器的地址栏指向了网站的根路径。 它没有匹配到任何现存路由,因此路由器也不会导航到任何地方。 <router-outlet>
下方是空白的。
要让应用自动导航到这个仪表盘,请把下列路由添加到 AppRoutingModule.Routes
数组中。
src/app/app-routing.module.ts
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
这个路由会把一个与空路径“完全匹配”的 URL 重定向到路径为 ‘/dashboard’ 的路由。
浏览器刷新之后,路由器加载了 DashboardComponent
,并且浏览器的地址栏会显示出 /dashboard
这个 URL。
把仪表盘链接添加到壳组件中
应该允许用户通过点击页面顶部导航区的各个链接在 DashboardComponent
和 HeroesComponent
之间来回导航。
把仪表盘的导航链接添加到壳组件 AppComponent
的模板中,就放在 Heroes
链接的前面。
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
。
src/app/app-routing.module.ts (import HeroDetailComponent)
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
然后把一个参数化路由添加到 AppRoutingModule.routes
数组中,它要匹配指向英雄详情视图的路径。
src/app/app-routing.module.ts
{ path: 'detail/:id', component: HeroDetailComponent },
path
中的冒号(:
)表示 :id
是一个占位符,它表示某个特定英雄的 id
。
此刻,应用中的所有路由都就绪了。
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
的路由了, 修改仪表盘中的英雄连接,让它们通过参数化的英雄详情路由进行导航。
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()
方法中。
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
属性。
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
属性已经没用了。
最好清理掉它们,将来你会体会到这么做的好处。 下面是删除了死代码之后的类。
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
的英雄数据。
先添加下列导入语句:
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
服务注入到构造函数中,将它们的值保存到私有变量里:
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()
,代码如下:
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
参数:
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()
方法。
src/app/hero-detail/hero-detail.component.html (back button)
<button (click)="goBack()">go back</button>
在组件类中添加一个 goBack()
方法,利用你以前注入的 Location
服务在浏览器的历史栈中后退一步。
src/app/hero-detail/hero-detail.component.ts (goBack)
goBack(): void {
this.location.back();
}
刷新浏览器,并开始点击。 用户能在应用中导航:从仪表盘到英雄详情再回来,从英雄列表到 mini 版英雄详情到英雄详情,再回到英雄列表。
各页面效果:
Dashboard页面
英雄列表页面
英雄详情
查看最终代码
本页讨论的代码文件如下。
AppRoutingModule、AppModule 和 HeroService
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 {}
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 { }
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
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>
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
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>
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));
}
}
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
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>
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);
}
}
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
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>
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();
}
}
src/app/hero-detail/hero-detail.component.css
/* HeroDetailComponent's private CSS styles */
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;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #ccc;
cursor: auto;
}
小结
- 添加了 Angular 路由器在各个不同组件之间导航。
- 使用一些
<a>
链接和一个<router-outlet>
把AppComponent
转换成了一个导航用的壳组件。 - 在
AppRoutingModule
中配置了路由器。 - 定义了一些简单路由、一个重定向路由和一个参数化路由。
- 在
<a>
元素中使用了routerLink
指令。 - 把一个紧耦合的主从视图重构成了带路由的详情视图。
- 使用路由链接参数来导航到所选英雄的详情视图。
- 在多个组件之间共享了
HeroService
服务。