こんばんは。今回はAngularの話です。
今日は休日ということもあり、以前購入した「Angularアプリケーションプログラミング」の本を読みすすめていました。
- 作者: 山田祥寛
- 出版社/メーカー: 技術評論社
- 発売日: 2017/08/04
- メディア: 大型本
- この商品を含むブログを見る
その中で、はてなブックマークのAPIを使って、 ブックマークエントリー情報をJSONP形式で取得するという内容があったのですが・・・書籍の発売当時のAngularバージョンを元にして書かれているため、 うまく動きません。
そこで、JSONP形式による通信方法について調査し、現行バージョンでの取得サンプルを作成しました。
目次
- 目次
- サンプルプログラムのページ
- サンプルプログラムの置き場所
- Angularアプリの作成
- app.modules.tsの記述
- app.component.htmlの記述
- app.component.tsの記述
- サービスクラス(hatena-bookmark.service.ts)の作成
- app.component.tsの修正
- アプリを実行
- ハマったところ(あまりJSONP関係ない)
サンプルプログラムのページ
はてなブックマークエントリ情報APIで、指定したURLのブックマーク情報を取得します。 初期URLは僕が作成した、ツクールMVのプラグインURLが指定されています。
https://rinne-grid.github.io/index.html
(スクリーンショット)
初期画面
ブックマークエントリー取得
サンプルプログラムの置き場所
Githubに配置しました。
- 今回、@angular/materialを使って画面を作成していますが、この記事の本筋ではないので、コードの記載は省略します。
- 全ソースが上記にありますので、git cloneして動作確認等にご利用ください。
Angularアプリの作成
Angularアプリケーションの作成については、公式ドキュメントをご参照ください。
ざっくりと下記のような感じです
- npmの場合
$ npm i -g @angular/cli
$ ng new appname
- yarnの場合
$ yarn global add @angular/cli $ ng new appname
app.modules.tsの記述
- ポイント
- @angular/common/HttpClientを利用するため、HttpClientModuleをインポートする
- HttpClientのjsonpを利用するため、HttpClientJsonpModuleをインポートする
import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgModule } from '@angular/core'; import { HttpClientModule, HttpClientJsonpModule } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ // @angular/core BrowserModule, HttpClientModule, HttpClientJsonpModule, BrowserAnimationsModule, FormsModule, ], providers: [ ], bootstrap: [AppComponent] }) export class AppModule { }
app.component.htmlの記述
- ポイント
- はてなブックマークエントリーAPIを呼び出す契機となるボタンを配置する
- 取得するブックマークエントリの対象となるURLを入力するテキストボックスを配置する(コンポーネントのtargetUrlと双方向バインド)
<input name="targetUrl" [(ngModel)]="targetUrl"> <button name="getBookmarks" (click)="getBookmark()">取得</button>
app.component.tsの記述
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { targetUrl = 'https://github.com/rinne-grid/tkoolmv_plugin_RecollectionMode' constructor(){} getBookmark() { console.log(this.targetUrl); } }
サービスクラス(hatena-bookmark.service.ts)の作成
ng generateの利用
- コンソール、ターミナルでcliを実行し、サービスクラスのテンプレートを作成
$ ng generate service hatena-bookmark
サービスクラスの記述
import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable() export class HatenaBookmarkService { constructor(private http: HttpClient){} // はてなブックマークエントリ情報取得用のAPIエンドポイント private apiEndPoint: string = "https://b.hatena.ne.jp/entry/json/"; getBookmarks(targetUrl:string): Observable<any> { console.log(targetUrl); // API仕様によると、urlとcallbackを指定するとjsonpを戻すとのこと // callbackはjsonpの第2引数で指定する let httpParams = new HttpParams() .set("url", targetUrl); // httpParamsにパラメータを設定した状態でtoStringを実行すると、key=value形式で返してくれる // https://angular.jp/api/common/http/HttpParams#toString console.log(`${this.apiEndPoint}?${httpParams.toString()}`) return this.http.jsonp<any>(`${this.apiEndPoint}?${httpParams.toString()}`, "callback"); } }
サービスクラスをapp.module.tsのprovidersに追加
import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgModule } from '@angular/core'; import { HttpClientModule, HttpClientJsonpModule } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; //------------------------------------------------------- // 追加 //------------------------------------------------------- import { HatenaBookmarkService } from './hatena-bookmark.service'; @NgModule({ declarations: [ AppComponent ], imports: [ // @angular/core BrowserModule, HttpClientModule, HttpClientJsonpModule, BrowserAnimationsModule, FormsModule, ], providers: [ //------------------------------------------------------- // 追加 //------------------------------------------------------- HatenaBookmarkService ], bootstrap: [AppComponent] }) export class AppModule { }
app.component.tsの修正
- ポイント
- HatenaBookmarkServiceをDIする
- getBookmarkメソッドから、HatenaBookmarkServiceのgetBookmarksメソッドを呼び出す
- サービスクラスのgetBookmarksの戻り値(Observable
)をsubscribeし内容を取得する
import { Component } from '@angular/core'; import { HatenaBookmarkService } from './hatena-bookmark.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { targetEntry: any = null; targetUrl = 'https://github.com/rinne-grid/tkoolmv_plugin_RecollectionMode' constructor(private hatenaService: HatenaBookmarkService){} getBookmark() { console.log(this.targetUrl); this.hatenaService.getBookmarks(this.targetUrl).subscribe( (entries) => { this.targetEntry = entries; console.log(entries); } ); } }
アプリを実行
$ ng serve
http://localhost:4200
に接続する
この記事内のソースコードは@angular/materialに関する記述を除いているため、 リンク先のようなサンプルにはならないのですが、HttpClient.jsonpを利用して、 JSONP形式のデータを取得できたことがわかるかと思います。
StackBlitzに本記事のソースコードのみで作ったサンプルを配置しましたので、こちらもご利用ください
(StackBlitz)本記事内のソースコードのみのバージョン
ハマったところ(あまりJSONP関係ない)
- Github Pagesにサンプルを上げたとき、はてなブックマークAPIのURLがhttpで、GithubPagesがhttpsだったのでMixedコンテンツになってしまってうまく動かなかった
- APIがhttpsも対応していたため、httpsに変更
- 同様に、StackBlitzからのリクエストもhttpsに変更したら動いた
- HttpParamsにパラメータをセットする際にインスタンスを作成した後にappendやsetを実行してもうまくセットされなかった
// ダメだった let httpParams = new HttpParams(); httpParams.set("hoge", "foo");
// うまくいった let httpParams = new HttpParams().set("hoge", "foo");
AngularによるJSONP取得ですが、だいぶ簡単に書くことができて、非常に便利だと思いました。 HttpClient.get, post, put等だとCORS対策等でハマりがちなのですが、 JSONPの場合は(開発の時点では)あまりハマることなくすんなりいけたように思います。
それでは、皆さんも楽しいAngularプログラミングを。