題して「頼りすぎないAngular」ということで、Angularの層をなるべく薄くアプリケーションを作るにはどうすればいいかというのを考えるシリーズです。 Angular良さそうなんだけどロックインされて捨てにくそう、という人々向けに、コードのモジュール性とフレームワーク非依存性を重視した実装パターンを試行錯誤します。
第一回目はAngularのHttpClientを覚えずに、人気のnpmモジュールである axios を使ってAngularアプリでAjaxする例を紹介します。 axiosはTypeScriptの型定義を同梱していて、インターセプターなどAngularのHttpClientと同じような機能が揃っています。
Live Example
今回の完成形はこちらです。
Random User Generatorからユーザー情報のJSONを取得し、画面に表示するアプリケーションです。
HttpClient
さて今回はAngular公式のHttpClientモジュールを使わずフレームワーク非依存のaxiosを使ってHttpClientを作ります。 次のようなファイルでアプリケーション用のカスタムインスタンスを生成してexportします。 今回は何もしませんが実際はデフォルトのヘッダを追加したりインターセプターを追加したりいろいろすると思います。
import axios from 'axios'; const instance = axios.create(); export default instance;
UserRepository
次に、作成したHttpClientを使ってAPI呼び出しを行うためのサービスクラスを作ります。 単純にimportして使うだけです
import { Injectable } from '@angular/core'; import httpClient from '../infrastructure/http-client'; @Injectable() export class UserRepository { async random() { const { data } = await httpClient.get('https://randomuser.me/api/'); const { results: [user] } = data; return user; } }
作成したサービスクラスをAppModuleに登録します。
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { UserRepository } from './repository/user'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent], providers: [UserRepository] }) export class AppModule { }
AppComponent
最後にコンポーネントからサービスを利用します。ここはAngularのDIを使います。
import { Component } from '@angular/core'; import { UserRepository } from './repository/user'; @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { user: User | null = null; constructor(private userRepo: UserRepository) { } ngOnInit() { this.fetchUser(); } async changeUser() { this.user = null; await this.fetchUser(); } private async fetchUser() { this.user = await this.userRepo.random(); } }
テンプレートでは名前と写真を表示して、ボタンを押すとchangeUser()
メソッドをトリガーするようにしています。これで完成です。
<h2>Angular with axios</h2> <ng-container *ngIf="user as user"> <h2>{{ user.name.first + ' ' + user.name.last | titlecase }}</h2> <img src="{{user.picture.large}}"> </ng-container> <button (click)="changeUser()">Change User</button>
テストとDI
サービスクラスのUserRepository
では直接httpClient
をimportして参照しましたが、AppComponent
ではDI経由でUserRepository
を参照しました。
この違いは、ユニットテストをどう書くかという観点で分かれています。
axiosはmoxiosというパッケージを使うことで簡単にaxiosのインスタンスをモック化できます。
そのため、httpClient
の初期化のテストにおいても、UserRepositry
の振る舞いのテストにおいても、DIは必要ありません。
しかしAppComponent
からUserRepository
を直接参照すると、簡単にはモックできません。
なのでテスト時にUserRepository
のモックを提供できるようにDI経由で参照しています。
利点・欠点
利点
- axiosは有名で人気なライブラリなので学習しやすい
- HttpClient部分はAngularじゃなくても使える
欠点
- RxJSの恩恵を受けられない
- 遅延実行
- 複数値の返却
RxJSの恩恵については、UserRepository
の層でfromPromise
関数などを使ってObservable
を返すようにすれば少しだけ解決します。
しかしFlux的な設計をするとなるとObservableなのはストアだけで良くて、fetch自体は単発で終わるほうが扱いやすいので特に欠点ではない気もしています。
まとめ
- AngularのHttpClientは必須ではない
- axiosのようなライブラリを使ってフレームワーク非依存の独自HttpClientを作れる
- DIするかどうかはテストしやすさを考える
次回は未定です。