第 8 天 迈出 RxJS 小小的一步|pipe、operators

前情提要

使用了 AsyncPipe 管道来取得所有英雄资料後,我们要在英雄资讯页面,传递参数来取得特定的英雄资料。在先前撰写的程序码中,我们知道如何透过 ActivedRoute 提供的状态快照 snapshot 来取得路由参数,再进一步透过 HttpClient 的 get 请求英雄资料。这看来是两个步骤,但对於 RxJS 来说,它可以是一个资料流。今天,就让我们玩起来吧!

但还是先声明一下,这里只会很扼要地说明语法完成了什麽事,和一些比较容易理解的观念,想要深入地了解 RxJS 这边 Mike 大大有本好书

重构取得个别英雄资料的方法

首先,我们先在 HeroDetailComponent 来重构 getHero() 方法,原本的程序码如下:

export class HeroDetailComponent implements OnInit {

 hero: Hero | null = null;

  constructor(
    private http: HttpClient,
    private route: ActivatedRoute
  ) { }

  ngOnInit(): void {
    const heroId = this.route.snapshot.paramMap.get('id')!;
    this.getHero(heroId);
  }

  private getHero(id: string): void {
    this.http.get<Hero>(`api/heroes/${id}`).subscribe((selectedHero) => {
      this.hero = selectedHero;
    })
  }
}

我们调整成:

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

import { Observable } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';

import { Hero } from '../shared/models/hero.model';

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

  hero$: Observable<Hero>;

  constructor(
    private http: HttpClient,
    private route: ActivatedRoute
  ) {
    this.hero$ = this.getHero();
  }

  ngOnInit(): void {}

  private getHero(): Observable<Hero> {
    return this.route.paramMap.pipe(
      map((params) => params.get('id')),
      switchMap((heroId) => this.http.get<Hero>(`api/heroes/${heroId}`))
    );
  }

}

我们聚焦在 getHero() 方法完成的事情,也就是 hero$ 属性到底会拿到什麽资料。当然我们知道,最後在 hero.detail.component.html 会使用 AsyncPipe 管道来订阅资料并将资料显示在画面上:

<mat-card *ngIf="hero$| async as hero">
  <mat-card-header>
    <div mat-card-avatar></div>
    <mat-card-title>{{ hero.name }}</mat-card-title>
  </mat-card-header>
  (略)
  </mat-card-content>
</mat-card>

这个 hero 资料是经过种种过程才拿到画面上的,而这个过程就是「资料流」。也就是说,当订阅的时候,这道流就会往目的地流动;在流动过程中,可以对这些资料进行加工,比如,筛选资料(攻击力低的英雄不要来啊、男英雄退散啊...)。好比水流经过了一个管道,在管道中进行了各种加工,最後可能变成可乐吧!

这个管道就是 pipe,而加工的方法就是操作符(operators),让我们看看,在英雄资料流通过管道的时候,使用了哪些操作符进行加工:

  private getHero(): Observable<Hero> {
    return this.route.paramMap.pipe(
      map((params) => params.get('id')),
      switchMap((heroId) => this.http.get<Hero>(`api/heroes/${heroId}`))
    );
  }

map 的意图就是转换资料,在这里我们将收到的 params,转换成只取用 id (params.get('id')),并将转换的资料继续送往下个操作符 switchMap。

switchMap 会取消前一个订阅,并发出新的可观察者(observable),意思是,不会继续将 heroId 往下传递,而是产生新的可观察者(在这里,发送了 http 请求来取得特定的英雄资料)。

虽然我们不会将 heroId 继续往下传递,但可以使用它来做为发出 http 请求的参数。

这样,就将原先拆成两个步骤完成的功能,改写成一个资料流。其他就同先前所做的,在画面上使用 AsyncPipe 来订阅这个资料。

程序码更简洁了,但好像哪里怪怪的?

那股莫名的异样感是什麽呢?

我们是重构了 getHero(),但取得资料的细节仍旧写在 HeroDetailComponent 里。不是说「与资料互动的事情交给 HeroService 服务就好了」吗?

没错,接着我们会完成这件事。但先想一想:嗯?所以应该要在 HeroService 依赖注入 ActivedRoute 来取得路由参数吗(heroId)?这样就可以把 getHero() 整段程序搬到 HeroService 去了!太棒了!

是吗?

或许不是这样。诚然,取得特定英雄资料,必须有 heroId 作为参数。但是,取得 heroId 的方法可不是只能透过路由参数。记得吗?一开始我们是透过属性系结的方法来取得英雄资料(包含了 heroId),也就是说,属性系结在某种情境下完全是可行的方案,当然,你可能还想得到其他取得方式。

作为 HeroService 服务,getHero() 方法要处理的事情是接受参数 heroId,并用它来发送 http 请求取得特定的英雄资料,至於 heroId 怎麽来,这个方法可能不需要负责。

所以 heroId 应该由注入此服务使用 getHero() 的地方来提供,也就是 HeroDetailComponent。

让我们针对下列档案进行调整:

hero.service.ts

  getHero(heroId: string): Observable<Hero> {
    return this.http.get<Hero>(`api/heroes/${heroId}`);
  }

而将 hero-detail.component.ts 的 getHero() 调整为:

  private getHero(): Observable<Hero> {
    return this.route.paramMap.pipe(
      map((params) => params.get('id')),
      switchMap((heroId) => this.heroService.getHero(heroId!))
    );
  }

也就是将 switchMap 操作符产生的新 observable,从原先的发送 http 请求,改为使用 HeroService 的 getHero()。这样我们就完成了重构,程序正常运行:)

今天的程序码已推上 Github


<<:  Day9 跟着官方文件学习Laravel-登入验证

>>:  [DAY-09] 放宽更多限控制 决策不必上级核准

Chapter2 - Canvas动画(II)用国中数学拆解Ease-out和Ease-in

如何计算每一侦的位移 首先我们改写一下昨天的格式,还记得昨天我们用到的是这样的写法: cursorX...

【如何设计软件 ? 】领域驱动设计 | 4 层架构 + 3 类物件

有想法 x 也有做法 大纲 前人的专案 领域驱动设计 理论与分层结构 领域层的物件 专案架构实作 有...

【Day6】Props和States之间到底是什麽关系!? 怎麽传怎麽用咧..? o_O ||

这篇要来谈React的states跟Props States跟Props可以看作是React里面的A...

Day 2 为什麽要学Compose UI?

嗨!大家好,我是Teng: 今年的疫情蛮严重的,希望大家都过得安好, 希望疫情快点过去,能回到一些线...

[DAY2] 适者生存下的环境

前言 哈罗,我是神天,一个不专业的废物.w. 欢迎大佬在下面用力的鞭打我这个不成熟的废物 昨天忘记提...