前編: ngularJSのfactoryとserviceを読み解く(前編)
前回のAngulaJS!(なんちゃらライブ的な) factoryやserviceといったAngularJSの機能をドキュメントを読んだりぐーぐる先生に聞いたりしてもまったくさっぱりよくわからなかったのでAngularJSのコードを追ってみた。
factoryは初期化処理をInjectorで一度だけ実行してその結果をAngularJSのcacheFactoryを使ってキャッシュしているところまではわかった。
しかし、serviceはよくわからなかった。 よくわからないので振り返りから。
.service(name, fn)
- serviceが呼ばれる
- $injector.instantiateにfnが渡される
- $injector.instantiateで別のクラス(仮にAとする)を作成してprototypeを継承する
- $injector.instantiateでクラスAのインスタンスを生成して$injector.invokeにserviceに指定したfnと共に渡す
- $injector.invokeでDIに指定したDependenciesを解析、取得
- $injector.invokeで、引数をdependenciesに、コンテキストをクラスAのインスタンスにしていしてfnを実行する
- $injector.invokeの返り値がobjectまたはfunctionであればそれをfactoryに渡す
- $injector.invokeの返り値がobjectでもfunctionでもなければクラスAのインスタンスをfactoryに渡す
つまり、こういうことだ!
// serviceに登録するfunction(コンストラクタ) var User = function (options) { this.name = options.name || null; this.age = options.age || null; this.set = function (column, value) { this[column] = value; }; this.get = function (column) { return this[column]; }; }; // $injector.instantiate var Constructor= function () {}; Constructor.prototype = User.prototype; var instance = new Constructor(); // $injector.invoke User.apply(instance, [{name: "tarou", age: 10}]); var user = instance;
・・・実にわかりにくい。
気をつけなければならないのは、serviceの第二引数は初期化コールバック関数ではなく、クラスのコンストラクタであること。
factoryとおんなじ用に使うのは不適切で、angularとは別に定義したクラスを指定する(app.service("name", ["$rootScope", MyServiceClass])
のように)か、コンストラクタで完結するクラスを定義すべきだ。
var User = function (options) { this.name = options.name || null; this.age = options.age || null; this.set = function (column, value) { this[column] = value; }; this.get = function (column) { return this[column]; }; }; app.service('user', [User]);
app.service('user', [function () { this.name = options.name || null; this.age = options.age || null; this.set = function (column, value) { this[column] = value; }; this.get = function (column) { return this[column]; }; }]);
serviceはどういう時に使うべき?
factoryとの違いとして、serviceはインスタンスの生成を保証し、かつ同一の名前空間で他のインスタンスは作られない。
デザインパターンで言うところのSingletonであり、アプリケーション内で整合性を管理する用途にもちいられるのだと想定できる。
たとえば、複数のController間で一つのリソースを管理したい場合などがそれに該当すると思われる。
下手に$rootScopeに値を入れたりするよりは固く実装できるのではないだろうか。
まとめ
serviceはSingletonなので、それなりの扱いを用意しよう。 serviceメソッドの第二引数はコンストラクタなのでそれを念頭に置いて利用しよう