Angularのサービスを使いこなす!サービスの作成とシングルトンパターン。
この記事では、Angularの開発をしていくうえで欠かすことのできない「サービス」について説明していきたいと思う。
サービスをうまく使いこなすことにより、よりビジネスロジックが見える状態となり、コンポーネントごとの役割を明確に、また、簡潔にすることができる。
また、プロジェクトが抱える構造的な問題を解決する糸口となることも多いので、サービスについてはよく理解しておこう。
また、この記事ではよく質問されるサービスのシングルトン・ノンシングルトンパターンについても、それぞれサンプルコードを用いて説明していくので最後まで読んでほしい。
サービスとは?
まずAngularの「サービス」について説明しておこう。
サービスは一種のクラスでありTypeScriptファイルである。
サ-ビスはおもにビジネスロジックを記述するのに使用され、サービスの目的に基づいて命名されるのが基本。
サービスの使用例としては以下のようなものが考えられる。
- アプリの言語切替を担うサービス ⇒ LanguageService
- チケットの購入などを担うサービス ⇒ BookingService
- 商品の配送にまつわるロジックを担うサービス ⇒ DeliveryService
また、コンポーネントがビューと密接に結びついているのに対し、サービスはビューとの直接的な関りはほとんど持たない。
サービスはコンポーネントにインジェクトして使われる、サービスのスコープ内であればどのコンポーネントでも使用することができる。
サービスを作成する
では、サービスの作成方法について見ていこう。
プロジェクト内に「service」フォルダを作成して、その中に新しいサービスを作ろう。
コンポーネントと同様にAngular CLIを使えばコマンドを通じて簡単にサービスの追加をおこなうことができる。
ng generate service service-name
コマンドを実行するとサービスファイル(ts)とサービスのユニットテストのファイル(spec.ts)が作成される。
サービスファイルを開いて見てみると、Serviceの先頭に@Injectableアノテーションが付けられているのが分かるだろう。
@Injectable({
providedIn: 'root'
})
「providedIn: ‘root’」と書かれているのは、アプリのルートに対して登録されているということであり、アプリ内のどのコンポーネントからもサービスを呼び出すことができる。この場合、サービスはシングルトンとなる。
@Injectableアノテーションはサービスが他のサービスを利用していない場合は省略可能だが、ほとんどのケースではサービスは他のサービスを使用することとなる。@Injectableアノテーションは付けたままにしておこう。
テストのためにサービスに適当な関数をpublicで作成しておく。
public sendArticle(articleId: number) {
console.log('商品ID::' + articleId )
}
サービスを使用する
先ほど作成したサービスを使用するコンポーネントに読み込む。
サービスの読み込みは通常constructor内でおこなう。
constructor(private _deliveryService: DeliveryService) {
this._deliveryService.sendArticle(12);
}
読み込んだサービスはコンポーネントまたテンプレート内で使用することができる。テンプレートで使用する場合はアクセスレベルをpublicにする。
<button (click)="deliveryService.sendArticle(12)"></button>
シングルトン・ノンシングルトン
Angularのサービスはシングルトンとしても、ノンシングルトンとしても生成することができる。
それぞれに宣言の仕方やインポートの仕方が異なるので覚えておこう。
シングルトンとなるパターン
サービスをシングルトンとして使用する場合は以下の2つの方法のいずれかで宣言する。
@Injectableアノテーションを付ける
一つ目の方法は@Injectableアノテーションを使用してサービスを宣言する方法。Angular CLIのコマンドを使ってサービスを作成した場合は自動で@Injectableアノテーションが付けられる。
@Injectable({
providedIn: 'root'
})
モジュールにサービスを登録する
モジュールにサービスを登録した場合もそのサービスはシングルトンとなる。
app.module.tsを開き@NgModuleアノテーションのprovidersプロパティに目的のサービスを記述してサービスをインポートしておく。
NgModule({
providers: [MyService]
})
ノンシングルトンとなるパターン
次はサービスをノンシングルトンとして使用するパターンについて説明しよう。
コンポーネントにサービスを登録する
サ-ビスをノンシングルトンとして使用したい場合は、コンポーネントのprovidesに直接サービスを登録する。
@Component({
providers: [MyService]
})
この場合はサ-ビスには@Injectableアノテーションを付けず、モジュールにもサービスを登録しない。
サ-ビスインスタンスのテスト
「シングルトン」「ノンシングルトン」の両方のパターンを実際に実装して確認してしよう。
ここではテストのために以下のようなサービスを作成した。
export class NumberService {
public randomNumber: number;
constructor() {
this.randomNumber = Math.floor(Math.random() * 100) + 1;
}
public printOutNumber() {
console.log('Number::' + this.randomNumber);
}
}
- イニシャライズの際にランダムな数値を生成して変数に納める。
- printOutNumber()関数を呼び出せば数値が出力される。
2つの異なるコンポーネントからprintOutNumberを呼び出し、同じ数値が出力されればサービスはシングルトン、異なる数値が出力されればサービスはノンシングルトンと考えることができる。
まずはサービスに@Injectableアノテーションを付け、2つの任意のコンポーネントから関数を呼び出し、シングルトンであることを確認する。
次に@Injectableアノテーションをサービスから削除して、2つのコンポーネントのprovidesプロパティにサービスを登録して、アプリをアップデートする。
異なる数値が出力されたなら2つのコンポーネントで使用しているサービスは異なるインスタンスを持つこととなり、サービスはノンシングルトンであると言える。