HTTP を用いて非同期で情報を取得する例

ここでは HTTP リクエストを使い、バックエンドのサーバーから非同期で情報を取得する基本的な例を示します。

具体的にここで何をするか、初めに説明します。

まずは、別途ウェブサーバー上にスクリプトをおいて、単純な JSON 文字列を返すようにしておきます。

内容的には車のメーカー (=Toyota)、名前(=Prius)、年代(=2017) の三つのデータです。

JSON 文字列の作り方はどうやってもいいですが、とりあえず PHP でこんなスクリプトを書きました。

<?php

$car = [
	'make' => 'Toyota',
	'name' => 'Prius',
	'year' => 2017
];

echo json_encode( $car );

?>

これを Angular のアプリケーションで受け取り表示することを考えます。

データ取得中はスピナーが回り・・・

データが取れたら、文字を表示します。

ただこれだけです。簡単ですね。

モデルクラスを作成

車を表すモデルクラス Car (src/app/car.ts) を次のように作ります。

export class Car {
  constructor(
    public make: string,
    public name: string,
    public year: number
  ){}
}

constuctor 内の public パラメータ

ここで constructor 内で public パラメータを使っています。Angular ではコンストラクタの役割といえば DI のため、 ということになります。しかし、TypeScript としては public パラメータは自動的に公開されたプロパティを作るときの簡便な方法となります。

Car を表示するコンポーネント

Car オブジェクトをサーバーから取得して返すサービス CarService は後で作りますが、その前に、「CarService を使う」側を先に説明します。

ここでは AppComponent をそのまま書き換えてしまいます。

テンプレート src/app/app.component.html は次の通りです。

<p *ngIf="isLoading"><i class="fa fa-spinner fa-spin fa-lg"></i></p>
<p *ngIf="!isLoading">
  <span *ngIf="isFulfilled"><i class="fa fa-car"></i> {{car.make}} {{car.name}} {{car.year}}</span>
  <span *ngIf="!isFulfilled"><i class="fa fa-warning"></i> Error occurred. {{err}}</span>
</p>

データ取得中のプロパティ isLoading と取得成功を表すプロパティ isFulfilled で、データ取得中・取得完了後、 取得成功・失敗の状態を制御しています。

コンポーネント src/app/app.component.ts は次の通りです。

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { CarService } from './car.service';
import { Car } from './car';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [CarService]
})
export class AppComponent implements OnInit {

  car: Car;
  err: string;

  isLoading = false;
  isFulfilled = false;

  constructor(private carService:CarService){}

  ngOnInit(){

    this.isLoading = true;

    this.carService.getCar()
      .then(
        car => {
          this.car = car;
          this.isLoading = false;
          this.isFulfilled = true;
        }
      )
      .catch(
        err => {
          this.err = err;
          this.isLoading = false;
          this.isFulfilled = false;
        }
      );
  }
}

OnInit のイベントにて、データの取得を開始しています。

データは providers に登録している CarProvider のオブジェクトを使います。 このオブジェクトは、コンストラクタのパラメータで渡されています。DI です。

CarProvider の getCar なるメソッドで車オブジェクトを取得していますが、ここが非同期で実行されます。 すなわち、getCar を呼んで直ちに結果が取得できるのではなくて、呼び出してから、バックエンドでリモートサーバーに問い合わせを行い、 結果がとれたときに、結果の成功・失敗に応じた処理を行います。

getCar メソッドは Car 型の Promise を返すメソッドです。

Promise を使えば成功時に then にて Car 型のオブジェクト car を引数とする関数をコールバックでき、 失敗時には失敗理由を受け取り catch にて関数をコールバックできます。

CarService のクライアント (使う側) は以上です。

HTTP 上で非同期でデータを取得する

それでは、CarService をみてみましょう。

import {Http, Response} from '@angular/http';
import {Injectable} from '@angular/core';
import {Car} from './car';

import 'rxjs/add/operator/toPromise';

@Injectable()
export class CarService {

  constructor(private http:Http){
  }

  getCar() : Promise<Car> {
    return this.http
      .get("http://url to server")
      .toPromise()
        .then(this.handleData)
        .catch(this.handleFailure);
  }

  handleData(response: Response) : Promise<Car> {
    let json = response.json();
    let car: Car;

    car = new Car(json.make, json.name, json.year);

    return Promise.resolve(car);
  }

  handleFailure(error: any) : Promise<string> {
    let msg : string;
    console.log(error);
    msg = error.toString();
    return Promise.reject(msg);
  }
}

まずコンストラクタで HTTP クラスのオブジェクトを受け取ります。

HTTP オブジェクトは get メソッド、post メソッドなどを持っていて、 HTTP クライアントとして使えます。 デフォルトではバックエンドで XMLHttpRequest を使います。

HTTP オブジェクトの get メソッドや post メソッドなど、サーバーへの問い合わせを実行するメソッドは Response の Observable (Observable<Response>) を返します

今回の CarService サービス内では Observable を Promise に変換しています。これを行うための toPromise オペレータは rxjs/add/operator/toPromise から取り込みます。

Promise を返すメソッドとなれば話は単純になります。使う側では上でみたように then で正常時のコールバック、catch で異常発生時のコールバックを指定するだけです。

catch ではバックエンドでの失敗を受けとるだけではなく、then コールバック関数内での失敗も catch します。このため、必ずしも Response のエラーを捕らえるだけではないので、 catch のパラメータの型は any にしておき、いろんなものをキャッチできるようにしています。

以上で HTTP クラスを用いて、サーバーに問い合わせを行いデータを取得する単純な例について説明しました。

サーバーにアクセスした時に、次のように Access-Control-Allow-Origin ヘッダーがないとか、リクエスト送信元が許可されないなどのエラーが発生する場合があります。

この場合、アクセスを全面的に許可するなら Apache なら次のように .htaccess を構成すれば OK です。

Header set Access-Control-Allow-Origin "*"

また、この変更でウェブサーバーの 500 エラーになるなら、mod_headers モジュールが有効でない可能性がありますので、 次を試してみてください。

$ sudo a2enmod headers
$ sudo service apache2 restart