Angular最速マスターへの道 第6章

Blog Single

Angular7.0が9月か10月あたりでリリースされるとかされないとか。。。
Angularはバージョンアップのペースが早いので本当に飽きませんね!(マスターへの道が遠ざかるだけなのでは←)
さぁ本日もAngular最速マスターへの道やっていきましょう!

第6章目次

  • Service

Service

Serviceにはデータの取得や保存処理を扱う役割があります。
Component内でもその処理を記述することが出来ないわけではないのですが、Angularの公式(日本語ドキュメント)には以下のように記載してあります。

コンポーネント内では直接データの取得や保存を行うべきではありません。 もちろん、故意に仮のデータを渡してもいけません。 コンポーネントはデータの受け渡しに集中し、その他の処理はサービスクラスへ委譲するべきです。

とまぁServiceを使うように推奨しているので言う通りにしておきましょう!

プロジェクト内でデータ送受信を行うService

これまでのやり方だと、ルーティングが変わるとComponentの値が初期化されてしまい、変更があったデータの保管ができませんでしたよね。
そういう時にもServiceが利用できます。

例として、メッセージをやりとりするServiceを作ってみましょう。
Serviceはいつもの如くAngular CLIのコマンドで作成します。

ng g service services/message --spec false

Angular6以降、コマンドでServiceを作るとデフォルトで@Injectable({providedIn: 'root'})というのが設定されており、この設定のServiceはModuleに登録不要です。
つまりどこからでも使用できるようになったと!なんと便利な!
Serviceのファイルを置く場所は特に指定はありませんのでお好きな場所にどうぞ!
(AFMではservices/に作成していきます)

メッセージのやりとりということで送信と受信用のメソッドをServiceに作ります。

export class MessageService {
    private messages: string[] = [];

    public add(text: string): void {
        this.messages.push(text);
    }

    public get(): string[] {
        return this.messages;
    }
}

単純にまぁこのような感じでしょうか。
ではこのServiceをComponentで使用してみましょう。

export class MessageComponent implements OnInit {
    public messages: string[] = [];

    constructor(private messageService: MessageService) {}

    ngOnInit(): void {
        this.receive();
    }

    public send(message: string): void {
        this.messageService.add(message);
        this.receive();
    }

    public receive(): void {
        this.messages = this.messageService.get();
    }
}
<input #message type="text">
<button (click)="send(message.value)"></button>

<p *ngFor="let message of messages">{{message}}</p>

ComponentでServiceを使用する時はconstructorに定義します。
あとは呼びたいところでServiceのメソッドを実行するだけです。

ただ、ここで注意しておかなければいけないことが。
実際にこれがサーバーを介しての処理であった場合、Serviceでの処理は非同期処理になります。
つまりここで言うと、メッセージの受信で即座にデータが返ってくるとは限らないため、messagesに期待している値が取得できないのです。
それは困る。ではどうするか。
Angularが推奨している方法として、RxJSObservableクラスを使用します。
英語の意味のままですが「監視」の役割を持つ、コールバックやPromiseと似たような存在です。
ではこのObservableを使って先ほどの処理を書き換えていきましょう。

import { Observable, of } from 'rxjs';

export class MessageService {
    private messages: string[] = [];

    public add(text: string): Observable<string> {
        this.messages.push(text);
        return of(text);
    }

    public get(): Observable<string[]> {
        return of(this.messages);
    }
}

このofという関数は、Observableを生成してくれます。
引数に返したい値を設定して、Observableを返すようなメソッドにします。
次は実行するComponentを書き換えます。

export class MessageComponent implements OnInit {
    // 省略

    public send(message: string): void {
        this.messageService.add(message)
            .subscribe(() => this.receive());
    }

    public receive(): void {
        this.messageService.get()
            .subscribe(messages => this.messages = messages);
    }
}

Observableのsubscribeではデータの受け取りが完了した状態でのコールバック処理を設定することができます。
上のコードでは、メッセージ送信処理が成功したらメッセージ受信処理を行い、
受信処理が成功したらプロパティに返り値を格納するといった処理を書きました。
ちなみにこのsubscribeの第1引数に成功時の処理、第2引数にはエラー時の処理、第3引数に成功・エラー両方で実行される処理を書くことができます。

DEMOではちょっと見た目を変えて実装してみました。
全く会話の成立しない某アプリ風ページです笑

DEMOはこちら

SixthMessageComponentのコード
MessageServiceのコード

HTTPリクエストを投げるService

先程のはプロジェクト内データのやりとりでしたが、今度はHTTPリクエストをする場合。
たとえばAPIなどでデータの取得・登録をしたりですね。
Angularのcommom/httpにあるHttpClientクラスを使ってリクエストを作成します。
HttpClientを使う場合はAppModuleにHttpClientModuleをimportしておく必要がありますので忘れずにimportしてください。

import { HttpClientModule } from '@angular/common/http';

@NgModule({
    imports: [
        HttpClientModule,
        // 省略
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }

早速、HttpClientをServiceで使用していきましょう。
例として、よく使うであろうGET・POSTリクエストの書き方をご紹介します。

import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';

export class TestService {

    constructor(private http: HttpClient) { }

    // GETリクエスト
    public getList(): Observable<any[]> {
        const url = 'https://afm-example.com/item/list';
        const options = {responseType: 'json'};
        return this.http.get<any[]>(url, options);
    }

    // POSTリクエスト
    public addItem(): Observable<any> {
        const url = 'https://afm-example.com/item/add';
        const params = {name: 'アイテム1'};
        const options = {responseType: 'json'};
        return this.http.post<any>(url, params, options);
    }
}

HttpClientにあるget()post()を使用していきます。
どちらも第一引数にリクエスト先のURLをまず設定します。
optionsはレスポンスの形式やヘッダーの設定で、ここではJSON形式のレスポンスを受け取る設定をしています。
これはリクエストの形式に合わせて随時設定して下さい。
POSTに関してはリクエストパラメータも設定します。こちらも形式はそれぞれに合ったもの入れる必要があります。

基本的な送信を行うだけであればこれだけです。
あれ、割とコード短くて済むのね。と思う方もいるのではないでしょうか。
あとは先程と同じようにComponent側ではObservable.subscribe()をしてコールバックを書いていくだけです。

もっと言うとエラーハンドリングとかレスポンス値のマップとかの処理も必要になってくる場合もありますが、それはまた別の機会に。。。

こちらのDEMOではGitHubで公開されているAPIの中のリポジトリ検索を使用して動かしてみましたので試して見てください!
(HttpClientの処理は今後も使用することを考えCommonServiceにまとめて書いております)

DEMOはこちら

SixthApiComponentのコード
CommonServiceのコード
GithubServiceのコード

まとめ

他の言語でもHTTPリクエストを投げたりしてデータのやりとりをするとなると色々と面倒だったりもしますが、個人的にAngularは比較的簡潔にかけるといった印象があります。
(超絶便利なRxJSのおかげというのもありますが笑)
Serviceがあるとデータ処理が本当に便利ですし、共通処理もある程度まとまります。
もちろんローカル環境でもAPIは送信できるので皆さんも無料公開APIなどを利用してやってみてください!

次章予告

  • Pipe

参考

Angular最速マスターへの道シリーズはGitHubに公開しています。

Posted by Mao Miyaji
千葉にある夢の国を愛して止まない、元「魚のお姉さん」のエンジニア。PHP, TypeScriptメインで、暇さえあれば色々な言語を一かじり。

Other Posts: