今天教大家如何實作抽換Service的功能,
這樣的寫法可以讓專案更增加彈性,實作細節是等到實際使用時才知道,
若是沒有實作出新的Service,還是可以使用原來的Service提供服務。
我希望我的票卷頁面能夠提供匯率計算的功能
因為很多旅遊票卷通常是以旅遊當地的幣別做計價
但來網站訂票的人可能會希望看到換算為台幣為多少。
因此,我打算做一個ExchangeRateComponent
並且使用一個基本的ExchangeRateService做匯率計算。
這個ExchangeRateService是取用內部的JSON來取抓幣別匯率資料。
但是未來我希望能抽換ExchangeRateService,
改用APIExchangeRateService(使用線上讀取資料庫設定的幣別匯率資料)
而且不需修改ExchangeRateComponent的程式碼。
使用AngularDoc Extension
在core目錄上右鍵選單ng g component
在輸入框輸入exchange-rate
會自動產生ExchangeRateComponent
並在CoreModule的declaration
的區塊加好此元件
因為TicketModule要使用,
所以還需要手動在exports
區塊加上ExchangeRateComponent做導出
接下來製作ExchangeRateComponent的元件內容
in exchange-rate.component.html
<span>
<button class="btn btn-info" (click)="calculate()">匯率計算</button>
<span *ngIf="resultMoney">{{to}}:{{resultMoney}}</span>
</span>
in exchange-rate.component.ts
import { Component, OnInit,Input,Output,EventEmitter } from '@angular/core';
@Component({
selector: 'app-exchange-rate',
templateUrl: './exchange-rate.component.html',
styleUrls: ['./exchange-rate.component.scss']
})
export class ExchangeRateComponent implements OnInit {
@Input()
from:string; //原始幣別
@Input()
to:string = 'NTD'; //欲換算幣別
@Input()
sourceMoney:number; //原始金額
resultMoney:number; //計算後金額
constructor() { }
ngOnInit() {
}
calculate(){
//TODO:稍後會使用ExchangeService來取得匯率資料
this.resultMoney = 0;
}
}
使用AngularDoc Extension
在core目錄上右鍵選單ng g service
,在輸入框輸入exchange-rate
會自動產生ExchangeRateService
並在CoreModule的providers
的區塊加好此服務
其服務的功能是取用JSON寫好的匯率幣別資料。
in exchange-rate.service.ts
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
@Injectable()
export class ExchangeRateService {
constructor(private http:Http) { }
getExchangeRate(){
return this.http
.get("api/exchange-rate.json")
.map((res:Response) => res.json());
}
}
in api/exchange-rate.json
[
{
"key":"JPY-NTD",
"value":0.3
}
]
in exchange-rate.component.ts
修改constructor注入ExchangeRateService
constructor(private exService: ExchangeRateService) { }
另外,改寫calculate()
方法
/**
* 取得ExchangeRateService的幣別匯率資料來做匯率換算
*/
calculate() {
this.exService
.getExchangeRate()
.subscribe(
(data:any[]) => {
let rate:number = 0;
// forEach找到符合的幣別匯率資料
data.forEach((item)=>{
let toRefKey = this.from + "-" + this.to;
if(item.key === toRefKey){
rate = item.value;
}
});
this.resultMoney = this.sourceMoney * rate;
});
}
in ticket-list.component.html
<app-exchange-rate [from]="ticket.currency" [sourceMoney]="ticket.price">
</app-exchange-rate>
使用ExchangeRateService抓取的結果
假定線上資料庫有一個每日會抓取最新匯率的匯率資料表,
然後我們可以使用自己制作的API取得匯率資料,例如今天是2016-12-28,那麼就使用http://localhost:3000/rates/2016-12-28
查詢今天的匯率資料。
我們在ticket的目錄下新增一個APIExchangeRateService,來使用此API。
in api-exchange-rate.service.ts
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
@Injectable()
export class APIExchangeRateService {
url:string = "http://localhost:3000/rates"
constructor(private http:Http) { }
getExchangeRate(){
var today = new Date().toISOString().slice(0, 10);
let queryUrl = this.url + "/" + today;
return this.http
.get(queryUrl)
.map((res:Response) => res.json());
}
}
接下來,我們在TicketModule設定使用此Service,
使用APIExchangeRateService取代掉原來的ExchangeRateService
providers:[
TicketService,
{
provide:ExchangeRateService,useClass: APIExchangeRateService
}
]
我們完全不需要修改ExchageRateComponent這個元件。
它就能使用APIExchangeRateService。
這樣的寫法可以讓專案更增加彈性,實作細節是等到實際使用時才知道,若是沒有實作出APIExchangeRateService,還是可以使用原來的ExchangeRateService提供服務。
使用APIExchangeRateService抓取的結果
在寫service的時候,如果遇到function屬於Observable的,我會盡量讓subscriber在註冊後取道的值是不用在經過加工的. 意思是指
/**
* 取得ExchangeRateService的幣別匯率資料來做匯率換算
*/
calculate() {
this.exService
.getExchangeRate()
.subscribe(
(data:any[]) => {
let rate:number = 0;
// forEach找到符合的幣別匯率資料
data.forEach((item)=>{
let toRefKey = this.from + "-" + this.to;
if(item.key === toRefKey){
rate = item.value;
}
});
this.resultMoney = this.sourceMoney * rate;
});
}
這段subscribe後的動作,是否可以直接將尋找匯率的程式包在map裡,這樣子,subscribe時回傳的資料,就可以直接使用了. 範例如下
/**
* 取得ExchangeRateService的幣別匯率資料來做匯率換算
*/
calculate() {
this.exService
.getExchangeRate()
.map((data:any[]) => {
let rate:number = 0;
// forEach找到符合的幣別匯率資料
data.forEach((item)=>{
let toRefKey = this.from + "-" + this.to;
if(item.key === toRefKey){
rate = item.value;
}
})
return rate;
})
.subscribe(
(rate:number) => {
this.resultMoney = this.sourceMoney * rate;
});
}
這樣子的寫法,就更乾淨了,下一階段的重構, 就是把map裡的部分抽出來變成一個單獨的function之類的.
提供給你參考看看.
你提供的寫法更好耶,這有點算是FP的思維嗎?
map用來「取匯率」
subscribe則是「用匯率」
這樣程式碼更有彈性,發生改A錯B的機率會更小!
有點類似那樣子的觀念吧. 我只是覺得這樣子的寫法可以讓程式更容易維護.
其實這樣子的寫法,也有其他的使用情境
getSource(){
return this.http.get('....').map(res=> res.json()).share();
}
personName$ = this.getSource().map(data=>{
return `${data.firstName} ${data.lastName}`;
});
commecnts$ = this.getSource().map(data=>{
return data.comments;
});
頁面
<h2>{{ this.personName$ | async }}</h2>
<ul>
<li *ngFor="comment of this.comments$ | async">
{{ comment }}
</li>
</ul>