Angular Serviceクラス -データアクセス- #memo 

#はじめに
articleデータは、BackendAPIからHTTPメソッドで取得するが、Componentにはデータの取得や、保存のロジックを実装するべきではない。
Componentは、Templateに渡すデータの提示のみに集中するべきである。
従って、データアクセスには、Serviceクラスを実装する。

###Service
ComponentクラスはTemplateに対する処理に集中すべきであるため、データの取得や保存処理はServiceクラスに任せる。
**Serviceクラスの生成**
```$ ng g service services/article-list```

```typescript:article-list.service.ts

import { Injectable } from '@angular/core';
import { ARTICLES } from '../models/mock-article';

@Injectable({
  providedIn: 'root'
})

export class ArticleListService {
  getArticles() { return ARTICLES; }
  constructor() { }
}
```

**Dependency Injection**
@Injectableを付与することでクラスをServiceとしてComponentに引き渡すことができるようになる。

```typescript:article-list.component.ts
import { Component, OnInit } from '@angular/core';
import { ARTICLES } from '../../models/mock-articles';
import { Article } from 'src/app/models/article';
import { ArticleListService } from 'src/app/services/article-list.service';

@Component({
  selector: 'app-article-list',
  templateUrl: './article-list.component.html',
  styleUrls: ['./article-list.component.scss']
})

export class ArticleListComponent implements OnInit {
  articleList: Article[] = ARTICLES;
  carouselPosition = {
    maxWidth: '600px',
    width: '100%',
    margin: '3% auto 0 auto',
  };
  // ArticleListServiceを有効化
  constructor(private articleListService: ArticleListService) { }
  // Component初期化時に発火
  ngOnInit() {}
}
```
定義済みのServiceを利用するためにモジュールに登録する。
**実装のポイント**
・HttpClientからObservableが返される
・Componentに注入するServiceは最小限に
・[CORS対応](https://qiita.com/syukai/items/4e88749d1d62f1148e45)はapi側で制御

```typescript:app.module.ts
import { HttpClientModule } from '@angular/common/http';
// components
// 略
// services
import { ArticleListService } from './services/article-list.service';
// 略
  imports: [
    BrowserModule,
    NgbModule,
    HttpClientModule,
  ],
  providers: [
    ArticleListService,
```
HttpClientのgetメソッドを用いてJSONデータを取り出す

```typescript:article-list.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Article } from '../models/article';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})

export class ArticleListService {
  constructor(public httpClient: HttpClient) { }
  // 投稿記事情報を取得
  getArticles(page: number, pageSize: number): Observable<any> {
    // 一覧の開始位置と取得する行数をパラメータ指定する
    const params = new HttpParams()
      .set('offset', (page * pageSize).toString())
      .set('limit', pageSize.toString());
    console.log(params);

    return this.httpClient.get<Article[]>('http://localhost:8080/article/list', { params: params });
  }
}
```
ComponentにおいてServiceメソッドを使う

```typescript:article-list.component.ts
import { Component, OnInit } from '@angular/core';
// import { ARTICLES } from '../../models/mock-articles'; // DELETE
import { Article } from 'src/app/models/article';
import { ArticleListService } from 'src/app/services/article-list.service'; // NEW

@Component({
  selector: 'app-article-list',
  templateUrl: './article-list.component.html',
  styleUrls: ['./article-list.component.scss']
})
export class ArticleListComponent implements OnInit {
  articleList: Article[]; // FIX
  CONTENT_LENGTH = 3;

  constructor( private articleListService: ArticleListService ) { } // NEW
  // ngOnInit　はコンポーネントが生成された時に呼ばれる処理
  ngOnInit() { this.getArticles(); } // NEW

  getArticles() { // NEW
    this.articleListService.getArticles(0, 5).subscribe(
      (articles) => {
        console.log(JSON.stringify(articles));
        this.articleList = articles;
      }
    );
  }
}
```
###Observable
**Observables**というアーキテクチャを用いたリアクティブプログラミング用のライブラリであり、非同期処理を簡潔かつ可読性高くコーディングできることを主な目的としている。Rxでは```Observable```と呼ばれるものを扱う。```Observable<T>```でT型のストリームとなる。

```typescript:article-list.service.ts
getArticles(page: number, pageSize: number): Observable<any> {
    console.log('[getArticles] page = ' + page);
    console.log('[getArticles] pageSize = ' + pageSize);

    const params = new HttpParams()
      .set('offset', (page * pageSize).toString())
      .set('limit', pageSize.toString());

    return this.httpClient.get<Article[]>(this.apiUrl + '/article/list', { params })
      .pipe( catchError(this.handleError) );
  }
```
```typescript:article-list.component.ts
  getArticles(page: number) {
    this.articleListService.getArticles(page, this.PAGE_SIZE).subscribe(
      (articles) => {
        console.log(JSON.stringify(articles));
        this.articleList = articles;
      },
      (error: AppError) => {
        if (error instanceof NotFoundError) {
          console.log('ERROR: Not Found.');
        } else if (error instanceof BadRequestError) {
          console.log('ERROR: Bad Request.');
        } else {
          throw error;
        }
      }
    );
  }
```

###HTTPInterceptor
HttpInterceptorはHttpRequestやHttpResponseが処理される前に割り込む処理である

**CommonInterceptor**
最小構成で実装

```typescript:common-interceptor.ts

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
// Interceptor は Service である
@Injectable()
// HttpInterceptor を実装する
export class CommonInterceptor implements HttpInterceptor {
  // 戻り値はObservable<HttpEvent<any>>
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // req は次のinterceptメソッドの引数 req として渡される
    return next.handle(rep);
  }
}
```
```typescript:common-interceptor.ts
// 略
@Injectable()
export class CommonInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // req は Read Only なため、新しい req を生成して後続処理に渡す
    const newReq = req.clone({ setHeaders: { Accept: 'application/json'}});
    return next.handle(newReq);
  }
}
```
###MockBackendInterceptor
**実装のポイント**
・reqの中身に応じてHttpReponseを返す
・RxJS処理の流れ
.....

```typescript:mock-backend-interceptor.ts
import { Injectable } from '@angular/core';
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { delay, mergeMap } from 'rxjs/operators';
import { ARTICLES } from '../models/mock-articles';

@Injectable()
export class MockBackendInterceptor implements HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return of(null).pipe(
            mergeMap(() => {
                console.log('[MockBackend] URL: ' + req.url);

                if (req.method === 'GET') {
                    if (req.url.endsWith('/article/list')) {
                        const begin = parseInt(req.params.get('offset'), 10);
                        const end   = parseInt(req.params.get('limit'), 10) + begin;
                        return of(new HttpResponse({ status: 200, body: ARTICLES.slice(begin, end) }));
                    } 
                } 
                return next.handle(req);
            }),
            delay(1000)
        );
    }
}
```
プロバイダーを追加
providersは上から順にinterceptするので注意

```typescript:app.module.ts
import { MockBackendInterceptor } from 'src/app/interceptors/mock-backend-interceptor';
// 略
 providers: [
    { provide: ErrorHandler, useClass: AppErrorHandler },
    { provide: HTTP_INTERCEPTORS, useClass: CommonInterceptor, multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: MockBackendInterceptor, multi: true }, // NEW
  ],
```