pirosikick's diary

君のハートにunshift

Angularのservice, factory, providerの使い分け #scripty01

SCRIPTY#1 〜フロントエンド紳士・淑女のための勉強会〜 - connpass

「Angular.jsとThree.jsを一緒に使った時の話」というタイトルでLTしました。 資料とかは後日会社のブログにて公開されるのでもう少々お待ちください。

で、自分の発表の時に「service, factory, providerをどう使い分けているか」という質問があって、なんかうまく回答できたか自信がなかったのと、ちょっと間違ったことを言ってしまったので、ここで補足したいと思います。

単純な違い

Service

angular.module('App')

// exampleをserviceで定義
.service('example', function () {
  this.methodA = function () {
    console.log('call methodA!!');
  }
})

// exampleをInjectする
.controller('Ctrl', ['example', function (example) {
  example.methodA(); // 'call methodA!!'とコンソールに出力される
}]);

Factory

  • .factory()に渡した関数を実行した返り値がDIされる
angular.module('App')

// exampleをserviceで定義
.factory('example', function () {

  function Example() { ... }
  
  Example.prototype.methodA = function () {
    console.log('call methodA!!');
  }
  
  // これがcontrollerや他のserviceにInjectされる
  return new Example()
})

// exampleをInjectする
.controller('Ctrl', ['example', function (example) {
  example.methodA(); // 'call methodA!!'とコンソールに出力される
}]);

Provider

  • factory, serviceと大きく違うのがconfigブロックに渡せること
    • configブロックはアプリケーション開始前に実行される
    • configブロックでproviderのプロパティの変更ができる
  • providerの$getを実行した返り値がDIされる
angular.module('App')

// exampleをproviderで定義
.provider('example', function () {

  // 外に晒す設定値
  this.options.message = 'call method of example.';
  
  // こいつの返り値がcontrollerや他のserviceにInjectされる
  this.$get = function () {
  
    var message = this.options.message;

    // これがInjectされる
    return {
      methodA: function () {
        console.log(message);
      }
    }
  }
})

// configブロックで使う場合、
// サービス名 + ProviderでInjectする。
// 今回の場合、example + Provider = exampleProvider
.config(['exampleProvider', function (exampleProvider) {

  // 設定値をいじり挙動を変えることができる
  exampleProvider.options.message = 'change behavior of methodA.';

}])

// exampleをInjectする
.controller('Ctrl', ['example', function (example) {
  example.methodA(); // 'change behavior of methodA.'とコンソールに出力される
}]);

使い分け

provider

  • 外部に公開するライブラリなど、再利用性を高くしたい場合にproviderを使う
    • 利用者によって設定を変更し挙動を変える必要がある場合など
    • $httpもproviderで定義されていて、headerのデフォルト値などが変更可能
    • ngモジュールのproviderを見るとどういうところで使うべきがだいたいわかるかも
    • 公式の見解
      • providerがコアで、service, factory, value, constantはproviderのシンタックスシュガーである
      • providerは冗長&多くの機能があるが、
      • providerを使うのがoverkillになってしまうケースが多いだろう

serviceとfactory

  • .service.factoryについては「これだ!」っていう理由が無い限り、
  • シンプルな.serviceで定義すればいいんじゃないかと思う。
これだ!っていう理由
  • Angularの外で定義しているクラスとかをそのままインスタンス化して使いたい
  .factory('example', function () {
    // Angularの外で定義しているクラスをそのまま返す
    return new OutOfAngularWorldClass();
  })
  • valueに依存関係がほしい、または初期化関数が欲しい場合
  // .valueは第2引数がそのままInjectされる
  .value('apiKey', 'XXXXXXX')

  // 依存関係がほしい&初期化したい
  .factory('apiKey', ['otherService', function (otherService) {
    // 例えば別のサービスの値や関数を使って初期化して返す
    return otherService.someValue + 'XXXXXXX';
  }]);

注意点

provider, factory, serviceともにシングルトンになっていて、生成後は内部でキャッシュされる。 なので下記のようなfactoryは最初にInjectされたタイミングで生成された値で固定されてしまう

.factory('randomValue', function () {
  // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
  var guid = (function() {
    function s4() {
      return Math.floor((1 + Math.random()) * 0x10000)
                .toString(16)
                .substring(1);
    }
    return function() {
      return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
            s4() + '-' + s4() + s4() + s4();
    };
  })();
  
  return guid();
});

Angular2.0でどうなるか

今日のQ&Aの時に「2.0ではfactory, serviceはなくなるかも」と言いましたが、あれは自分の記憶違いでした。 ごめんなさい。。。

正確には、

  • .config, .constant, .providerが廃止され
  • .constant.valueに一本化、
  • .providerは下記のように.configブロックではなく.valueで設定値の変更が可能になる
  module.value('$http.config', {
    defaultHeaders: {
      // ...
    }
  });

でした。

2.0でDI周りがどうなるかは以前ブログに書いたのと、ここにコードと仕様があるのでそちらを見てください。(ブログの方はちょっとふるくなっているかもです)

まとめ

  • 今はAngular.jsの本が2冊も出てるのでそれを見てからやればいいと思う。
  • 公式のドキュメントが充実しているのでそちらを出来る限り見るのと、
  • derectiveに関してはngモジュールのソースを見て勉強するといいと思う