SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

次世代Webアプリケーションフレームワーク「Angular」の活用

「Angular 2」のHTTPクライアント機能で非同期HTTP通信を使いこなす

次世代Webアプリケーションフレームワーク「Angular」の活用 第6回


  • X ポスト
  • このエントリーをはてなブックマークに追加

HTTP通信処理をサービスに分離

 ここまでは、HTTP通信処理をコンポーネントに直接実装しましたが、コードのメンテナンス性や可読性を考えると、通信処理をコンポーネントから分離しておくのが望ましいです。以下では、リスト3の実装をもとに、通信処理をサービスに切り出して、依存性注入でコンポーネントに提供する方法を説明します。サービスと依存性注入については過去記事も参考にしてください。

 まず、リスト4のようなサービスクラスを作ります。

リスト4 HTTP通信サービスクラス(angular-003-service/app/httpexec.service.ts)
import { Injectable } from "@angular/core";
import { Http, Response, Headers, RequestOptions } from "@angular/http";
import { Observable } from "rxjs/Observable";

// RxJSのメソッド ...(1)
import "rxjs/add/observable/throw";
import "rxjs/add/operator/map";
import "rxjs/add/operator/catch";

@Injectable()
export class HttpExec {
  // コンストラクター ...(2)
  constructor(private http:Http) {
  }
  // URLとPOSTデータを引数にHTTP POSTを実行
  doJsonPost(url:string, postData:Object):Observable<string> {
    let headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded" });
    let options = new RequestOptions({ headers: headers });
    // postメソッドでObservableを返す ...(3)
    return this.http.post(url, postData, options)
                    .map(this.extractData)
                    .catch(this.handleError);
  }
  // APIレスポンスからレスポンス文字列を取得 ...(4)
  private extractData(res: Response) {
    // 戻り値を文字列で取得
    let body = res.text();
    // nullの場合は空文字を返却
    return body || ""
  }
  // エラーハンドル処理 ...(5)
  private handleError (error: Response | any) {
    let errMsg: string;
    // Responseオブジェクトからエラーを取得してエラー文言を作成
    if (error instanceof Response) {
      errMsg = error.status + ":" + error.statusText;
    }
    // errorがResponseオブジェクトでない場合は、可能な限りエラー文言を取得
    else {
      errMsg = error.message ? error.message : error.toString();
    }
    // エラー文言を戻す
    return Observable.throw(errMsg);
  }
}

 (1)で、JavaScriptのRxライブラリ(RxJS)からthrow、map、catchメソッドを参照しています(利用法は後述します)。サービスクラスのコンストラクター(2)でHttpクラスのインスタンスを受け取り、POST通信を実行するdoJsonPostメソッド内で利用します(3)。

 (3)で実行されているmapメソッドは、Observableから取得した値を変換するメソッドで、ここではResponseオブジェクトからレスポンス文字列を取得する処理(4)を指定します。また、エラー発生時の処理を指定するcatchメソッドでは、エラー文言をObservable.throwメソッドで戻す処理(5)を指定しています。(4)と(5)では、nullのレスポンス文字列や、Responseクラス以外のエラーオブジェクトに対応する異常系の実装を行っています。

 リスト4のサービスを使うコンポーネントの実装はリスト5になります。

リスト5 HTTP通信サービスを利用する実装(angular-003-service/app/app.component.ts)
// コンストラクター ...(1)
constructor(private httpExec:HttpExec) {
}
// ボタン押下時の処理
private onClickPost() {
  // POSTデータ
  let postData = "title=" + encodeURIComponent("POSTテストデータ") +
    "&value=" + encodeURIComponent("HTTP POSTで送信したデータです。");
  // doJsonPostメソッドの戻り値をsubscribeして、内容を取得 ...(2)
  this.httpExec.doJsonPost("http://localhost:3030/post-test-data", postData)
  .subscribe(
    // 成功時処理 ...(3)
    resString => {
      this.resString = resString;
    },
    // エラー時処理 ...(4)
    errMsg => {
      console.error(errMsg);
    }
  );
}

 リスト4で実装したHttpExecサービスのインスタンスを(1)のコンストラクターで受け取り、(2)でサービスのdoJsonPostメソッドを実行します。戻り値のObservableオブジェクトから、subscribeメソッドで結果を受け取ります。成功時処理(3)の引数resStringにはリスト4(4)で作成したレスポンス文字列が、エラー時処理(4)の引数errMsgにはリスト4(5)で作成したエラー文言が渡されます。

 リスト4、5のようにHTTP通信処理を切り出しても、切り出す前(図2)とまったく同じく動作します。

 このように、HTTP通信処理固有の実装をサービスに切り出して、コンポーネントから分離しておけば、例えばサービスの実装を別のものに置き換えてもコンポーネント側に影響を及ぼさないようにできます。

[参考]ObservableとPromise

 ここまで説明したように、Angular 2のHTTPクライアントは、非同期処理を実現するRxとObservableに依存しています。一方で、Promiseと呼ばれる非同期処理の実装方法が、ECMAScript 2015から利用可能になりました。

 Angular 2のHTTPクライアントが返却するObservableオブジェクトは、toPromiseメソッドでPromiseオブジェクトに変換できます。リスト4、5をもとに、ObservableをPromiseに変換する例(angular-004-promise)を、ダウンロードできるサンプルに含めています。

JSONPでWebAPIにアクセス

 Angular 2のHttpクラスによるHTTP通信は、いわゆる「同一オリジンポリシー」により、外部Webサーバーへのアクセスが制限されます。そのため、さまざまなWebページからのアクセスが想定されるWebAPIでは、この制限を回避できるJSONP(JSON with padding)が利用されます。

 以下では、Angular 2のHTTPクライアントでJSONPを利用する例として、Yahoo!ショッピングの商品検索APIで、商品をキーワード検索するサンプルを紹介します。このサンプルでは、指定したキーワードにマッチした製品の写真・名称・価格がリスト表示されます。

図3 JSONPのWebAPIで商品検索(angular-005-jsonp)
図3 JSONPのWebAPIで商品検索(angular-005-jsonp)

 JSONPの機能を提供するAngular 2のモジュールはJsonpModuleです。リスト1でHttpModuleを設定したのと同じように、ルートモジュールにJsonpModuleを設定します。

 WebAPIを実行するサービスの実装はリスト6のようになります。

リスト6 WebAPI実行サービス(angular-005-jsonp/app/yahooapi.service.ts)
@Injectable()
export class YahooAPI {
  // アプリケーションID ...(1)
  APP_ID = "<アプリケーションIDを設定>";
  // APIのURL ...(2)
  API_URL
    = "http://shopping.yahooapis.jp/ShoppingWebService/V1/json/itemSearch";
  // コンストラクター ...(3)
  constructor(private jsonp:Jsonp) {
  }
  // Yahoo!ショッピングの商品検索APIを実行
  searchShopping(query:string):Observable<Object> {
    // 検索パラメーターを設定 ...(4)
    let params = new URLSearchParams();
    params.set("appid", this.APP_ID);   // アプリケーションID
    params.set("query", query);               // 検索文字列
    params.set('callback', 'JSONP_CALLBACK'); // JSONPのコールバック名
    // 検索パラメーターをリクエストオプションに設定 ...(5)
    let options = new RequestOptions({search: params});
    // jsonp.getメソッドでAPIを実行するObservableを返す ...(6)
    return this.jsonp.get(this.API_URL, options)
                     .map(this.extractData)
                     .catch(this.handleError);
  }
  // APIレスポンスからJSONオブジェクトを取得 ...(7)
  private extractData(res: Response) {
    // 戻り値をJSONオブジェクトして取得して返却
    let body = res.json();
    // nullの場合は空オブジェクトを返却
    return body || { };
  }
(handleErrorは略)
}

 (1)にはYahoo!の開発者サイトで取得できるアプリケーションIDを設定します。(2)はAPIのURLです。(3)のコンストラクターで、JSONPの処理を実行するJsonpクラスのインスタンスを受け取ります。

 APIを実行するには、まずWebAPIに与える検索パラメーターをURLSearchParamsオブジェクトに設定します(4)。ここでは、アプリケーションIDと検索文字列、JSONPのコールバック名を指定します。APIに指定するパラメーターはYahoo!のドキュメントも参照してください。(5)では、リクエストオプションを表すRequestOptionsオブジェクトを生成して、searchプロパティに(4)のURLSearchParamsオブジェクトを設定しています。

 JSONPでAPIを実行する処理は(6)です。JsonpクラスはHttpクラスのサブクラスなので、Httpクラスと同一形式で記述できます。JSONP固有の処理は内部で行われるため、実装時にJSONPであることを意識する必要はありません。

 APIの戻り値をJSONオブジェクトで引き渡すため、(7)のextractDataメソッドで、ResponseオブジェクトのjsonメソッドでJSONオブジェクトを取得して返却します。エラーハンドル処理handleErrorの処理はリスト4と同様です。

 コンポーネント側では依存性注入でYahooAPIクラスのインスタンスを受け取って、searchShoppingメソッドで検索を実行します。詳細はサンプルコードを参照してください。

まとめ

 本記事では、Angular 2が提供するHTTPクライアント機能を利用して、Webサーバーと非同期通信を行う方法を説明しました。Angular 2のHttpModule/JsonpModuleに含まれるHttpクラスやJsonpクラスと、Reactive ExtensionsのObservableやPromiseの仕組みを組み合わせて、通信の非同期処理を簡潔に記述できます。

参考資料

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
次世代Webアプリケーションフレームワーク「Angular」の活用連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト  吉川 英一(ヨシカワ エイイチ)

WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS Twitter: @yyamada(公式)、@yyamada/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/10009 2017/04/24 11:02

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング