プロバイダーについて

概要

Webアプリケーションは、何かを行うためのオブジェクトの組み合わせで構成されています。 これらのオブジェクトは、アプリケーションが動作するためにインスタンス化し、互いに連携し合うことが必要になります。 Angularアプリケーションのオブジェクトの多くはインスタンス化され、 インジェクターサービスによって、 自動的に互いに連携しあいます。

インジェクターに、サービス特殊オブジェクトの、2つのタイプのオブジェクトを作成します。

サービスは開発者がサービスを書くことによって、APIが定義されたオブジェクトです。

特殊オブジェクトは、特定のAngularフレームワークAPIに準拠します。 これらのオブジェクトはコントローラー、ディレクティブ、フィルター、アニメーションいずれかの1つです。

インジェクターは、これらのオブジェクトを作成する方法を知っている必要があります。 そのオブジェクトを作成ための"レシピ(作り方)"を登録することによって、それをインジェクターに伝えます。 これには5つのタイプのレシピが存在します。

最も冗長ではありますが、最も包括的なものの1つは、プロバイダ(Provider)レシピです。 残りの4つのタイプのレシピ - Value、ファクトリー(Factory)、サービス(Service)と定数(Constant)は、 プロバイダレシピの構文糖(シンタックスシュガー)に過ぎません。

それでは、様々なレシピのタイプにおけるサービスの作成・使用の際の筋道の違いについて確認していきましょう。 出来るだけ単純なケースとして、コードの様々な場所で共有することが必要とされる文字列があり、 これをValueレシピを通して実現する事から始めてみましょう。

モジュール上の単語

これらの全てのオブジェクトの作成と互いに連携する方法をインジェクターに伝えるために、 "レシピ"の登録が必要になります。 各レシピは、オブジェクトの識別子とこのオブジェクトの作成方法の説明を持ちます。

各レシピは、Angularモジュールに所属します。 Angularモジュールは、1つ以上のレシピを保持する入れ物の役割をします。 また、手動でモジュールの依存性を保持するのは楽しいものでは無いため、 モジュールには他のモジュール同様に依存性についての情報を含めることが可能です。

Angularアプリケーションがアプリケーションモジュールを与えられて開始されると、 Angularは、コアの"ng"モジュール、アプリケーションモジュール、それらに依存するモジュール内で定義されている全てのレシピを結合したレシピの登録を作成する、 新しいインジェクターのインスタンスを作成します。 インジェクターは、アプリケーションのためにオブジェクトの作成が必要となった際に、 登録されているレシピにその事を助言(consult)します。

Valueレシピ

とてもシンプルな"clientId"と呼ばれる、リモートAPIのために使用される認証IDの文字列を提供するサービスが必要であるとします。 これは次のように定義します。

var myApp = angular.module('myApp', []);
myApp.value('clientId', 'a12345654321x');

myAppと呼ばれるモジュールを作成し、単純に文字列のためのclientIdサービスを構成するための"レシピ"を含む、 モジュールの定義が指定されていることが確認出来ると思います。

そして下記は、Angularのデータバインディングを通して、それを表示する方法になります。

myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
  this.clientId = clientId;
}]);
<html ng-app="myApp">
  <body ng-controller="DemoController as demo">
    Client ID: {{demo.clientId}}
  </body>
</html>

この例では、Valueレシピを使用して値を定義し、 DemoControllerがサービスに"clientID"を問い合わせた際にそれを提供しています。

では、より複雑な例を見て行きましょう。

ファクトリー(Factory)レシピ

Valueレシピは非常に単純に書くことが出来ますが、サービスを作成する際に必要とされるいくつかの重要な機能が欠落しています。 それではValueレシピをより強力にしたFactoryについて見て行きましょう。 Factoryレシピは、下記の機能を追加します。

  • 他のサービス(依存した)を使用する機能
  • サービスの初期化
  • 初期化の遅延

Factoryレシピは、0個以上の引数(これらは他のサービスに依存します)を取る関数を使用する新しいサービスを構築します。 この関数の戻り値は、このレシピによって作成されたサービスインスタンスです。

注意: Angularの全てのサービスはシングルトンです。 これは、インジェクターは各レシピを使用して、1度だけオブジェクトを作成することを意味します。 インジェクターはその後、今後それを必要とするもののために、その参照をキャシュします。

FactoryはValueレシピをより強力にしたものであるため、これで同じサービスを構築することが出来ます。 先ほどのclientIdレシピの例を使用して、下記のようにFactoryレシピに書き換えることが出来ます。

myApp.factory('clientId', function clientIdFactory() {
  return 'a12345654321x';
});

ただし、与えられるトークンは単なる文字列のリテラルであり、 このような場合であれば、まだコードを簡単に書けるValueレシピの方が適しています。

しかしながら、認証のためのリモートAPIを使用してトークンを算出して作り出すサービスも必要です。 このトークンは'apiToken'と呼ばれ、clientId値を元に計算され、 ブラウザのローカルストレージに秘密裏に格納されます。

myApp.factory('apiToken', ['clientId', function apiTokenFactory(clientId) {
  var encrypt = function(data1, data2) {
    // NSA-proof暗号化アルゴリズム
    return (data1 + ':' + data2).toUpperCase();
  };

  var secret = window.localStorage.getItem('myApp.secret');
  var apiToken = encrypt(clientId, secret);

  return apiToken;
}]);

上記のコードは、clientIdサービスに依存したapiTokenサービスをFactoryレシピを通して定義する方法の一例になります。 このFactoryサービスは、認証トークンを作り出すためにNSA-proof暗号化を使用してます。

注意: ファクトリー関数の名前に"Factory"を付ける(例: apiTokenFactory)のは、とても良い慣習です。 この名付けは必須ではありませんが、コードを追う際やデバッグ中にスタックトレースする際に役立ちます。

Valueレシピ同様Factoryレシピは、それがプリミティブ、オブジェクトリテラル、関数、またカスタムタイプのインスタンスであっても あらゆるタイプのサービスを作り出すことが出来ます。

サービス(Service)レシピ

JavaScript開発者は、オブジェクト指向のコードを書くために、よくカスタムタイプを使用します。 カスタムタイプのインスタンスであるunicornLauncherサービスを通して、 宇宙にユニコーンを浮かべる(?)方法について見て行きましょう。

function UnicornLauncher(apiToken) {

  this.launchedCount = 0;
  this.launch() {
    // apiTokenを含む、リモートAPIへのリクエストを作ります
    ...
    this.launchedCount++;
  }
}

まずユニコーンを配置するところから始めますが、UnicornLauncherはapiTokenに依存することに注意してください。 これはFactoryレシピを使用して、apiTokenへの依存を安全に行うことが出来ます。

myApp.factory('unicornLauncher', ["apiToken", function(apiToken) {
  return new UnicornLauncher(apiToken);
}]);

ただし、これはServiceレシピに最も適したユースケースです。

ServiceレシピはValueまたはFactoryレシピのようなサービスを作りますが、 new演算子でコンストラクタを実行することによってそれを行います。 このコンストラクタは、このタイプのインスタンスによって必要とされる依存性を表す引数を、 0、または1つ以上取得することが可能です。

注意: Serviceレシピは、Constructor Injectionと呼ばれるデザインパータンに従います。

UnicornLauncherタイプのために、常にコンストラクタを持つために、 上記のFactoryレシピをこのようなServiceレシピに置き換えることが出来ます。

myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);

とてもシンプルになりました!

注意: 我々はサービスレシピを'Service'と呼ぶことにしましたが、我々はこの事を後悔しており、 ご存知の通り、どういうわけかこの悪行をそのまま公開してしまいました。 これは子孫の1つに'Children'(子供達)と名付けるようなものです。 「Boy, that would mess with the teachers.」

プロバイダ(Provider)レシピ

ここまで、2種類以上のレシピを確認してきました。 これらはどれも非常に専門的で、稀にしか使用されません。 既に最初に説明したように、Providerレシピはコアとなるレシピのタイプで、 全ての他のレシピのタイプはこれのシンタックスシュガーに過ぎません。 これは最も多機能な最も冗長なレシピですが、ほとんどのサービスでそれが行き過ぎてしまいます。

Providerレシピは、$getメソッドを実装したカスタムタイプとして統語(文法?)的に定義されます。 このメソッドは、Factoryレシピを使用したようなファクトリー関数に過ぎません。 事実、もしFactoryレシピを定義すると、ファクトリー関数に設定するための$getメソッドを持つ空のProviderタイプが、 目に付かないところで自動的に作成されます。

アプリケーションが開始される前にアプリケーション側の設定のためのAPIを公開したい場合にのみ、Providerレシピを使用すべきです。 これはアプリケーション間に、僅かな振る舞いの違いがあるようなサービスを再利用するようなケースでのみ、通常は有益になります。

unicornLauncherサービスが、素晴らしいことに多くのアプリケーションで使用されているとしましょう。 デフォルトで、このランチャーは保護シールド無しで宇宙にユニコーンを放ちます。 ただし幾つかの惑星では大気が厚く、銀河旅行へそれを送りだす前にアルミ箔で各ユニコーンを包まなければならず、 さもなければ、大気を通過する際に燃え尽きてしまいます。 アルミ箔の保護が必要とするアプリの各発射に対してそれを使用することを設定できるのであれば、それは素晴らしい事です。 これを下記のようにして設定します。

myApp.provider('unicornLauncher', function UnicornLauncherProvider() {
  var useTinfoilShielding = false;

  this.useTinfoilShielding = function(value) {
    useTinfoilShielding = !!value;
  };

  this.$get = ["apiToken", function unicornLauncherFactory(apiToken) {

    // UnicornLauncherコンストラクタも、useTinfoilShielding引数を受け付け、
    // それを使用するように変更されたとみなします。
    return new UnicornLauncher(apiToken, useTinfoilShielding);
  }];
});

アルミ箔の変更をアプリケーション内に行うために、config関数をモジュールAPIを通して作成し、 UnicornLauncherProviderがそれに注入されている必要があります。

myApp.config(["unicornLauncherProvider", function(unicornLauncherProvider) {
  unicornLauncherProvider.useTinfoilShielding(true);
}]);

unicornのプロバイダがconfig関数に注入されることに注意してください。 この注入は、インスタンス化され配置(注入された)全てのプロバイダインスタンスのみの、 通常のインスタンスのインジェクターとは異なるプロバイダインジェクターによって行われます。(翻訳に自信なし)

アプリケーションの起動中、Angularが全てのサービスを作り終わる前に、設定と全てのプロバイダのインスタンス化が行われます。 我々はこれをアプリケーションライフサイクルの構成(configuration)フェーズと呼んでいます。 サービスは、この時点ではまだ作られていないため、このフェーズ中にアクセス可能な状態ではありません。

プロバイダとのやり取りが許可されない、この構成フェーズが終わると、サービス作成のプロセスが開始されます。 我々はアプリケーションのこのライフサイクルの部分を実行(run)フェーズと読んでいます。

定数(Constant)レシピ

Angularがどのようにして構成フェーズと実行フェーズのライフサイクルを分離し、 どのようにしてconfig関数を通して自分のアプリケーションに構成を提供するかを学んできました。 config関数はサービスが利用出来ない構成フェーズ内で実行するため、 Valueレシピを介して作成されるシンプルな値のオブジェクトであってもアクセスすることは出来ません。

URL接頭辞のような単純な値であれば、依存関係や構成を持たなくても、 構成、実行フェーズの両方で手軽に利用可能にする事が出来ます。 これを行うのがConstantレシピです。

unicornLauncherサービスは、構成フェーズ中にもし放たれる先の惑星名が提供されれば、 ユニコーンにその名前をスタンプするものとします。 惑星名はアプリケーションで一定であり、また様々なコントローラーでアプリケーション実行中に使用されます。 この惑星名を一定として、下記のように定義することが可能です。

myApp.constant('planetName', 'Greasy Giant');

unicornLauncherProviderに、これを下記のように設定します。

myApp.config(['unicornLauncherProvider', 'planetName', function(unicornLauncherProvider, planetName) {
  unicornLauncherProvider.useTinfoilShielding(true);
  unicornLauncherProvider.stampText(planetName);
}]);

Constantレシピは実行中にも利用可能なValueレシピのようなものであるため、 コントローラーとテンプレート上でも使用することが可能です。

myApp.controller('DemoController', ["clientId", "planetName", function DemoController(clientId, planetName) {
  this.clientId = clientId;
  this.planetName = planetName;
}]);
<html ng-app="myApp">
  <body ng-controller="DemoController as demo">
   Client ID: {{demo.clientId}}
   <br>
   Planet Name: {{demo.planetName}}
  </body>
</html>

特別な用途のオブジェクト

最初に少し触れましたが、サービス間で異なる特別な用途のオブジェクトがあります。 これらのオブジェクトはフレームワークのプラグインとして拡張し、 それ故Angularによって指定されたインターフェースの実装がされなければいけません。 これらのインターフェースには、コントローラー、ディレクティブ、フィルター、アニメーションが該当します。

これらの特別なオブジェクト(Controllerオブジェクトを除く)を作るためのインジェクターへの指示には、 裏でFactoryレシピが使用されています。

Directive APIを介して、先程定義した惑星名(このケースでは"Planet Name: Greasy Giant")を表示する、 planetNameのConstantに依存した非常にシンプルなコンポーネントを作成する方法について見て行きましょう。

ディレクティブは、Factoryレシピを介して登録されるため、 Factoryと同じ文法として使用することが出来ます。

myApp.directive('myPlanet', ['planetName', function myPlanetDirectiveFactory(planetName) {
  // ディレクティブ定義オブジェクト
  return {
    restrict: 'E',
    scope: {},
    link: function($scope, $element) { $element.text('Planet: ' + planetName); }
  }
}]);

その後、下記のようにしてコンポーネントを使用します。

<html ng-app="myApp">
  <body>
   <my-planet></my-planet>
  </body>
</html>

Factoryレシピの使用は、Angularのフィルターとアニメーションの定義も可能にしてくれますが、コントローラーは少し特殊です。 コントローラーは、自身のコンストラクタ関数のために、引数を自身の依存性として宣言するカスタムタイプとして作成します。 このコンストラクタは、その後モジュールに登録されます。 それでは、最初の例で作成したDemoControllerを見てみましょう。

myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
  this.clientId = clientId;
}]);

DemoControllerは、アプリケーションがDemoControllerのインスタンスを必要とする度に、 自身のコンストラクタを介してインスタンス化されます。 (このシンプルなアプリケーションでは1度だけです) サービスとは異なり、コントローラーはシングルトンではありません。 コンストラクタは全ての要求されたサービスと一緒に(?)呼び出され、このケースではclientIdサービスになります。

これまでの重要なポイントについて、まとめてみましょう。

  • インジェクターはレシピを使用して2つのタイプのオブジェクトを作成します。 1つはサービスで、もう1つは特殊な目的のオブジェクトです。
  • オブジェクトを作成する方法を定義するレシピには、 Value、Factory、Service、Provider、Constantのの5種類があります。
  • FactoryとServiceは、最もよく使用されるレシピです。 この2つの違いは、Serviceレシピはカスタムタイプのオブジェクトに向いていることに対し、 FactoryはJavaScriptのプリミティブと関数を生み出すことが出来ます。
  • Providerレシピは、コアなレシピであり、他の全てのレシピはこれのシンタックスシュガーに過ぎません。
  • Providerは最も複雑なタイプのレシピです。 グローバル設定を必要とする再利用コードを含むものを構築しない限り、必要ありません。
  • コントローラーを除く全ての特殊な用途のオブジェクトは、Factoryレシピを介して定義されます。
機能 / レシピタイプ Factory Service Value Constant Provider
依存性を持つことが可能 yesyesnonoyes
注入が使いやすいタイプ noyesyes*yes*no
設定フェーズで利用可能なオブジェクト nononoyesyes**
関数/プリミティブを作成可能 yesnoyesyesyes

* 動的にnew演算子を使用するため、初期化にコストが掛かります。

** config(構成)フェーズ中はServiceのオブジェクトは利用できませんが、Providerインスタンスなら利用できます。 (unicornLauncherProviderの例は上記を確認してください。)

 Back to top

© 2017 Google
Licensed under the Creative Commons Attribution License 3.0.

このページは、ページトップのリンク先のAngularJS公式ドキュメント内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。

  • AngularJSの更新頻度が高いため、元のコンテンツと比べてドキュメントの情報が古くなっている可能性があります。
  • "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。