第8回:ニュースフィード取得サーバIFをWebアプリから呼び出す
今回は前回作成したニュースフィード一覧取得IFをフロントエンドから呼び出してみます。
REST API操作用のサービスを作成する
空のサービスを作成する
フロントエンドでREST APIを取り扱うためのサービスを作成します。
下記のコマンドを実行します。
$ ng g service services/rest/rest
rest.service.ts
とテストコードのrest.service.spec.ts
が作成されます。
モジュールで作成したサービスを読み込む
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は非同期処理を行うためのものです。
下記を参照してください。
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についてを参照してください。
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に入っています。