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

Angular #27 - 深入 Service [4]

shutterstock_198004562
  • HttpInterceptor

    • 用來攔截所有的 requestresponse

      • 可以用來設定所有 request 加上 Authorization 的 header
      • 也可以統一處理特定 response,像是 4xx/5xx 系列的錯誤
      • 就目前的範例專案的使用情境是從 StocksService 取回最新資料的時候,要同時去更新 AccountService 的資料
    • 要怎麼建立 HttpInterceptor?

        ng g s services/interceptor
      • 沒錯,你沒看錯,interceptor 在 Angular 也只是 service 的一種
      • 承上,跟一般的 service 不同的是它需要實作 HttpInterceptor 這個介面並定義 intercept 這個方法的內容
    • 接著我們就來填入內容吧:

        import { Injectable } from '@angular/core';
        import { Observable } from 'rxjs/Observable';
        import 'rxjs/add/operator/do';
        import { HttpEvent, HttpInterceptor, HttpResponse, HttpHandler, HttpRequest } from '@angular/common/http';
        import { AccountService } from './account.service';
        import { Stock } from './stocks.model';
        import { ConfigService } from './config.service';
      
        @Injectable()
        export class StocksInterceptor implements HttpInterceptor {
      
          constructor(private accountService: AccountService) { }
      
          intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            const request = req.clone();
            request.headers.append('Accept', 'application/json');
      
            return next.handle(request).do(event => {
              if (event instanceof HttpResponse && event.url === ConfigService.get('api')) {
                const stocks = event.body as Array<Stock>;
                let symbols = this.accountService.stocks.map(stock => stock.symbol);
                stocks.forEach(stock => {
                  this.accountService.stocks.map(item => {
                    if (stock.symbol === item.symbol) {
                      item.price = stock.price;
                      item.change = ((stock.price * 100) - (item.cost * 100)) / 100;
                    }
                  });
                });
                this.accountService.calculateValue();
                return stocks;
              }
            });
          }
      
        }
      • Import 很多很多東西
        • Injectable: 畢竟還是 service
        • Observable: interceptor 也是回傳 Observable,畢竟它是攔截了 response,既然 response 是 Observable 那被攔截也不該變
        • HttpEvent: HttpSentEvent | HttpHeaderResponse | HttpResponse<T> | HttpProgressEvent | HttpUserEvent<T> 的其中一種
        • HttpHandler: 很重要,是拿來告訴 Angular 說,「喂,我攔截完幹完大事了」的物件,如果沒有它,request/response 就會斷掉拋錯
        • 剩下的…顯而易見,不多作解釋
      • 實作 intercept,有幾點要留意的:
        • 這個方法是必定得實作的,否則會編譯不過
        • 承上,所有的 HTTP request 發生時都會觸發 intercept 這個方法,它有兩個參數,第一個是 request 本身,第二個是一個 HttpHandler
        • 當我們攔截到請求後,如果要對 request 作任何的加工,都必須先 clone,因為此時的 HttpRequest 是 immutable 的
        • 接著加了 Accept: application/json 到 clone 出來的 request 的 header 中,並呼叫 httpHandler 的 handle(request).do,這時 Angular 就會把 request 發出,並拿到一個 event
        • 承上,判斷 event 為 HttpResponse 且 URL 是股票資訊的 API,我們就可以同步更新 AccountService 存的資訊了
    • 定義好 Interceptor,要在哪注入呢? 其實注入在哪都不對,正確的方式是讓 AppModule 認得這它

      • 修改 app.module.ts 如下

           // ...(略)
          import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
        
          // ...(略)
        
          @NgModule({
            // ...(略)
            providers: [
                // ...(略)
                {
                  provide: HTTP_INTERCEPTORS,
                  useClass: StocksInterceptor,
                  multi: true
                },
                // ...(略)
             ],
            bootstrap: [AppComponent]
          })
          export class AppModule { }
        • 這次的 provider 比較特別,是一種 multiprovider,有看到那個 multi: true 吧!只要有加上這個的 provider 就會共同被視為一個整體,因為通常 interceptor 不會只有一個,例如以下的範例:
            export const interceptorProviders = 
               [
                { provide: HTTP_INTERCEPTORS, useClass: RequestTimestampService, multi: true },
                { provide: HTTP_INTERCEPTORS, useClass: AjaxBusyIdentifierInterceptorService, multi: true },
                { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorService, multi: true },
                { provide: HTTP_INTERCEPTORS, useClass: XML2JsonInterceptorService, multi: true },
                { provide: HTTP_INTERCEPTORS, useClass: ErrorNotifierService, multi: true },
                { provide: HTTP_INTERCEPTORS, useClass: RetryInterceptorService, multi: true }    
            ];
        • HTTP_INTERCEPTORS,會被當作 token 把一群的 interceptor 註冊成一個整體(還記得 DI 是怎麼註冊並找到 service 的嗎?)
  • Helper Services

    • 主要處理一些非商業邏輯的共用性質的演算法,比方說排序, 解析...等

      • 這些 Helper 大部份不會一開始就存在,而是在開發的過程中慢慢發現,似乎很多地方都有用到相似的邏輯的時候,再抽出來

      • 跟一般的 service 差不多,都是為了讓 component 中的邏輯不要過於複雜,使其能專注在 UI 的呈現上

      • 以下的範例就是一個方便管理 localStorage 的 Helper

          import { Injectable } from '@angular/core';
        
          @Injectable()
          export class LocalStorageService {
            get(key: string, fallback: any) {
              let value = localStorage.getItem(key);
              return (value) ? JSON.parse(value) : fallback;
            }
        
            set(key: string, value: any) {
              localStorage.setItem(key, JSON.stringify(value));
            }
          }
        • 很…單純的 service,get 就是看 localStorage 有沒有值,沒有的話就回傳 fallback
        • 承上,set ...就是把傳入的值轉成 JSON 再存
  • 小結:

    • Service 就到這告一個段落了
      • 本篇的重點在於 Interceptor
      • Service 實際上要怎麼分類其實是一門玄學,只要能夠分得清楚,邏輯說得通,符合 Object Oriented Programming 相關的原則就好
      • 硬幹 Service 之前,先看一下官方的 API 文件中是否已經有既有的 Service 可以沿用了,否則只是 reinvent the wheel
×
Stay Informed

When you subscribe to the blog, we will send you an e-mail when there are new updates on the site so you wouldn't miss them.

Angular #28 - 深入 Routing [1]
Angular #26 - 深入 Service [3]

相關文章

 

評論

尚無評論
已經注冊了? 這裡登入
2026年4月04日, 星期六

Captcha 圖像