Angularで自作の「サービス」を定義するには?(@Injectableデコレーター):Angular TIPS
既存のコンポーネントからアプリ固有のロジックをサービスとして切り出すための基本的な方法を説明する。
※現在では、Web標準技術を利用したアプリ開発が広く普及し、そのためのフレームワークも多数存在しています。その中でも主流のフレームワークの1つである「Angular」を活用し、そのための知識を備えることには大きな意味があります。本連載は、Angularユーザーに向けて、その使いこなしTIPSを紹介するものです。なお、本連載は「Build Insider」で公開していた連載「Angular Tips」を同サイトおよび筆者の了解を得たうえで、本フォーラムに移行したものです。記事はBuild Insiderで公開した状態のまま移行しているため、用語統一などの基準が@ITの通常の記事とは異なる場合があります。
【対応バージョン】
Angular 5以降。v5時点で執筆しました。
本連載では、ほとんどの処理をコンポーネントの中で記述しています。もっとも、これはあくまでコードの簡単化を目的としているためで、本格的なアプリ開発では、アプリ固有のロジックは、積極的にサービスとして切り出していくべきです。これによって、コンポーネントの見通しがよくなるだけでなく、それぞれのコンポーネントの役割分担が明確になり、単体テストも実施しやすくなります。
例えば以下は、別稿「TIPS:配列の内容を順に出力するには?」で解説したサンプルを、コンポーネント/サービスに分離する例です。記事情報を取得するコードをサービスに切り分け、コンポーネントからはサービス経由で記事情報を取得します。
(1)サービスクラスを定義する
まずは、サービスの実体となるArticleServiceクラスを定義します。
import { Injectable } from '@angular/core';
import { Article } from './article';
// [1]@Injectableデコレーターを付与
@Injectable()
export class ArticleService {
// [2]全ての記事情報を配列として返す
getArticles(): Article[] {
return [
{
url: 'http://www.buildinsider.net/web/jqueryref',
title: 'jQuery逆引きリファレンス',
author: 'WINGSプロジェクト',
released: new Date(2017, 10, 1)
},
……中略……
];
}
}
サービスクラスであることの条件は、@Injectableデコレーターで修飾されていることです([1])。@Injectableデコレーターを付与することで、クラスをサービスとしてコンポーネントに引き渡せるようになります。
[Note]
より厳密には、サービスクラスの中で別のサービスを利用していない場合には、@Injectableデコレーターは省略可能です。よって、本文の例でも、@Injectableデコレーターをコメントアウトしても、サンプルは正しく動作します。
しかし、一般的にサービスクラスは、他のサービスに依存していることがほとんどでしょう(例えばHTTP通信を担当するHttpClientサービスは頻繁に利用します)。そもそも他のサービスを利用しているかどうかによって、@Injectableデコレーターを明示するかどうかを書き分けるのは、むしろ間違いのもとです。まずは単純に、サービスクラスには一律、@Injectableデコレーターを付与すると覚えておくことをお勧めします。
あとは、サービスが提供すべき処理をメソッドとして定義するだけです。[2]では、全ての記事情報を配列として返すgetArticlesメソッドを定義しています。本稿では、記事情報をハードコーディングしていますが、もちろん、実際のアプリではネットワーク経由で問い合わせ、データを取得することになるでしょう。
なお、インポートしているArticleクラスは次のように定義しています。
export class Article {
url: string;
title: string;
author: string;
released: Date;
}
(2)モジュールでサービスを有効化する
定義済みのサービスを利用するには、モジュールに登録します。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Provider } from '@angular/core';
import { AppComponent } from './app.component';
import { ArticleService } from './article.service';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [ArticleService],
bootstrap: [AppComponent]
})
export class AppModule { }
サービスを登録するのは、@NgModuleデコレーター(providersパラメーター)の役割です。配列([……])となっていることからも分かるように、複数のクラス(サービス)を列挙しても構いません。
これで、AppModuleモジュール配下で、ArticleServiceサービスが有効になりました*1。
*1 同じ要領で、@Componentデコレーターのprovidersパラメーターにサービスを登録した場合には、そのコンポーネントの配下でのみサービスは有効になります。
(3)コンポーネントからサービスをインスタンス化する
サービスを利用するための準備ができたところで、コンポーネントからArticleServiceサービスを呼び出しているのが、以下のコードです。
import { Component, OnInit } from '@angular/core';
import { Article } from './article';
import { ArticleService } from './article.service';
@Component({
selector: 'app-root',
template: `
<table class="table">
<tr>
<th>タイトル</th><th>著者</th><th>公開日</th>
</tr>
<tr *ngFor="let article of articles">
<td><a href="{{article.url}}">{{article.title}}</a></td>
<td>{{article.author}}</td>
<td>{{article.released | date: 'mediumDate'}}</td>
</tr>
</table>
`,
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
articles: Article[];
// [1]ArticleServiceサービスを有効化
constructor(private articleservice: ArticleService) {}
// [2]コンポーネントの初期化時に記事情報を取得
ngOnInit() {
this.articles = this.articleservice.getArticles();
}
}
準備済みのサービスをインスタンス化するには、コンストラクターの引数にサービス型(ここではArticleService)の引数を渡すだけです([1]*1)。これによって、コンポーネントをインスタンス化する際に、Angularによって自動的にインスタンス化されたサービスが引き渡されます。この機能を「あるオブジェクトが動作するのに必要な(=依存している)オブジェクトを注入する」という意味で、依存性注入と呼びます。
依存性注入についてはAngularの核とも言える機能の一つなので、より詳細な設定については、後日別稿で解説の予定です。
*1 引数の頭に「private」とあるのは、注入されたサービスをプライベートプロパティとして登録しなさい、という意味です。
あとは、初期化メソッド(ngOnInit[2])でArticleService#getArticleメソッドにアクセスし、記事情報を取得するだけです。ngOnInitは、コンポーネントが初期化されたタイミングで呼び出される予約メソッドです。詳細は、後日別稿「TIPS:コンポーネントのライフサイクルとは?」で解説の予定です。
以上の手順を完了したら、サンプルを実行してみましょう。記事情報の一覧が表示できていたら、サービスとコンポーネントの連携は正しくできています。
処理対象:サービス(Service) カテゴリ:基本
処理対象:デコレーター(Decorator) カテゴリ:サービス(Service)
API:Injectable(@Injectable) カテゴリ:@angular > core > DECORATOR(デコレーター)
Copyright© Digital Advantage Corp. All Rights Reserved.