初心者がAngularでGitHubのGraphQLを叩けるようになるまでの手順を記します。Apolloを使います。
今回のGitHubリポジトリ→ ovrmrw/angular-apollo-github-graphql
angular-cliをインストール
$ npm i -g angular-cli
新しいAngularプロジェクトを作成
$ ng new new-project
ng new
は通信環境やマシンスペックによってはお風呂に入れるぐらいの時間がかかります。
Apolloをインストール
$ npm install apollo-client angular2-apollo graphql-tag --save
Apolloを使うとAngularでGraphQLサーバーを叩いてデータを取得することができます。
今回のサンプルではGitHubのGraphQL APIを叩いてみます。
GitHub側の設定をする
個人的にはここが一番難儀しました。
結論から言うとここをよく読む必要があります。→ Accessing GraphQL
まずは
1) You must be signed up for the Early Access Program.
だそうです。指示に従ってください。
次に
2) You'll need an OAuth token with the right scopes.
だそうです。
Generating an OAuth tokenの項を読んで指示に従ってください。
そして得られたTokenは次で使います。
GitHub Tokenをプロジェクトに含める
config.secret.default.ts
をコピーしてconfig.secret.ts
を作ります。
そして中身を
export const githubToken = 'your_github_token';
のように編集します。
当然のことですがこのファイルはGitHubリポジトリにpushされないよう気を付けなければなりません。
app.module.tsを編集する
これがapp.module.ts
の全コードです。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { ApolloModule } from 'angular2-apollo';
import { AppComponent } from './app.component';
import { AppService } from './app.service';
import { githubToken } from '../../config.secret';
import { StoreModule } from './store';
const networkInterface = createNetworkInterface('https://api.github.com/graphql');
networkInterface.use([{
applyMiddleware(req, next) {
if (!req.options.headers) {
req.options.headers = {};
}
req.options.headers['authorization'] = `bearer ${githubToken}`;
next();
}
}]);
const client = new ApolloClient({ networkInterface });
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
ApolloModule.withClient(client),
StoreModule,
],
providers: [AppService],
bootstrap: [AppComponent]
})
export class AppModule { }
少々ごちゃごちゃしてるのでApolloに関わる部分だけ抜き出してみましょう。
import ApolloClient, { createNetworkInterface } from 'apollo-client';
import { ApolloModule } from 'angular2-apollo';
import { githubToken } from '../../config.secret';
const networkInterface = createNetworkInterface('https://api.github.com/graphql');
networkInterface.use([{
applyMiddleware(req, next) {
if (!req.options.headers) {
req.options.headers = {};
}
req.options.headers['authorization'] = `bearer ${githubToken}`;
next();
}
}]);
const client = new ApolloClient({ networkInterface });
@NgModule({
imports: [ApolloModule.withClient(client)],
})
export class AppModule { }
大分すっきりしました。
networkInterface
というのを作ってApolloClient
を生成してApolloModule
に含めるという内容です。
先程のGitHub Tokenはリクエストのヘッダーに含めているのだということがなんとなくわかりますね。
GraphQL APIを叩いてみる (app.service.tsを作る)
Apolloのドキュメントを参考にしつつServiceを作ります。
僕は今のところRedux大好きっ子なのでAPIの戻り値をdispatcherでStoreに送るという流れを作ります。
apollo.watchQuery
の戻り値は拡張したObservableなのでRxJSにも慣れておくと良いでしょう。take(1)
を挟むことで毎回completeしてメモリリークしないようにしています。
import { Injectable } from '@angular/core';
import { Angular2Apollo } from 'angular2-apollo';
import gql from 'graphql-tag';
import { environment } from '../environments/environment';
import { Dispatcher, Action, RequestViewerAction, ViewerState } from './store';
const CurrentViewerQuery = gql`
query {
viewer {
name
login
}
}
`;
@Injectable()
export class AppService {
constructor(
private dispatcher$: Dispatcher<Action>,
private apollo: Angular2Apollo,
) { }
requestViewer(): void {
console.time('requestViewer');
this.apollo
.watchQuery({ query: CurrentViewerQuery })
.take(1)
.subscribe(result => {
if (!environment.production) {
console.log('result:', result);
}
const viewer = result.data.viewer as ViewerState;
this.dispatcher$.next(new RequestViewerAction(viewer));
}, err => console.error(err), () => console.timeEnd('requestViewer'));
}
}
ComponentでStateを受ける (app.component.tsを編集する)
ReduxですのでComponentはActionを呼んだ後Stateを受ける必要があります。
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { AppService } from './app.service';
import { Store, AppState } from './store';
@Component({
selector: 'app-root',
template: `
<h1>{{title}}</h1>
<pre>{{state | async | json}}</pre>
<div>
<button (click)="requestViewer()">Request Viewer</button>
</div>
`,
styleUrls: ['./app.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
title = 'app works!';
constructor(
private service: AppService,
private store: Store,
) { }
requestViewer(): void {
this.service.requestViewer();
}
get state(): Observable<AppState> { return this.store.getState(); }
}
処理の流れはこう。
-
requestViewer()
でServiceのActionを呼ぶ。 -
get state()
で更新されたStateをObservableとして受ける。 - templateの
{{state | async | json}}
でstate
をAsyncPipeで受ける。 - Viewが更新される。
Apolloがデータをキャッシュしていることを確認する
アプリを起動してみましょう。
$ npm start
http://localhost:4200/
をブラウザで開きます。
Request Viewerボタンをクリックすると自分の名前が表示されることが確認できるかと思います。
さらに何回もクリックしても表示は変わりませんが、デベロッパーツールでNetworkを監視してみてください。2回目以降はHTTPリクエストが発生していないことがわかります。
So Cool!!
ちなみに
this.apollo.watchQuery({ query: CurrentViewerQuery })
を
this.apollo.watchQuery({ query: CurrentViewerQuery, forceFetch: true })
とするとキャッシュしていても毎回強制的にfetchします。キャッシュの仕組みに関してはGraphQL Concepts Visualizedが詳しいです。
Redux部分について
ReduxはRxJSで構築しています。ソースコードはこちら。→ src/app/store