AngularでGitHubのGraphQL APIを叩く

初心者がAngularでGitHubのGraphQLを叩けるようになるまでの手順を記します。Apolloを使います。

今回のGitHubリポジトリ→ [ovrmrw/angular-apollo-github-graphql](https://github.com/ovrmrw/angular-apollo-github-graphql)

## angular-cliをインストール
```
$ npm i -g angular-cli
```

## 新しいAngularプロジェクトを作成
```
$ ng new new-project
```

`ng new`は通信環境やマシンスペックによってはお風呂に入れるぐらいの時間がかかります。

## Apolloをインストール
[Apollo Initializationドキュメント](http://dev.apollodata.com/angular2/initialization.html)

```
$ npm install apollo-client angular2-apollo graphql-tag --save
```

Apolloを使うとAngularでGraphQLサーバーを叩いてデータを取得することができます。
今回のサンプルではGitHubのGraphQL APIを叩いてみます。

## GitHub側の設定をする
個人的にはここが一番難儀しました。
結論から言うとここをよく読む必要があります。→ [Accessing GraphQL](https://developer.github.com/early-access/graphql/guides/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`を作ります。
そして中身を

```ts:config.secret.ts
export const githubToken = 'your_github_token';
```
のように編集します。

当然のことですがこのファイルはGitHubリポジトリにpushされないよう気を付けなければなりません。

## app.module.tsを編集する
これが`app.module.ts`の全コードです。

```ts:src/app/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に関わる部分だけ抜き出してみましょう。

```ts:src/app/app.module.ts(抜粋)
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 Queriesドキュメント](http://dev.apollodata.com/angular2/queries.html)

Apolloのドキュメントを参考にしつつServiceを作ります。
僕は今のところRedux大好きっ子なのでAPIの戻り値をdispatcherでStoreに送るという流れを作ります。
`apollo.watchQuery`の戻り値は拡張したObservableなのでRxJSにも慣れておくと良いでしょう。`take(1)`を挟むことで毎回completeしてメモリリークしないようにしています。

```ts:src/app/app.service.ts
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を受ける必要があります。

```ts:src/app/app.component.ts
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(); }

}
```

処理の流れはこう。

1. `requestViewer()`でServiceのActionを呼ぶ。 
2. `get state()`で更新されたStateをObservableとして受ける。
3. templateの`{{state | async | json}}`で`state`をAsyncPipeで受ける。
4. Viewが更新される。

## Apolloがデータをキャッシュしていることを確認する
アプリを起動してみましょう。

```
$ npm start
```

`http://localhost:4200/`をブラウザで開きます。

**Request Viewer**ボタンをクリックすると自分の名前が表示されることが確認できるかと思います。

さらに何回もクリックしても表示は変わりませんが、デベロッパーツールでNetworkを監視してみてください。2回目以降はHTTPリクエストが発生していないことがわかります。

**So Cool!!**

ちなみに

```ts:src/app/app.service.ts
this.apollo.watchQuery({ query: CurrentViewerQuery })
```
を

```ts:src/app/app.service.ts
this.apollo.watchQuery({ query: CurrentViewerQuery, forceFetch: true })
```
とするとキャッシュしていても毎回強制的にfetchします。キャッシュの仕組みに関しては[GraphQL Concepts Visualized](https://medium.com/apollo-stack/the-concepts-of-graphql-bc68bd819be3#.juccoy4fv)が詳しいです。

## Redux部分について
ReduxはRxJSで構築しています。ソースコードはこちら。→ [src/app/store](https://github.com/ovrmrw/angular-apollo-github-graphql/tree/master/src/app/store)