「英雄之旅」已经可以浏览完整的英雄列表,并透过路由参数来取得特定的英雄资料,达到换页浏览细节资讯的功能,可以说,我们初步完成了英雄的资料查询。在我们进一步完成对资料的增、删、改之前,先要来重构程序码:将元件中与资料互动的相关逻辑移走——让元件专注在展示资料上,而关於与资料互动的逻辑,我们将新增服务(service)来统一管理。
在一般的情境下,一个服务应该是整个 app 共用的。如此一来,不管这个服务注入在哪个元件中(被哪个元件、功能使用),都能够确保它的状态(资料)是一致的。因此,我们在 shared 资料夹下新增 services 资料夹来管理 service 档案,并在此资料夹执行指令:
ng g s hero // g for generate; s for service
档案目录如下:
src
⌞app
⌞ shared
⌞ models
⌞ services
hero.service.ts
打开 hero.service.ts
档案:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class HeroService {
constructor() { }
}
可以注意到服务其实也是个类别(class),此外,@Injectable
装饰器是非常重要的,因为:
private http: HttpClient
(当然还需要在上面 import HttpClient):import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class HeroService {
constructor(
private http: HttpClient
) { }
}
@Injectable({
providedIn: 'root' // 应用程序级服务,确保注入此服务的地方,状态(资料)是一致的。
})
目前在 App 中,拥有两个与资料互动的地方:
先在 hero.service.ts
来撰写取得所有英雄资料的方法:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Hero } from './../models/hero.model';
@Injectable({
providedIn: 'root'
})
export class HeroService {
constructor(
private http: HttpClient
) { }
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(`api/heroes`);
}
我们主要关注 getHeroes():
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(`api/heroes`);
}
可以看到,这个方法会回传的资料型态是 Observable<Hero[]>
,Observable 是 RxJS 的术语,意思是可观察的。在 Angular,使用 HttpClient 的 get 方法回传的资料预设都是 Observable。
接着,在 hero-list.component.ts
依赖注入 HeroService,并调整取得所有英雄资料的方法:
import { HeroService } from './../shared/services/hero.service';
import { Component, OnInit } from '@angular/core';
import { Hero } from './../shared/models/hero.model';
@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
styleUrls: ['./hero-list.component.css']
})
export class HeroListComponent implements OnInit {
heroList: Hero[] = [];
constructor(
private heroService: HeroService
) {}
ngOnInit(): void {
this.heroService.getHeroes().subscribe((heroList) => {
this.heroList = heroList;
})
}
}
程序码几乎相同,但取得资料的种种逻辑被移到 HeroService 了——我们不必在 HeroListComponent 知道这些资讯。
除了上面的使用方法之外,因为 HttpClient 回传的资料是可观察的(Observable),因此我们可以使用下列的方法来实作。首先调整 hero-list.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { HeroService } from './../shared/services/hero.service';
import { Hero } from './../shared/models/hero.model';
@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
styleUrls: ['./hero-list.component.css']
})
export class HeroListComponent implements OnInit {
heroList$: Observable<Hero[]>;
constructor(
private heroService: HeroService
) {
this.heroList$ = this.heroService.getHeroes();
}
ngOnInit(): void {}
}
我们将原本的属性 heroList: Hero[]
调整为 heroList$: Observable<Hero[]>
,$
是 RxJS 的惯用写法,代表这是一个可以被观察(订阅)的属性——这里也可以注意到,原本在 hero-list.component.ts
中的订阅(subscribe)资料的行为消失了。因为,我们将使用 Angular 提供的 Async 管道来订阅它,让我们在画面档案 hero-list.component.html
来完成这件事:
<div class="hero-container" *ngIf="heroList$ | async as heroList">
<mat-card class="hero-item" *ngFor="let hero of heroList">
(略)
</mat-card>
</div>
在 div 上我们使用了 *ngIf 指令,放置 heroList$ 属性并使用 async 管道,这个管道将订阅 heroList$,也就是 HeroService 服务的 getHeroes()
回传的资料:
getHeroes(): Observable<Hero[]> {
return this.http.get<Hero[]>(`api/heroes`);
}
当有接收到资料时,*ngIf 就会为 true,以下的所有标签就会建立出来(显示在画面上)。同时,得到的资料被指派为变数 heroList (as heroList)。这与原先的程序码是相同的(*ngFor="let hero of heroList"
),因此其他程序码并不需要改动。
藉由将 getHeroes()
移到 HeroService 并改为 RxJS 的写法,我们的程序码更为精简。而另外一个方法 getHero(heroId)
将更深入地使用到 RxJS 的 operator,这是我们明天要来完成的事:「极简 RxJS 使用方法」。打完这几个字我自己都抖了起来,赶快睡觉压压惊 :P。
程序码已推上 Github。
<<: [ Day 07 ] Class Component
前言 Sass对现在前端来说已经是不可或缺的技术,不仅用起来顺手,如果熟悉之後切版的速度将会加快非常...
前言 前一天我们在 K8s Cluster 建置好了 ArgoCD 服务,今天就来实际操作看看,透过...
取自 Artifact Austin: Leaving Pixels Behind - Todd ...
昨天我们虽然各别把 .java 跟 .class 分类到不同的资料夹, 但长久下来还是不够的,我们还...
今天内容跟我原本预期的,出入满多QQ 因为先前有买一的书,叫「黑客大揭秘 近源渗透测试」, 本来打算...