Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Angular TIPS

Angular TIPS

自作のサービスを定義するには?(@Injectableデコレーター)

2018年3月12日

既存のコンポーネントからアプリ固有のロジックをサービスとして切り出すための基本的な方法を説明する。

  • このエントリーをはてなブックマークに追加

【対応バージョン】 Angular 5以降。v5時点で執筆しました。

 本連載では、ほとんどの処理をコンポーネントの中で記述しています。もっとも、これはあくまでコードの簡単化を目的としているためで、本格的なアプリ開発では、アプリ固有のロジックは、積極的にサービスとして切り出していくべきです。これによって、コンポーネントの見通しがよくなるだけでなく、それぞれのコンポーネントの役割分担が明確になり、単体テストも実施しやすくなります。

 例えば以下は、別稿「TIPS:配列の内容を順に出力するには?」で解説したサンプルを、コンポーネント/サービスに分離する例です。記事情報を取得するコードをサービスに切り分け、コンポーネントからはサービス経由で記事情報を取得します。

(1)サービスクラスを定義する

 まずは、サービスの実体となるArticleServiceクラスを定義します。

TypeScript
import { Injectable } from '@angular/core';

import { Article } from './article';

// 1@Injectableデコレーターを付与
@Injectable()
export class ArticleService {
  // 2全ての記事情報を配列として返す
  getArticles(): Article[] {
    return [
      {
        url: 'https://www.buildinsider.net/web/jqueryref',
        title: 'jQuery逆引きリファレンス',
        author: 'WINGSプロジェクト',
        released: new Date(2017, 10, 1)
      },
      ……中略……
    ];
  }
}
記事情報を取得するためのサービスクラスを定義(article.service.ts)

 サービスクラスであることの条件は、@Injectableデコレーターで修飾されていることです(1)。@Injectableデコレーターを付与することで、クラスをサービスとしてコンポーネントに引き渡せるようになります。

[Note]

 より厳密には、サービスクラスの中で別のサービスを利用していない場合には、@Injectableデコレーターは省略可能です。よって、本文の例でも、@Injectableデコレーターをコメントアウトしても、サンプルは正しく動作します。

 しかし、一般的にサービスクラスは、他のサービスに依存していることがほとんどでしょう(例えばHTTP通信を担当するHttpClientサービスは頻繁に利用します)。そもそも他のサービスを利用しているかどうかによって、@Injectableデコレーターを明示するかどうかを書き分けるのは、むしろ間違いのもとです。まずは単純に、サービスクラスには一律、@Injectableデコレーターを付与すると覚えておくことをお勧めします。

 あとは、サービスが提供すべき処理をメソッドとして定義するだけです。2では、全ての記事情報を配列として返すgetArticlesメソッドを定義しています。本稿では、記事情報をハードコーディングしていますが、もちろん、実際のアプリではネットワーク経由で問い合わせ、データを取得することになるでしょう。

 なお、インポートしているArticleクラスは次のように定義しています。

TypeScript
export class Article {
    url: string;
    title: string;
    author: string;
    released: Date;
  }
Articleクラスを定義(article.ts)
(2)モジュールでサービスを有効化する

 定義済みのサービスを利用するには、モジュールに登録します。

TypeScript
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 { }
定義済みのサービスをモジュールに登録するコード(app.module.ts)

 サービスを登録するのは、@NgModuleデコレーター(providersパラメーター)の役割です。配列([……])となっていることからも分かるように、複数のクラス(サービス)を列挙しても構いません。

 これで、AppModuleモジュール配下で、ArticleServiceサービスが有効になりました*1

  • *1 同じ要領で、@Componentデコレーターのprovidersパラメーターにサービスを登録した場合には、そのコンポーネントの配下でのみサービスは有効になります。
(3)コンポーネントからサービスをインスタンス化する

 サービスを利用するための準備ができたところで、コンポーネントからArticleServiceサービスを呼び出しているのが、以下のコードです。

TypeScript
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[];

  // 1ArticleServiceサービスを有効化
  constructor(private articleservice: ArticleService) {}

  // 2コンポーネントの初期化時に記事情報を取得
  ngOnInit() {
    this.articles = this.articleservice.getArticles();
  }
}
ArticleServiceサービスを利用するコンポーネント(app.component.ts)
サービス経由で取得した記事情報を一覧表示

 準備済みのサービスをインスタンス化するには、コンストラクターの引数にサービス型(ここではArticleService)の引数を渡すだけです(1*1)。これによって、コンポーネントをインスタンス化する際に、Angularによって自動的にインスタンス化されたサービスが引き渡されます。この機能を「あるオブジェクトが動作するのに必要な(=依存している)オブジェクトを注入する」という意味で、依存性注入と呼びます。

 依存性注入についてはAngularの核とも言える機能の一つなので、より詳細な設定については、後日別稿で解説の予定です。

  • *1 引数の頭に「private」とあるのは、注入されたサービスをプライベートプロパティとして登録しなさい、という意味です。

 あとは、初期化メソッド(ngOnInit2)でArticleService#getArticleメソッドにアクセスし、記事情報を取得するだけです。ngOnInitは、コンポーネントが初期化されたタイミングで呼び出される予約メソッドです。詳細は、後日別稿「TIPS:コンポーネントのライフサイクルとは?」で解説の予定です。

 以上の手順を完了したら、サンプルを実行してみましょう。記事情報の一覧が表示できていたら、サービスとコンポーネントの連携は正しくできています。

処理対象:サービス(Service) カテゴリ:基本
処理対象:デコレーター(Decorator) カテゴリ:サービス(Service)
API:Injectable(@Injectable) カテゴリ:@angular > core > DECORATOR(デコレーター)

※以下では、本稿の前後を合わせて5回分(第43回~第47回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

Angular TIPS
43. ページング処理を実装するには?(ngFor/slice)

ngForディレクティブとsliceパイプを使って、ページャー(=ページングのためのリンクリスト)を実装する方法を説明する。

Angular TIPS
44. 式の値によってメッセージを切り替えるには?(ngSwitch)

JavaScriptのswitch文のように、ある式を評価して、その式の値に応じて、表示するメッセージを切り替える方法を説明する。

Angular TIPS
45. 【現在、表示中】≫ 自作のサービスを定義するには?(@Injectableデコレーター)

既存のコンポーネントからアプリ固有のロジックをサービスとして切り出すための基本的な方法を説明する。

Angular TIPS
46. サーバーサイドと非同期通信するには?(HttpClientサービス)

HttpClientサービスを使ってWeb APIと非同期通信するための基本的な方法を説明する。

Angular TIPS
47. ファイルをサーバーにアップロードするには?(HttpClientサービス)

HttpClientサービスを使ってサーバーにファイルをアップロードするための基本的な方法を説明する。

サイトからのお知らせ

Twitterでつぶやこう!