AngularのHttpClient

参考

準備

動作確認を簡単にするために。
https://github.com/typicode/json-server

{
  "posts": [
    { "id": 1, "title": "json-server", "author": "typicode" }
  ]
}

json-server --watch db.json

データを取得する

import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';

export interface Book {
  id?: number;
  title: string;
  author: string;
}

const url = 'http://localhost:3000';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  book: Book;

  constructor(
    private http: HttpClient
  ) { }

  ngOnInit(): void {
  }

  get(id: number) {
    this.http.get<Book>(`${url}/posts/${id}`)
      .subscribe(data => {
        this.book = data;
      }, (err: HttpErrorResponse) => {

        // エラー処理
        if (err.error instanceof Error) {

          // A client-side or network error occurred. Handle it accordingly.
          console.log('An error occurred:', err.error.message);
        } else {

          // The backend returned an unsuccessful response code.
          // The response body may contain clues as to what went wrong,
          console.log(`Backend returned code ${err.status}, body was: ${err.error}`);
        }
      });
  }
}

レスポンスデータの詳細を見る

this.http
  .get<Book>(`${url}/posts/1`, { observe: 'response' })
  .subscribe(resp => {
    console.log(resp);
  });

retry

RxJSにretryというオペレータがある。

import 'rxjs/add/operator/retry';
this.http
  .get<Book>(`${url}/posts/${id}`)

  // Retry this request up to 3 times.
  .retry(3)

  // Any errors after the 3rd retry will fall through to the app.
  .subscribe(...);

データを送信する

const body: Book = {
  title: 'foo',
  author: 'bar'
};
this.http
  .post<Book>(`${url}/posts`, body)
  .subscribe(resp => {
    this.book = resp;
  });

Headersを追加

this.http
  .post(`${url}/posts`, body, {
    headers: new HttpHeaders().set('Authorization', 'my-auth-token'),
  })
  // ...

URL Parametersを追加

this.http
  .post(`${url}/posts`, body, {
    params: new HttpParams().set('param', 'value')
  })
  // ...

進捗を確認

const body: Book = {
  title: 'foo' + new Date().valueOf(),
  author: 'bar' + new Date().valueOf()
};
const req = new HttpRequest('POST', `${url}/posts`, body, {
  reportProgress: true,
});

this.http.request(req).subscribe(event => {

  // Via this API, you get access to the raw event stream.
  // Look for upload progress events.
  if (event.type === HttpEventType.UploadProgress) {

    // This is an upload progress event. Compute and show the % done:
    const percentDone = Math.round(100 * event.loaded / event.total);
    console.log(`File is ${percentDone}% uploaded.`);
  } else if (event instanceof HttpResponse) {
    console.log('File is completely uploaded!');
  }
});

https://angular.io/guide/http#listening-to-progress-events

応用

Interceptorサービスを登録して、リクエスト・レスポンスに処理を追加できる。

import {NgModule} from '@angular/core';
import {HTTP_INTERCEPTORS} from '@angular/common/http';

@NgModule({
  // Interceptorは複数登録可能
  // 登録順に処理される
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: NoopInterceptor,
    multi: true,
  }],
})
export class AppModule {}

Interceptorを利用してヘッダーを追加

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { AuthService } from './auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private auth: AuthService) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authHeader = this.auth.getAuthorizationHeader();

    // HttpRequestはイミュータブル(直接、値を変更できない)
    const authReq = req.clone({ headers: req.headers.set('Authorization', authHeader) });
    return next.handle(authReq);
  }
}

Interceptorを利用してログを表示

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  const started = Date.now();
  return next
    .handle(req)

    // Rxのstreamに影響を与えずに処理を追加
    .do(event => {
      if (event instanceof HttpResponse) {
        const elapsed = Date.now() - started;

        // 経過時間を表示
        console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
      }
    });
}

https://angular.io/guide/http#logging

Interceptorを利用してキャッシュ機能を実装

constructor(private cache: HttpCache) { }

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

  if (req.method !== 'GET') {
    return next.handle(req);
  }

  const cachedResponse = this.cache.get(req);
  if (cachedResponse) {
    return Observable.of(cachedResponse);
  }

  // キャッシュがなかった場合
  return next.handle(req).do(event => {
    // Remember, there may be other events besides just the response.
    if (event instanceof HttpResponse) {
      this.cache.put(req, event);
    }
  });
}
/**
 * 以前にキャッシュされていれば、2つのResponseを返す
 */
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  if (req.method !== 'GET') {
    return next.handle(req);
  }

  // This will be an Observable of the cached value if there is one,
  // or an empty Observable otherwise. It starts out empty.
  let maybeCachedResponse: Observable<HttpEvent<any>> = Observable.empty();

  const cachedResponse = this.cache.get(req);
  if (cachedResponse) {
    maybeCachedResponse = Observable.of(cachedResponse);
  }

  const networkResponse = next.handle(req).do(event => {
    if (event instanceof HttpResponse) {
      this.cache.put(req, event);
    }
  });

  return Observable.concat(maybeCachedResponse, networkResponse);
}}

https://angular.io/guide/http#caching

XSRF

テスト