AngularJSでServiceがControllerへDIされるまでの流れをざっくり見てみる

引き続きAngularJSのDeveloper Guideを読んで勉強しています。

今回はInjecting Services Into Controllersのサンプルコードで、Serviceが登録されてからControllerの中で利用されるまでAngularJSの中では何が起こっているのか、関係のあるところだけ辿って見てみました。

サンプルコードは以下の通り。

angular.
 module('MyServiceModuleDI', []).
 factory('notify', ['$window', function(win) {
    var msgs = [];
    return function(msg) {
      msgs.push(msg);
      if (msgs.length == 3) {
        win.alert(msgs.join("\n"));
        msgs = [];
      }
    };
  }]);
 
function myController($scope, notify) {
  $scope.callNotify = function(msg) {
    notify(msg);
  };
}

angular.module('MyServiceModuleDI', [])から時系列順に見てみます。

angular.module('MyServiceModuleDI', [])

angular.moduleは、AngularPublic.jsの(setupModuleLoader(window))で定義されます。angular.module関数の中身は、loader.jsで定義されているmoduleという関数です。

angular.module('MyServiceModuleDI', [])を実行すると、'MyServiceModuleDI'という名前でmoduleInstanceというオブジェクトがキャッシュされ、また戻り値として帰ってきます。

moduleInstanceは以下の様なプロパティを持っています。

        var moduleInstance = {
          _invokeQueue: invokeQueue,
          _runBlocks: runBlocks,
          requires: requires,
          name: name,
          provider: invokeLater('$provide', 'provider'),
          factory: invokeLater('$provide', 'factory'),
          service: invokeLater('$provide', 'service'),
          value: invokeLater('$provide', 'value'),
          constant: invokeLater('$provide', 'constant', 'unshift'),
          filter: invokeLater('$filterProvider', 'register'),
          controller: invokeLater('$controllerProvider', 'register'),
          directive: invokeLater('$compileProvider', 'directive'),
          config: config,
          run: function(block) {
            runBlocks.push(block);
            return this;
          }
        };
.factory('notify', ['$window', function(win) { (省略) }

factoryは先ほど出てきたmoduleInstanceのプロパティです。factory関数を実行すると、moduleInstanceのinvokeQueueの中に引数として渡したServiceのファクトリなどが追加されます。コードで表すと以下の様な感じです。

invokeQueue.push(['$provide', 'factory', ['notify',  ['$window', function(win) { (省略) }]]]);
createInjector(modules)

DOMContentLoadedイベントが発生するとアプリケーションのBootstrapプロセスに入ります。この中でcreateInjector(modules)が実行されるとModuleがロードされ、ServiceをControllerへDIすることが出来るようになります。

createInjectorはinjector.jsで定義されている関数です。createInjectorを実行すると内部的にproviderInjectorinstanceInjectorが作成されます。

Moduleがロードされる過程では、moduleInstanceのinvokeQueueに追加されたServiceのファクトリがproviderInjectorに登録されます。'notify'の場合以下の様な感じです。

return providerCache['notify' + "Provider"] = { $get: ['$window', function(win) { (省略) }] };

ServiceをControllerへDIする際にはinstanceInjectorが持っているキャッシュからServiceを取得しますが、キャッシュにない場合はproviderInjectorに登録されたファクトリを実行してServiceを取得します。

$injector.instantiate(constructor, locals);

アプリケーションのBootstrapプロセスでは$injector.instantiateが実行され、ServiceがControllerへDIされます。instantiateもinjector.jsで定義されています。