AngularのHttp -> HttpClientにした話

この記事は、[Business Bank Group Developers Advent Calendar](https://qiita.com/advent-calendar/2018/bbg_developers) 3日目の記事です。

---

こんにちは、ビジネスバンクグループでエンジニアをしているしみきょん [@kyokoshimizu](https://qiita.com/kyokoshimizu) です。

今Angularのversionを6にアップデートしようとしているのですが、その前に必須となるHttp -> HttpClient 移行した話を書きます


## 移行作業
もともと、HttpModuleを使用してHTTPリクエストを行なっていた処理を、HttpClient, HttpClientModuleを使用するようにしました。

```typescript
// import { HttpModule } from '@angular/http';
import { HttpClient, HttpClientModule } from '@angular/common/http';
```
もともと、こんな感じだったHTTPリクエスト用のServiceが

```typescript
import { Headers, Http, URLSearchParams } from '@angular/http';

export interface HttpOptionsInterface {
  params: any;
  body: any;
  ...
}

export class BaseService {
  constructor(private http: HttpClient) {}
 
  search<Hoge>(url: string, options?: HttpOptionsInterface): Observable<Hoge> {
    const headers = this.getHeaders(options.header);
    const params = this.getSearchParams(options.params);

    return this.http.get(url, Object.assign({ headers: headers, params: params }))
      .map(res => res.json() as Hoge);
  }

  ...

  protected getHeaders(options: any = {}): Headers { ... } // 認証トークンやContent-Typeのセット
  protected getSearchParams(params: any): URLSearchParams { .. } // リクエストパラメータのセット
}
```

こんな感じに変わりました

```typescript
import { HttpClient, HttpParams } from '@angular/common/http';

export interface HttpOptionsInterface {
  params: any;
  body: any;
  ...
}

export class BaseService {
  constructor(private http: HttpClient) {}
 
  search<Hoge>(url: string, options?: HttpOptionsInterface): Observable<Hoge> {
    return this.http.get<Hoge>(url, { params: this.getSearchParams(options.params) });
  }

  ...

  protected getSearchParams(params: any): HttpParams { .. }
}
```
### レスポンスがJSONオブジェクトとして返却されるようになりました
そのため、レスポンスを.jsonする必要がなくなりました。
またメソッドを呼ぶ際に型パラメータを指定することによって、レスポンスオブジェクトの型定義もできます。

```typescript
this.http.get<Hoge> // -> Observable<Hoge>
```
responseTypeを指定することによって、JSON以外のデータをリクエストすることも可能です。
リクエスト処理が失敗した時は、HttpErrorResponseが返却されます。

### リクエスト周りの変更点

Headers -> HttpHeaders
URLSearchParams -> HttpParams

になりました。
HttpParamsはimmutableです。
HttpParamsOptionsというオプションがあって

```typescript
  new HttpParams({fromObject: { hoge: 1, hoge: 2 }})
```
みたいなことができるのですがAngular5以降しかfromObjectオプションがないので、Angular4環境で作業していた私は使用できませんでした。。

string, 配列, オブジェクトの３パターンの値をHttpParamsにセットしたかったので現状こんな感じになってます
（Angular６にしたらリファクタリングする予定です）

```typescript
let urlSearchParams = new HttpParams();
_.each(params, (val, key) => {
  if (val instanceof Array) {
    _.each(val, (v) => urlSearchParams = urlSearchParams.append(key.toString(), v));
  } else {
    const value = (val instanceof Object) ? JSON.stringify(val) : val;
    urlSearchParams = (_.isUndefined(value) || _.isNull(value)) ?
      urlSearchParams.delete(key.toString()) : urlSearchParams.append(key.toString(), value);
  }
});
```

RequestOptionsArgsはなくなり、HttpClient#get, HttpClient#deleteにはbodyを渡せなくなったのでgetやdeleteにbodyを渡したい時は、HttpClient#requestを使用するようにしました。

### Interceptor
インターセプターという機能が追加されました。
HttpInterceptorを実行したclassにinterceptメソッドを書くと、リクエストとレスポンス時に処理をフックすることができます。
今回は、もともとgetHeadersで行なっていたheader情報の挿入をinterceptorに切り出しました。

```typescript
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';

@Injectable()
export class DefaultRequestInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const newReq = req.clone({
      setHeaders: { 'Content-Type': 'application/json' }
    });
    return next.handle(newReq);
}
```
interceptor便利です！

### まとめ
以上、HttpClient移行の話でした。インターセプターは今後も色々と活用していきたいなと思います！
[日本語公式ドキュメント](https://angular.jp/guide/http.old) 丁寧に説明されていてわかりやすかったです


明日は、 [@bigplants](https://qiita.com/bigplants) さんが担当です！
お願いします！