はじめに
最近、Ajax通信でAPIをコールしてJSON形式で取得したデータで動的に画面を差分更新するようなSPAの開発が流行っています。
今回は、AngularでRest APIをコールしてJSON形式のデータを取得し、データをリスト表示するサンプルプログラム作成を行います。
ついでに、コールされる側のAPIもSpring Bootで作成してみましょう。
今回やりたいことを絵にすると、こんな感じ
環境
- OS:OS X 10.11 El Capitan
- STS:3.8.4.RELEASE
- node:v8.1.0
- Angular CLI:1.1.1
- Visual Studio Code:1.13
前提
以下が完了している前提で説明します。


サンプルプログラム作成手順
サンプルプログラムは以下の手順で行います。
- RestAPIの修正(JSONP対応)
- AngularのRestAPIコールサービス追加
- ローカルPCでAPサーバ、Webサーバ起動
1.RestAPIの修正(JSONP対応)
まずはバックエンドの作成から行います。
事前に作成したRest APIをJSONPでコールできるように、JsonpAdvice.javaを追加します。
package com.example;
import java.io.IOException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
super("callback");
}
@Bean
public MappingJackson2HttpMessageConverter MappingJackson2HttpMessageConverter(ApplicationContext ctx) {
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json().applicationContext(ctx).build();
return new MappingJackson2HttpMessageConverter(mapper) {
@Override
protected void writePrefix(JsonGenerator generator, Object obj) throws IOException {
if (!(obj instanceof MappingJacksonValue))
return;
String funcName = ((MappingJacksonValue) obj).getJsonpFunction();
if (funcName != null)
generator.writeRaw(funcName + "(");
}
};
}
}
最終的に出来上がるRestAPIのプロジェクト構成は以下のようになります。
■クロスドメイン制約
XMLHttpRequest(ブラウザ上でサーバとHTTP通信をするためのAPI)では別ドメインにリクエストできない制約。
ブラウザを操作している人が意図しないところで勝手にAjax通信で別サービスに個人情報を登録される。。。みたいなことにならないための制約。
■JSONP
クロスドメインのAPIからデータを取得するしくみ。scriptタグのsrc属性ではクロスドメインのURLを指定してデータを取得することができる。これをJSONPでは利用する。
JSONPを実現するためにRest API側の対応も必要となる。
以上でRestAPIの修正は完了です。
2.AngularのRestAPIコールサービス追加
次はAngularからRestAPIをコールするサービスを追加します。
やることは以下。
- item.service.tsの作成
- app.module.tsの修正
- dashboard.component.tsの修正
itemService.tsの作成
import { Injectable } from '@angular/core';
import { Headers, Http, Jsonp, RequestOptionsArgs, RequestOptions, URLSearchParams} from '@angular/http';
import {Observable} from "rxjs/Observable";
import "rxjs/add/operator/map";
import { Item } from '../item';
@Injectable()
export class ItemService {
//RestAPIのURL
private itemsUrl = 'http://localhost:8080/api/items';
//JSONPコールバック関数名(Angular固有値)
CALLBACK = 'JSONP_CALLBACK';
//コンストラクタで利用するモジュールをインスタンス化
constructor(private http: Http, private jsonp: Jsonp) { }
//商品一覧取得
getItems(): Observable<Item[]> {
//リクエストパラメータセット
let option : RequestOptions;
option = this.setHttpGetParam(this.itemsUrl);
//レスポンス返却
return this.jsonp.request(this.itemsUrl, option)
.map((response) => {
let content;
let obj = response.json();
content = {
error: null,
data: obj
};
console.dir(content);
return content;
});
}
//Http(Get)通信のリクエストパラメータをセットする
private setHttpGetParam(url: string): RequestOptions {
let param = new URLSearchParams();
param.set("callback", this.CALLBACK);
let options: RequestOptionsArgs = {
method: "get",
url: url,
search: param
};
return new RequestOptions(options);
}
}
2.app.module.tsの修正
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
//RestAPIコールに利用するモジュール追加
import { HttpModule,JsonpModule} from '@angular/http';
import { DashboardComponent } from './dashboard.component';
import { AppComponent } from './app.component';
//商品情報取得用serviceを追加
import {ItemService} from './services/item.service';
@NgModule({
declarations: [
AppComponent,
//DashboardComponentを読み込めるように追加
DashboardComponent
],
imports: [
BrowserModule,
//RestAPIコールに利用するモジュール追加
HttpModule,
JsonpModule
],
providers: [
//商品情報取得用serviceをDIできるように追加
ItemService
],
bootstrap: [AppComponent]
})
export class AppModule { }
3.dashboard.component.tsの修正
import {Component, OnInit} from "@angular/core";
import {Item} from "./item";
import {ItemService} from "./services/item.service";
@Component({
selector: "my-dashboard",
template: `
<ul>
<li *ngFor = "let item of itemList">
{{item.name}}:{{item.price}}円
</li>
</ul>
`,
})
export class DashboardComponent implements OnInit{
//コンポーネント生成時の処理
constructor(private itemService: ItemService){}
//商品リスト
itemList : Item[];
//画面初期表示イベント処理
ngOnInit(): void {
this.itemService.getItems().subscribe(
result => this.setItems(result),
error => alert('通信エラー' + error)
);
}
//Web APIから取得したデータを商品リストにセットする
setItems(result): void {
if(result.error) {
alert('Web APIエラー' + result.message);
}
this.itemList = result.data;
}
}
ngOnInit()で画面初期表示時にitemService.getItems()をコールしました。
APIコール時にはJSONPを利用し、
取得したデータはitemListにセットしてAngularが画面を差分更新してくれるって感じです。
最終的に出来上がるAngularプロジェクトの構成は以下のようになります。
以上でRestAPIをコールするサービス追加は完了です。
3.ローカルPCでAPサーバ、Webサーバ起動
APサーバ起動
STSを開き、パッケージ・エクスプローラーでsrc/main/java/com/example/RestApiApplication.javaを右クリック>実行>Spring Boot アプリケーションでAPサーバを起動します。
Webサーバ起動
VS Codeを開き、「Shift」+「Control」+「@」でターミナルを開きます。
カレントディレクトリをプロジェクト内に移し、以下コマンドでWebサーバを起動します。
> ng serve
以上で、AngularでRestAPIをコールするサンプルプログラム作成は完了です。
おわりに
今回はAngularでRest APIをコールするサンプルプログラムと、コールされる側のRestAPIをBoot Strapで作成しました。
クロスドメイン制約を回避するためには、JSONP対応をサーバ側で行い、コールバック関数でデータを受け取って・・・でしたね。
今回はGETメソッドのHTTP通信だったのでJSONPが利用できましたが、
POSTやPUTメソッドの場合、サーバ側でクロスドメインでのアクセスは〇〇ドメインからのみ許可みたいな設定が必要になります。