[Day30] Angular 的 Routing

终於来到第三十天了~~~~!不过其实本系列不会在今天结束,我们的前端 app 丑得要命,也还没布署到 VM 上,目前都只在本机自 high,笔者一定不忘初衷,会坚持到把所有的东西都做完!(不过过完 30 天可能会慢一点发文,笔者累到快挂了XD)

正文开始

因为 Angular 是单页应用(single page application, SPA)的框架,在单一个页的框架下要做到换页的效果,Angular 就必须根据使用者目前所巡览的位址来动态替换画面上的原件。截至目前,我们已经有了 4 个 component

  • AppComponent – root component,起始页面
  • IronmanComponent – 者用者资讯页面,目前只提供栏位,没有修改功能,但只要参考 Day29 稍作修改就能正常运作。
  • IronmanListComponent – 使用者清单页面
  • IronmanFormAddComponent - 新增使用者的表单页面

之前都是暂时用注解/解开 selector 来显示不同的页面,今天,我们就来加上 routing(路由),之後就可以直接用 url 来控制页面的显示了。

被冷落已久的 app-routing.module.ts

我们自从在 Day24 提过这个模组之後,就再也没关心过他,现在它终於要派上用场了。这个档案里的程序码也非常简单,只有三个部分

  1. imports – 引用这个档案中的程序需要的东西,例如配对到 routing 的 component
  2. routes – routing 的定义,一开始是个空阵列,等一下我们就要把我们的 routing 规则加到这边
  3. @NgModule() 装饰器 – 管理这个 routing module 的 import, export,比较重要的只有它会把上面的 routes 吃进来,当作管理 routing 的依据。

要加入 routing 规则很简单,只要在上面的 routes 中加入 routing 规则的物件阵列就可以了,例如

const routes: Routes = [
  { path: '', redirectTo: '/ironman-list', pathMatch: 'full' },
  { path: 'ironman/:id', component: IronmanComponent },
  { path: 'ironman-list', component: IronmanListComponent },
  { path: 'ironman-add', component: IronmanFormAddComponent },
  { path: '**', component: IronmanListComponent }
];

上面的 redirectTo 是转址的规则设定,而且我们指定要完全符合才做转跳(pathMatch: 'full'); path: '**' 是 Wildcard Route (万用路由),任何匹配不到的 url 都会显示 IronmanListComponent。请注意万用路由要放最後面,否则它後面的 routing 规则永远不会被用到。

如果我们巡览的 url 有匹配到 routes 的定义,那麽 Angular 就会把指定的 component 拿来塞进 app.component.html 中的 <router-outlet></router-outlet> 位置上,例如

  • mydomain.tw/ 会经过转址,最後显示 IronmanListComponent
  • mydomain.tw/ironman-add 会显示 IronmanFormAddComponent
  • mydomain.tw/ironman/3 会显示 IronmanComponent,并且会带入一个参数 id=3。

设定好 routes 之後,我们就可以把之前写死在 app.component.html 的 component selector(<app-ironman>、<app-ironman-list>)都删掉,留下<router-outlet></router-outlet>就好。尝试在网址的地方输入不同的位址,可以看到我们的 app 会根据 routing 设定更换显示的 component
https://ithelp.ithome.com.tw/upload/images/20210930/201406642HzqL2u7yR.png

取得 routing 中的参数

在上面的范例 routing 规则中,有一个比较特别、带了参数的规则{ path: 'ironman/:id', component: IronmanComponent },它代表了我们的 url 还带了一个名为 id 的参数,我们可以透过 Angular 的 ActivedRoute 类别帮我们取得这个参数,好让我们知道使用者要取得哪一笔资料,作法也非常简单,只要到 component.ts 引用并注入这个类别

// ironman.component.ts
// ...
import { ActivatedRoute } from '@angular/router';
// ...
  constructor(private route: ActivatedRoute, private ironmanService: IronmanService) { }

接着就能用 route.paramMap 来取得 url 所带的参数值

this.route.paramMap.subscribe(map => {
    const uid = map.get('id');
    if (uid) {
        this.ironmanService.getUserDetail(+uid) // 用加号把字串转成数字
          .subscribe(resp => {
            this.userInfo = resp;
          });
    }
});

上面的程序中,this.route.paramMap 也是一个 Observable 物件,所以我们必须订阅它,才能从它里面即时拿到参数的值。取得 id 的值之後用 id 查询使用者资料,然後换订阅 ironmanService 回传的 Observable,最後才把取得的资讯放到 userInfo 变数,让内嵌系结帮我们显示资讯。

使用 routerLink 建立连结

虽然我们可以用手 key 网址直接到我们想要的页面,但是我们当然还是要提供方便一点的连结啦,现在我们就来介绍最简单的 routerLink。routerLink 这个 Directive 可以让宿主元素(host element)直接变成一个可以点的连结,所以只要用这个 directive,我们就不用限定一定要用 HTML 的 a tag,也不用自己写 JS/TS 用点击事件作超连结。不过,虽然它可以点、可以转跳页面,但是滑鼠指标不会变手指,所以要自己改一下 XD

<!-- app.component.html -->
<span routerLink="/ironman-list" routerLinkActive="router-link-active"
    class="ironman-link-item">
    List
</span>
<span routerLink="/ironman-add" routerLinkActive="router-link-active" 
    class="ironman-link-item">
    Create
</span>
/* app.component.css */
.ironman-link-item {
  cursor: pointer;
  margin: 0px 10px 0px 10px;
}

.router-link-active {
  font-weight: 700;
  font-size: large;
  text-decoration: underline;
}

上面的程序中,routerLink 这个 Directive 设定当这宿主容器被点击时,要巡览到哪一个路径,而 routerLinkActive 则是用来指定当这个 routing 被启用的时候,这个宿主元素要套用什麽样的 CSS 样式。

用 Router 巡览到其他页面

除了使用静态的页面连结,我们也可以透过程序来让我们的 app 转跳到其他页面,例如我们之前使用者列表的页面中,当使用者的 verified 属性是 1/true 的时候,我们会在表格中显示一个「编辑」按钮,我们可以让这个按钮触发一个事件,再用 Router 类别的 navigate() 方法,帮我们转跳到编辑这个使用者的页面

<!-- ironman-list.component.html -->
<!-- ... -->
    <tr *ngFor="let user of userListFromApi">
      <!-- ... -->
      <td>
        <button *ngIf="user.verified == 1" (click)="onEditUser(user.userId)">编辑</button>
      </td>
    </tr>
<!-- ... -->
// ironman-list.component.ts
// ...
import { Router } from '@angular/router';
// ...
// 在建构式注入 Router
constructor(private ironmanService: IronmanService, private router: Router) { }
// ...
onEditUser(id: number) {
    this.router.navigate(['/ironman', id]);
}
// ...

改完~收工~我们的网站虽然丑,但是基本该有的功能都有得差不多了。
本系列不会在此结束,会坚持到把 Day01 提到的东西都做完(但可能会比发比较慢,已经累到要吃药了XD),竟请各位邦友持续关注!


<<:  [iT铁人赛Day30]铁人赛最终回 心得分享

>>:  [Day 16] v-text和v-html

常见漏洞披露(CVE)及常见弱点枚举(CWE)

图片来源:Bitsorbit CVE列表是指特定产品或系统中已识别的漏洞。与未发现或未知的漏洞相比...

[Day 15] Leetcode 138. Copy List with Random Pointer (C++)

前言 今天选择的是top 100 liked,并与linked list相关的题目:138. Cop...

Backtrader - 新增策略

以下内容皆参考 Backtrader 官网 昨天使用了 backtrader 将 shioaji 的...

[Day 7] 餐前浓汤 pt.4-资料内文取得及储存

上一篇我们提到了如何观察并且取出我们要的资料 也成功地把资料取出来了 这一篇我们将要对资料做最後的加...

[Day 13] 资料增强 — 我全都要.jpg

Collaboration and augmentation are the foundation...