Angular2でAjaxを使ってデータを取得する(Httpクライアント)

スポンサーリンク

画面遷移のないシングルページのWebアプリケーションでは、Ajaxが使えないと話にならないですよね。
Angular2ではHttpクライアントが機能として含まれています。

今回はAngular2でのHttpクライアントでデータを取得する方法を紹介します。

Httpクライアント

これから全部で3つのコードを紹介します。

今回例で使うソースコードは公式リファレンスで紹介されているものです。
Ajaxで取得したデータをリストで表示します。

app/toh.component.ts

import { Component }         from '@angular/core';
import { HTTP_PROVIDERS }    from '@angular/http';
import { HeroListComponent } from './hero-list.component';
import { HeroService }       from './hero.service';

@Component({
    selector: 'my-toh',
    template: 
        <h1>Tour of Heroes</h1>
        <hero-list></hero-list>
    ,
    directives: [HeroListComponent],
    providers:  [
        HTTP_PROVIDERS,
        HeroService,
    ]
})
export class TohComponent { }

ではまずはじめに、ベースとなるリスト表示のコンポーネントを定義します。

なんかいっぱい書いてあって、よくわからないですよねw
大丈夫です、ひとつずつ見ていきましょう。

まず、 HTTP_PROVIDERS をimportします。これは、Httpクライアントを使うための処理が定義されています。
@Componentproviders プロパティに指定しておきます。

実際にHttpクライアントで通信する機能は、HeroService に任せます。これも providers に追加します。
これは、Angular2のDependency Injection(依存的注入) という機能で外部化しています。
(Dependency Injectionについては後日詳しく紹介します。ここでは、処理を使いまわせる仕組みと考えていれば大丈夫です。)

Angular2ではコンポーネント志向の設計なので、処理を独立させることが簡単にできます。
コードを見ると、すっきりしていてストレスフリーですね。

app/toh/hero-list.component.ts (class)

次に、先ほど定義した TohComponent のリスト部分を定義します。
TohComponent内の <hero-list></hero-list> の部分がこれにあたります。

import { Component, OnInit } from '@angular/core';
import { Hero}               from './hero';
import { HeroService }       from './hero.service';

@Component({
    selector: 'hero-list',
    template: </span>
        <span class="o"><</span><span class="nx">h1</span><span class="o">></span><span class="nx">Tour</span> <span class="nx">of</span> <span class="nx">Heroes</span> <span class="p">()</span><span class="o"><</span><span class="sr">/h1</span><span class="err">>
</span>        <span class="o"><</span><span class="nx">h3</span><span class="o">></span><span class="na">Heroes</span><span class="p">:</span><span class="o"><</span><span class="sr">/h3</span><span class="err">>
</span>        <span class="o"><</span><span class="nx">ul</span><span class="o">></span>
            <span class="o"><</span><span class="nx">li</span> <span class="o">*</span><span class="nx">ngFor</span><span class="o">=</span><span class="s2">"let hero of heroes"</span><span class="o">></span>
                <span class="p">{{</span><span class="nx">hero</span><span class="p">.</span><span class="nx">name</span><span class="p">}}</span>
            <span class="o"><</span><span class="sr">/li</span><span class="err">>
</span>        <span class="o"><</span><span class="sr">/ul</span><span class="err">>
</span>        <span class="nx">New</span> <span class="nx">hero</span> <span class="na">name</span><span class="p">:</span>
        <span class="o"><</span><span class="nx">input</span> <span class="err">#</span><span class="nx">newHeroName</span> <span class="o">/></span>
        <span class="o"><</span><span class="nx">button</span> <span class="p">(</span><span class="nx">click</span><span class="p">)</span><span class="o">=</span><span class="s2">"addHero(newHeroName.value); newHeroName.value=''"</span><span class="o">></span>
            <span class="nx">Add</span> <span class="nx">Hero</span>
        <span class="o"><</span><span class="sr">/button</span><span class="err">>
</span>        <span class="o"><</span><span class="nx">div</span> <span class="kr">class</span><span class="o">=</span><span class="s2">"error"</span> <span class="o">*</span><span class="nx">ngIf</span><span class="o">=</span><span class="s2">"errorMessage"</span><span class="o">></span><span class="p">{{</span><span class="nx">errorMessage</span><span class="p">}}</span><span class="o"><</span><span class="sr">/div</span><span class="err">>
</span>    <span class="err">
    ...
})
export class HeroListComponent implements OnInit {

    constructor (private heroService: HeroService) {}

    errorMessage: string;
    heroes: Hero[];

    ngOnInit() { this.getHeroes(); }

    getHeroes() {
        this.heroService.getHeroes()
            .subscribe(
                heroes => this.heroes = heroes,
                error =>  this.errorMessage = <any>error
            );
    }

}

初期化のタイミングで、getHeroes メソッドで heroService.getHeroes() を実行して、Http通信をしています。
ngOnInit についてはAngular2のLifecycle Hooksを理解するを参考にしてください。

getHeroes メソッド内の subscribe() というのはRxJSの機能です。今はHttpの処理が終わった後に、
実行されるものだと思っていれば問題ありません。

Httpでデータを取得した後は、HeroListComponent自身の heroes メンバーに保存しています。
データが保存されれば、<li *ngFor="let hero of heroes"></li>heroes に自動的に反映されて、リストが生成されます。

app/toh/hero.service.ts

最後にHttpサービス本体です。

import { Injectable }     from '@angular/core';
import { Http, Response } from '@angular/http';
import { Hero }           from './hero';
import { Observable }     from 'rxjs/Observable';

@Injectable()
export class HeroService {

    constructor (private http: Http) {}

    private heroesUrl = 'app/heroes';  // URL to web api

    getHeroes (): Observable<Hero[]> {
        return this.http.get(this.heroesUrl)
            .map(this.extractData)
            .catch(this.handleError);
    }

    // レスポンスデータの整形処理
    private extractData(res: Response) {
        if (res.status < 200 || res.status >= 300) {
            throw new Error('Bad response status: ' + res.status);
        }
        let body = res.json();
        return body.data || { };
    }

    // エラー処理
    private handleError (error: any) {
        // In a real world app, we might send the error to remote logging infrastructure
        let errMsg = error.message || 'Server error';
        console.error(errMsg); // log to console instead
        return Observable.throw(errMsg);
    }
}

はじめに、@Injectable で他のコンポーネントからimportできるように宣言しています。

Httpを使うためには、import { Http } でHttpオブジェクトをimportする必要があります。
そして、constructorの引数で、Httpオブジェクトをクラスのhttpメンバーに追加します。
※TypeScriptでは、constructorの引数でprivatepublic などのキーワードをつけると、そのクラスのメンバーに追加されます。

ここでは、getHeroes メソッドに注目しましょう。
http.get()get メソッドでの通信を行います。第1引数にはURLを指定します。
返り値はRxJSのObservableというデータになりますが、今は理解できなくても大丈夫です。
ObservableのmapメソッドでAjaxでのレスポンスをチェック、json化しています。
RxJSは理解が難しいので、ここではレスポンスデータを整形していると認識できればOKです。
catch() ではエラーハンドリングをしています。

レスポンスデータをコンポーネントで受け取る

HeroListComponentgetHeroes メソッドがあったのを覚えていますか?

    getHeroes() {
        this.heroService.getHeroes()
            .subscribe(
                heroes => this.heroes = heroes,
                error =>  this.errorMessage = <any>error
            );
    }

この中の subscribe メソッドの第1引数に渡す関数の内で、レスポンスデータにアクセス出来ます。
データがコンポーネントに保存されると、自動的にリスト表示されるという仕組みです。

複雑だけど、まずは使って慣れよう

ファイルが複数に分かれていたり、
いろんな技術が一気に出てきてかなり複雑ですが、
要点を抑えて慣れてしまえばこっちのもの。
Httpクライアントはとても頻繁に使うので、ぜひとも理解してもらいたいです。

今回は get メソッドの紹介でしたが、今後 post メソッドなども紹介します。

参考


イソップへのお悩み相談募集中

イソップに相談しませんか?

当ブログで紹介しているような、Web制作やフリーランスへの悩みをイソップに相談してみませんか?
回答できることがあれば記事の中でご紹介します。