导航到用户详情

UserDetailComponent 用于显示所选用户的详情。但目前的 UserDetailsComponent 只能在 UsersComponent 的底部看到。

在本章节我们要实现能通过三种途径看到这些详情。

  • 通过在仪表盘中点击某个用户。
  • 通过在用户列表中点击某个用户。
  • 通过把一个“深链接” URL 粘贴到浏览器的地址栏中来指定要显示的用户。

从 UsersComponent 中删除用户详情

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

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

添加用户详情到路由

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

打开 AppRoutingModule(src/app/app-routing.module.ts) 并导入 UserDetailComponent

  1. import { UserDetailComponent } from './user-detail/user-detail.component';

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

  1. { path: 'detail/:id', component: UserDetailComponent },

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

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

完整代码如下:

  1. import { NgModule } from '@angular/core';
  2. import { RouterModule, Routes } from '@angular/router';
  3. import { DashboardComponent } from './dashboard/dashboard.component';
  4. import { UserDetailComponent } from './user-detail/user-detail.component';
  5. import { UsersComponent } from './users/users.component';
  6. const routes: Routes = [
  7. { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  8. { path: 'dashboard', component: DashboardComponent },
  9. { path: 'detail/:id', component: UserDetailComponent },
  10. { path: 'users', component: UsersComponent }
  11. ];
  12. @NgModule({
  13. imports: [ RouterModule.forRoot(routes) ],
  14. exports: [RouterModule]
  15. })
  16. export class AppRoutingModule { }

添加用户详情到 DashboardComponent

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

  1. <a *ngFor="let user of users" class="col-1-4"
  2. routerLink="/detail/{{user.id}}">

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

UsersComponent 中的用户链接

UsersComponent 中的这些用户条目都是

  • 元素,它们的点击事件都绑定到了组件的 onSelect() 方法中。现在需要修改如下:

    1. <h2>我的用户</h2>
    2. <ul class="users">
    3. <li *ngFor="let user of users">
    4. <a routerLink="/detail/{{user.id}}">
    5. <span class="badge">{{user.id}}</span> {{user.name}}
    6. </a>
    7. </li>
    8. </ul>

    你还要修改私有样式表(users.component.css),让列表恢复到以前的外观。修改后的样式代码如下:

    1. /* UsersComponent's private CSS styles */
    2. .users {
    3. margin: 0 0 2em 0;
    4. list-style-type: none;
    5. padding: 0;
    6. width: 15em;
    7. }
    8. .users li {
    9. position: relative;
    10. cursor: pointer;
    11. background-color: #EEE;
    12. margin: .5em;
    13. padding: .3em 0;
    14. height: 1.6em;
    15. border-radius: 4px;
    16. }
    17. .users li:hover {
    18. color: #607D8B;
    19. background-color: #DDD;
    20. left: .1em;
    21. }
    22. .users a {
    23. color: #888;
    24. text-decoration: none;
    25. position: relative;
    26. display: block;
    27. width: 250px;
    28. }
    29. .users a:hover {
    30. color:#607D8B;
    31. }
    32. .users .badge {
    33. display: inline-block;
    34. font-size: small;
    35. color: white;
    36. padding: 0.8em 0.7em 0 0.7em;
    37. background-color: #607D8B;
    38. line-height: 1em;
    39. position: relative;
    40. left: -1px;
    41. top: -4px;
    42. height: 1.8em;
    43. min-width: 16px;
    44. text-align: right;
    45. margin-right: .8em;
    46. border-radius: 4px 0 0 4px;
    47. }

    移除冗余代码

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

    最好清理掉它们,将来你会体会到这么做的好处。 下面是删除了冗余代码之后的类(users.component.ts):

    1. import { Component, OnInit } from '@angular/core';
    2. import { User } from '../user';
    3. import { UserService } from '../user.service';
    4. @Component({
    5. selector: 'app-users',
    6. templateUrl: './users.component.html',
    7. styleUrls: ['./users.component.css']
    8. })
    9. export class UsersComponent implements OnInit {
    10. users: User[];
    11. constructor(private userService: UserService) { }
    12. ngOnInit() {
    13. this.getUsers();
    14. }
    15. getUsers(): void {
    16. this.userService.getUsers()
    17. .subscribe(users => this.users = users);
    18. }
    19. }

    支持路由的 UserDetailComponent

    以前,父组件 UsersComponent 会设置 UserDetailComponent.user 属性,然后 UserDetailComponent 就会显示这个用户。

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

    UserDetailComponent 需要从一种新的途径获取要显示的用户。

    • 获取创建本组件的路由,
    • 从这个路由中提取出 id
    • 通过 UserService 从服务器上获取具有这个 id 的用户数据。

    在 UserDetailComponent 中先添加下列导入语句:

    1. import { ActivatedRoute } from '@angular/router';
    2. import { Location } from '@angular/common';
    3. import { UserService } from '../user.service';

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

    1. constructor(
    2. private route: ActivatedRoute,
    3. private userService: UserService,
    4. private location: Location
    5. ) {}
    • ActivatedRoute 保存着到这个 UserDetailComponent 实例的路由信息。 这个组件对从 URL 中提取的路由参数感兴趣。 其中的 id 参数就是要现实的用户的 id。
    • UserService 从远端服务器获取用户数据,本组件将使用它来获取要显示的用户。
    • location 是一个 Angular 的服务,用来与浏览器打交道。 稍后,你就会使用它来导航回上一个视图。

    从路由参数中提取 id

    修改 user.service.ts 文件,在 ngOnInit() 生命周期钩子 中调用 getUsers(),代码如下:

    1. ngOnInit(): void {
    2. this.getUser();
    3. }
    4. getUser(): void {
    5. const id = +this.route.snapshot.paramMap.get('id');
    6. this.userService.getUser(id)
    7. .subscribe(user => this.user = user);
    8. }

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

    paramMap 是一个从 URL 中提取的路由参数值的字典。 “id” 对应的值就是要获取的用户的 id。

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

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

    添加 UserService.getUser() 方法

    在 UserService 中并添加如下的 getUser() 方法

    1. getUser(id: number): Observable<User> {
    2. this.messageService.add(`UserService: 已经获取到用户 id=${id}`);
    3. return of(USERS.find(user => user.id === id));
    4. }

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

    像 getUsers() 一样,getUser() 也有一个异步函数签名。 它用 RxJS 的 of() 函数返回一个 Observable 形式的模拟用户数据。

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

    运行

    刷新浏览器,应用又恢复正常了。 你可以在仪表盘或用户列表中点击一个用户来导航到该用户的详情视图。

    如果你在浏览器的地址栏中粘贴了 localhost:4200/detail/11,路由器也会导航到 id: 11 的用户(”Way Lau”)的详情视图。

    导航到用户详情 - 图1\

    添加返回按钮

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

    现在我要在在 UserDetail 视图中也添加这样的一个按钮。我们把该后退按钮添加到组件模板的底部,并且把它绑定到组件的 goBack() 方法。

    修改 user-detail.component.html 添加:

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

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

    修改 user-detail.component.ts 添加:

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

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

    你已经满足了在本章开头设定的所有导航需求。

    导航到用户详情 - 图2\