第8回：ニュースフィード取得サーバIFをWebアプリから呼び出す

[目次：Webアプリ開発ロードマップ](https://qiita.com/radiance1104/private/7c5b04ce4cf8ec7bd708)
# 第8回：ニュースフィード取得サーバIFをWebアプリから呼び出す
今回は前回作成したニュースフィード一覧取得IFをフロントエンドから呼び出してみます。

## REST API操作用のサービスを作成する

### 空のサービスを作成する
フロントエンドでREST APIを取り扱うためのサービスを作成します。
下記のコマンドを実行します。

```
$ ng g service services/rest/rest
```
`rest.service.ts`とテストコードの`rest.service.spec.ts`が作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202428/34966b15-7ed3-45b0-7142-ea6bd8481cc9.png)

### モジュールで作成したサービスを読み込む
`src/app/app.module.ts`に下記の修正を行い、アプリにserviceを読み込ませます。

```
・・・
import { RestService } from './services/rest/rest.service';
・・・
  providers: [
    RestService
  ],
・・・
```

## REST APIのレスポンスデータを受け取るためのデータクラスを作成する
### ニュースフィード用のデータクラスを作成する
`src/app/services/rest/data/newsfeeds.ts`を下記の内容で新規に作成します。
ニュースフィードのデータを持たせるためのデータクラスです。

```
export class Newsfeed {
  message: string;
  createdAt: Date;

  decode(json: any) {
    if (json) {
      this.message = json.message;
      this.createdAt = json.createdAt ? new Date(json.createdAt) : undefined;
    }
  }
}

export class Newsfeeds {
  private sortedByDefault: Newsfeed[] = [];
  newsfeeds: Newsfeed[];

  decode(json: any) {
    if (json) {
      for (const jsonNewsfeed of json) {
        const newsfeed = new Newsfeed();
        newsfeed.decode(jsonNewsfeed);
        this.sortedByDefault.push(newsfeed);
      }
      this.sortByCreatedAt();
    }
  }

  sortByCreatedAt() {
    this.newsfeeds = this.sortedByDefault.slice();
    this.newsfeeds.sort((a, b) => {
      return b.createdAt.getTime() - a.createdAt.getTime();
    });
  }
}
```
JSONオブジェクトからデータを取り出し、内部に保持させるdecodeメソッドや、
日時で初期ソートを行うsortByCreatedAt()メソッドを持たせています。

### レスポンスデータ共通のデータクラスを作成する
`src/app/services/rest/data/response.ts`を下記の内容で新規に作成します。
全てのレスポンスデータが共通で持つ部分を作成します。

```
export class Response<T> {
  code: number;
  data: T;

  constructor(data: T, code?: number) {
    this.code = code ? code : 200;
    this.data = data;
  }
}
```

## RestServiceにニュースフィード一覧取得用の処理を実装する
### HttpClientModuleをモジュールに登録する
RestClientではHttpClientを利用したいので、HttpClientModuleを`app.module.ts`に登録します。

```
・・・
import { HttpClientModule } from '@angular/common/http';
・・・
  imports: [
・・・
    HttpClientModule,
  ],
・・・
```

### RestServiceにニュースフィード一覧取得の処理を実装する
`src/app/services/rest/rest.service.ts`にニュースフィード一覧取得の処理を実装します。

```
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { Response } from './data/response';
import { Newsfeeds } from './data/newsfeeds';

@Injectable({
  providedIn: 'root'
})
export class RestService {

  constructor(
    private httpClient: HttpClient
  ) { }

  async searchNewsFeeds(): Promise<Response<Newsfeeds>> {
    return new Promise((resolve, reject) => {
      const url = 'http://127.0.0.1:4300/newsfeeds';
      this.httpClient.get(url).toPromise().then(response => {
        const newsfeeds = new Newsfeeds();
        newsfeeds.decode(response);
        resolve(new Response(newsfeeds));
      }).catch(error => {
        console.error(error);
        reject(error);
      });
    });
  }
}
```

async、Promise、resolve、reject、then、catchは非同期処理を行うためのものです。
下記を参照してください。

* [非同期処理Promiseについて](https://qiita.com/drafts/d527d3023108a9228c09/edit)
* [非同期処理async,awaitについて](https://qiita.com/radiance1104/items/08ed1399ea084361766a)

REST APIは`this.httpClient.get(url)`の部分で呼び出されます。

## コンポーネントから作成したRestServiceを利用する
### TypeScriptファイルを修正する。
`src/app/components/newsfeeds/newsfeeds.component.ts`を下記のように修正します。
後述のhtmlの修正まで行わないとエラーになります。

```
import { Component, OnInit } from '@angular/core';
import { RestService } from '../../services/rest/rest.service';
import { Newsfeeds } from '../../services/rest/data/newsfeeds';

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

  newsfeeds: Newsfeeds;

  constructor(
    private restService: RestService
  ) { }

  async ngOnInit() {
    try {
      const response = await this.restService.searchNewsFeeds();
      this.newsfeeds = response.data;
    } catch (error) {
      // TODO: display alert dialog.
    }
  }
}
```

Serviceのオブジェクトはアプリ内で1つだけ生成されます。
constructor()の引数にServiceを記載することで、コンポーネントが生成される時に引数に渡されます。

```
  constructor(
    private restService: RestService
  ) { }
```

aync、awaitは非同期のためのものです。
[非同期処理async,awaitについて](https://qiita.com/radiance1104/items/08ed1399ea084361766a)を参照してください。

### HTMLファイルを修正する。
`src/app/components/newsfeeds/newsfeeds.component.html`を下記のように修正します。
ここまで実装すれば、画面にバックエンドから取得したデータが表示されます。

```
<div id="newsfeeds">
  <ng-container *ngIf="newsfeeds && newsfeeds.newsfeeds">
    <article *ngFor="let newsfeed of newsfeeds.newsfeeds" class="newsfeed">
      <p class="message">{{newsfeed.message}}</p>
      <p class="createdAt">{{newsfeed.createdAt | dateToString}}</p>
    </article>
  </ng-container>
</div>
```

元々はnewsfeedsに配列を持たせていましたが、newsfeeds.newsfeedsに配列を持たせるよう修正しています。
そのため、REST APIからデータをもらう前に画面を作ろうとするとnewsfeeds.newsfeedsに参照エラーが起きてしまいます。
そのためif文を追加し、データがあるときだけ表示するよう修正しています。
また、`*ngIf`は`*ngFor`と同じタグ内に書けないため、`<ng-container>`を追加し`*ngIf`を行っています。
`<ng-container>`は画面には表示されない制御用のタグです。

## 環境変数を利用する
### environmentsファイルを修正
`src/app/services/rest/rest.service.ts`にサーバアドレス`http://127.0.0.1:4300`を直書きしていたため、
環境変数に置き換えます。
`src/environments/environment.prod.ts`と`src/environments/environment.ts`に下記のrestの部分を追加します。

```
export const environment = {
  production: true,
  rest: {
    domain: 'http://127.0.0.1:4300'
  }
};
```

`environment.prod.ts`はプロダクション(商用)ビルド、`environment.ts`は開発用ビルドで利用される環境変数です。
今まで利用してきたビルドコマンド`$ ng serve`や`$ ng build`は開発用のビルドを行います。
`--prod`オプションをつけることでプロダクションビルドになります。

### 環境変数を埋め込む
`src/app/services/rest/rest.service.ts`に下記の修正を加えます。

```
・・・
import { environment } from '../../../environments/environment';
・・・
  async searchNewsFeeds(): Promise<Response<Newsfeeds>> {
    return new Promise((resolve, reject) => {
      const url = environment.rest.domain + '/newsfeeds';
・・・
```

## 最後に
今回はフロントエンドからREST APIを呼んでみました。
次回は、ニュースフィード投稿用のIFを作成していきます。
今回開発したソースコードは[GitHub](https://github.com/radiance1104/web-learning-loadmap/tree/create-get-newsfeeds-client)に入っています。