選單
GSS 技術部落格
在這個園地裡我們將從技術、專案管理、客戶對談面和大家分享我們多年的經驗,希望大家不管是喜歡或是有意見,都可以回饋給我們,讓我們有機會和大家對話並一起成長!
若有任何問題請來信:gss_crm@gss.com.tw
6 分鐘閱讀時間 (1148 個字)

Angular #24 - 深入 Service [1]

shutterstock_198004562
  • 先前開發 Dashboard 的時候,大部份取得資料的邏輯都寫在 controller,但那是因為當時會的還不多,所以不讓情境變得太複雜,而事實上:

    • 取得資料的邏輯是可以共用
    • 如果把取資料也放在 controller 中,那它的職責就不夠單一了
  • Service 可以分為幾類

    • 可注入類型
      • 就是典型的 Angular Service
      • 遵循著相依注入的設計模式
    • 不可注入的類型
      • 只是一般的 javascript 物件,提供一些方便的 function
      • 通常是用匯入的(日後會再提怎麼匯)
    • Helper/Utils
      • 使得操作某個 Component 或某個功能變得更容易
      • 舉例來說管理 Alert 的 Service
    • 專注於資料處理的 Service
      • 可以在不同的功能間共用資料
      • 一個常見的範例就是使用者的登入資訊(e.g. token/sessionid)
  • 接下來將透過一個股票交易的程式來示範如何建立 Service

    • 這支程式會用到多個 Service

      • 一個 Service 負責管理帳戶資訊
      • 一個 Service 負責管理系統相關的設定檔,比方說某支 API 的連結
      • 一個 Service 負責以 localStorage 為底提供存取資的功能,這主要是為了讓系統不會因為畫面重整就所有的資料都歸零了
    • 首先,先去下方的連結取得一個初步的框架

      • Github Link
      • 在 git clone 之後執行以下指令,確定程式可以跑得起來
          npm install
          ng serve
      • 打開瀏覽器造訪 localhost:4200,看一下雛型
    • 那麼就來建立第一個 Service 吧

      • 這個 Service 會管理帳戶相關的問題,例如持有多少股票,帳戶中還有多少資金…等

      • 不過打開 VSCode 一看 account.service.ts 已經存在了,如果是自己從頭建起的專案的話,就會是透過以下的指令:

          # 完整版
          ng generate service account
        
          # 懶人版
          ng g s account
      • 既然有了 Service,自然是要實作它,程式碼如下:

          import { Injectable } from '@angular/core'
          import { Stock } from './stocks.model';
        
          const defaultBalance: number = 10000;
        
          @Injectable()
          export class AccountService {
            private _balance: number = defaultBalance;
            private _cost: number = 0;
            private _value: number = 0;
            private _stocks: Stock[] = [];
        
            get balance(): number { return this._balance; }
            get cost(): number { return this._cost; }
            get value(): number { return this._value; }
            get stocks(): Stock[] { return this._stocks; }
        
            purchase(stock: Stock): void {
              stock = Object.assign({}, stock);
              if (stock.price < this.balance) {
                this._balance = this.debit(stock.price, this.balance);
                stock.cost = stock.price;
                this._cost = this.credit(stock.price, this.cost);
                stock.change = 0;
                this._stocks.push(stock);
                this.calculateValue();
            }
        
            sell(index: number): void {
              let stock = this.stocks[index];
              if (stock) {
                this._balance = this.credit(stock.price, this.balance);
                this._stocks.splice(index, 1);
                this._cost = this.debit(stock.cost, this.cost);
                this.calculateValue();
            }
        
            init() {
        
            }
        
            reset() {
              this._stocks = [];
              this._balance = defaultBalance;
              this._value = this._cost = 0;
            }
        
            calculateValue() {
              this._value = this._stocks
                                .map(stock => stock.price)
                                .reduce((a, b) => { return a + b }, 0);
            }
            private debit(amount: number, balance: number): number {
              return (balance * 100 - amount * 100) / 100;
            }
            private credit(amount: number, balance: number): number {
              return (balance * 100 + amount * 100) / 100;
            }
          }
        • 首先看到的是 Injectable 這個 Decorator,所有要被註冊在 DI 的 Service 都必須加上這個裝飾,當然…用之前別忘了 import
        • 再來還 import 了一個 interface Stock,就是一個交換資料的型態
        • 這個 service 主要有四個屬性:
          • balance(帳戶餘額):預設一開始有1萬塊,購買股票時會減少,賣出股票時會增加
          • cost(支出): 購買股票時會增加,賣出股票時會減少
          • value(價值): 持有股票的總價值
          • stocks(持有的股票)
        • 承上,這些屬性都有 getter,實際的值放在 private 的屬性
        • 這個 service 對外公開兩個方法,買/賣股票
        • 此外,還有兩個 private 的方法分別是借(debit)與貸(credit)
        • 最後是真實人生不存在的人生重來槍 reset,跟計算持有股票總值的 calculateValue,init .. 之後再說
      • 實作了 Service,就是要使用它

        • 先打開 AppComponent 的 controller,並填入以下內容:
            import { Component, OnInit, OnDestroy } from '@angular/core';
            import { AccountService } from './services/account.service';
            @Component({
              selector: 'app-root',
              templateUrl: './app.component.html',
              styleUrls: ['./app.component.css']
            })
            export class AppComponent implements OnInit, OnDestroy {
                constructor(private accountService: AccountService) {}
                // ...(略)
                reset(): void {
                    this.accountService.reset();
                }
                // ...(略)
            }
          • 重點就是在建構子注入 service
          • 當然也要正確的 import
          • 最後就是定義 reset 這個方法,並轉由 AccountService 處理
      • 前面有提到,要成為 service 的類別都必須要加上 Injectable(),但那只是其中一個條件,另一個條件是要在 app.module.ts 註冊這個 service

        • 打開 app.module.ts,然後找到 providers,把 service 加進去:
            // ...(略)
            @NgModule({
              declarations: [
                AppComponent,
                // ...(略)
              ],
              imports: [
                BrowserModule,
                // ...(略)
              ],
              providers: [
                // ...(略)
                AccountService,
                // ...(略)
              ],
              bootstrap: [AppComponent]
            })
            export class AppModule { }
          • AppModule 目前所認知的唯一作用就是會有一個 NgModule 負責註冊相關的元件,而其中的 providers 就是我們所關注的
          • AccountService 被放到 providers 陣列中時,Angular 才會在編譯的時候處理到它,並設定相關的 DI 機制
  • 小結

    • Service 是主要商業邏輯或是可共用的處理的所在地
      • 兩個要素,Injectableproviders
      • 下一篇將定義一些其他的 Component 來使用 AccountService
Angular #25 - 深入 Service [2]
Angular # 23 - 深入 Components [6]

相關文章

 

評論

尚無評論
已經注冊了? 這裡登入
Guest
2025/05/22, 週四

Captcha 圖像