iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0
Modern Web

angular專案開發指南系列 第 12

製作 Angular Http 通訊服務

  • 分享至 

  • xImage
  •  

前言

大多數前端應用都要透過 HTTP 協議與伺服器通訊,才能下載或上傳資料並訪問其它後端服務。Angular 給應用提供了一個簡化的 HTTP 客戶端 API,也就是 @angular/common/http 中的 HttpClient 服務類別。

在使用 HTTPClientModule 之前,你應該對下列內容有基本的瞭解:

  1. TypeScript 程式設計
  2. HTTP 協議的用法
  3. Angular 的應用設計基礎,就像 Angular 基本概念中描述的那樣
  4. Observable 相關技術和運算子。參見可觀察物件部分。

我的 HTTP 服務

使用 HttpClient,就要先匯入 Angular 的 HttpClientModuleHttpClient 服務為所有工作都使用了可觀察物件返回一個 Observable,使用 ES7 的 Async/Await 機制,以便在業務邏輯層面直接調用,就算為了完成一個功能而需要調用許多 API,也不怕陷入回調地獄。

  • HttpClient 返回一個 Observable,可配合 pipe 享受 Rxjs/operator 所帶來的便利。
  • Async/Await 使用避免回調地獄。

包裝過後的 Http 服務大致規格如下

p51

例如 Http GET 服務

p52

使用方式如下

async httpService() {
    let botType = await this.httpService.httpGET('/robot/botType');   // GET取得 botType
    let botData = await this.httpService.httpGET('/robot/botData?botType=' + botType);    // GET取得 botData
    this.httpService.httpPOST(botData); // POST寫入 botData
}

在實際專案 my-app 中新增 Http 服務

一個 Angular 專案通常會有一些公用元件或服務的模組,目的是為了方便管理維護與重複使用,容易匯入到其他中專案或模組中。

舉個例子 Angular 都會有的模組,像是 Shared Module,就可以讓你在 Angular 專案的功能模組中,一次性引入公用元件不必到功能模組中個別引入。

本篇要做的是先建立一個公用的 Http 服務,對 Angular 的 HttpClient 服務進行再包裝 (使用 async/await)。

STEP 1. 新增一個資料夾 services/

> cd /src/app/services
> ng g s http

CREATE src/app/services/http.service.spec.ts (347 bytes)
CREATE src/app/services/http.service.ts (133 bytes)

STEP 2. src\app\services\http.service.ts 建立服務方法,

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { lastValueFrom } from 'rxjs';
import { tap } from 'rxjs/operators';

/**
 * interface of API Response
 *
 * @export
 * @interface IHttpResponse
 */
export interface IHttpResponse {
    data?: object | [];
}

/**
 * Http Service
 *
 * @export
 * @class HttpService
 */
@Injectable({
    providedIn: 'root',
})
export class HttpService {
    /**
     * @ignore
     */
    constructor(private httpClient: HttpClient) {}

    /**
     * ### httpGET
     * > HttpClient Member - Http GET
     * >
     * > Observable to Promise(for Async/Await)
     *
     * @param {*} args[] - (args[0] : url)
     * @returns {Promise<object>}
     * @memberof HttpService
     */
    async httpGET(...args: any[]): Promise<object> {
        const apiUrl = args[0] || '';

        try {
            const result$ = this.httpClient.get(apiUrl).pipe(
                tap((resp: IHttpResponse) => {
                    return resp;
                })
            );

            return await lastValueFrom(result$);
        } catch (err) {
            return Promise.reject(err);
        }
    }
}

由於 Rxjs toPromise() deprecated 因此 toPromise() 改成 lastValueFrom()

STEP 3. 根模組 src\app\app.module.ts 引入 HttpClientModule

由於 Http providedInroot 所以我們在根模組引入 HttpClientModule

// Angular HttpClientModule
import { HttpClientModule } from '@angular/common/http';

// Home
import { HomeComponent } from './home/home.component';

@NgModule({
    declarations: [AppComponent, HomeComponent],
    imports: [
        BrowserModule,
        AppRoutingModule,
        BrowserAnimationsModule,
        HttpClientModule,
        NbThemeModule.forRoot({ name: 'default' }),
        NbEvaIconsModule,
        ...NEBULAR_ROOT,
        ...NEBULAR_ALL,
    ],
    providers: [],
    bootstrap: [AppComponent],
})
export class AppModule {}

STEP 4.HomeComponent 注入 HttpService 服務到 src\app\home\home.component.ts

import { Component, OnInit } from '@angular/core';
import { NbSidebarService } from '@nebular/theme';

// 使用公用服務 HttpService
import { HttpService } from '/services/http.service';

@Component({
    selector: 'app-home',
    templateUrl: './home.component.html',
    styleUrls: ['./home.component.scss'],
})
export class HomeComponent implements OnInit {
    constructor(private sidebarService: NbSidebarService, private httpService: HttpService) {}

    toggle() {
        this.sidebarService.toggle(true);
        return false;
    }

    async ngOnInit(): Promise<void> {
        const resp = await this.httpService.httpGET('http://localhost:3000/myapp-hello-mockdata');
        console.log(resp);
    }
}

範例使用上一篇做的 Mock API myapp-hello-mockdata


實際執行結果

src\app\home\home.component.ts

async ngOnInit(): Promise<void> {
    const resp = await this.httpService.httpGET('http://localhost:3000/myapp-hello-mockdata');
    console.log(resp);
}

範例的執行結果如下

p53

查看 Compodoc 註解網站得知目前專案結構如下,

p54


關於服務的 @Injectable

服務的 @Injectable providedIn 選項確定哪些注入器將提供可注入。

@Injectable({
    providedIn: 'root',
})

providedIn 可接受的參數有,

providedIn?: Type<any> | 'root' | 'platform' | 'any' | null
  • 'root':在大多數應用程式中是指應用程式級注入器。
  • 'platform':由頁面上所有應用程式共享的特殊單例平臺注入器。
  • 'any':在每個延遲載入的模組中提供一個唯一實例,而所有熱切載入的模組共享一個實例。
  • null:等效於 undefined 。可注入物不會在任何範圍內自動提供,必須新增到 @NgModule@Component@Directive的 providers 陣列中。

使用 @Injectable()providedIn 屬性優於 @NgModule() 的 providers 陣列,因為使用 @Injectable() 的 providedIn 時,優化工具可以進行搖樹優化,從而刪除你的應用程式中未使用的服務,以減小捆綁套件尺寸。

新建服務都會自動 providedInroot,這樣可以不用像以前去維護 providers 陣列


結論

本篇學習使用 Angular 建一個 Http 服務,包裝一個適用於 (async/await) 的 Http 服務,providedIn 到根模組 AppModule 後再把服務注入到 HomeComponent 的流程。

接下來讓我們完善 Http 服務的 CRUD API 再來實作租戶管理功能吧。


參考

使用 HTTP 與後端服務進行通訊

Rxjs toPromise() deprecated

Hierarchical injectors

Angular 建立 Injector 的流程

Injectable


上一篇
讓前後端獨立開發的假資料系統 - Json Server
下一篇
實作租戶管理頁面(1)
系列文
angular專案開發指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言