AngularJs 入門(二)--模塊

模塊

模塊是指寫Angular應用的代碼片斷,這樣可使代碼分離開來,所以代碼會更好維護,可讀和測試。還能夠在module裏定義代碼依賴關係,能夠調用一個模塊,再在代碼中定義這個模塊依賴於另外兩個。bootstrap

angular模塊經過angular.module(name,requires, configFn)方法生成:
1>參數name是模塊名稱;
2>參數requires表示依賴模塊數組。
若是不設置requires參數,調用angular.module(name)方法表示獲取這個模塊;若是肯定新模塊沒有依賴關係,必須設置requires爲空數[arguments];
若是不是字符串,則必須是方法(或數組格式的方法),那麼,這個方法就表明了一個模塊。
3>參數configFn是方法或數組,負責在模塊初始化時作一些配置,若是是數組,最後一個元素必須是方法。
方法configFn並非在執行angular.module()的時候當即執行,而是當這個模塊被第一次使用時,由注入器調用執行。同時,查看方法configFn中的this就會發現,這個this在瀏覽器中指向的是window,而不是module。並且,方法configFn只會執行一次,所以同一個angular模塊不會重複配置。
實例:
angular.module("MetronicApp")
    .controller('AppController', 
    ['$scope', '$rootScope', 'AppCommonService', 'DictService', 
    function ($scope, $rootScope, AppCommonService, DictService) {
        // code
    });
}]);

AngularJS容許咱們使用angular.module()方法來聲明模塊,這個方法可以接受兩個參數, 第一個是模塊的名稱,第二個是依賴列表,也就是能夠被注入到模塊中的對象列表。
同名模塊數組

已經初始化的angular模塊保存在一個叫modules的緩存對象中,key是模塊名,value是模塊對象。因此,定義一個同名的模塊,等於覆蓋以前的模塊。瀏覽器

服務注入

angular模塊只保留服務的定義,如今再來理解服務是如何加入注入器的。緩存

模塊定義服務、服務提供商;
 注入器根據模塊依賴關係加載模塊,實例化全部服務提供商;
 應用須要服務,注入器根據服務名尋找服務提供商,服務提供商實例化服務。

以上只是理論,如今從代碼層面來看Angular是如何實現的。app

每一個angular模塊內置有三個數組,invokeQueue保存如何注入服務提供商和值的信息;configBlocks保存模塊的配置信息;runBlocks保存這個模塊的執行信息。模塊被使用的時候,注入器根據invokeQueue中的信息,實例化服務提供商;根據configBlocks中的信息對服務提供商作一些額外的處理;根據runBlocks中提供的信息,調用前面的服務提供商提供的服務執行模塊須要完成的工做。
angular模塊提供了不少方法來填充這三個數組,好比config()、run()等。三個數組的元素也是數組,具體元素格式參考後面的說明。
如下是源碼,能夠清楚看到每一個模塊所要依賴的服務和運行機制,稍後詳細介紹。ide

var angular = ensure(window, 'angular', Object);
  angular.$$minErr = angular.$$minErr || minErr;
  return ensure(angular, 'module', function() {
    var modules = {};
    return function module(name, requires, configFn) {
      var assertNotHasOwnProperty = function(name, context) {
        if (name === 'hasOwnProperty') {
          throw ngMinErr('badname', 'hasOwnProperty is not a valid {0}     name', context);
        }
      };

      assertNotHasOwnProperty(name, 'module');
      if (requires && modules.hasOwnProperty(name)) {
        modules[name] = null;
      }
      return ensure(modules, name, function() {
        if (!requires) {
          throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
         "the module name or forgot to load it. If registering a module ensure that you " +
         "specify the dependencies as the second argument.", name);
        }

    /** @type {!Array.<Array.<*>>} */
    var invokeQueue = [];

    /** @type {!Array.<Function>} */
    var configBlocks = [];

    /** @type {!Array.<Function>} */
    var runBlocks = [];

    var config = invokeLater('$injector', 'invoke', 'push', configBlocks);

    /** @type {angular.Module} */
    var moduleInstance = {
      // Private state
      _invokeQueue: invokeQueue,
      _configBlocks: configBlocks,
      _runBlocks: runBlocks,
      requires: requires,
      name: name,
      provider: invokeLaterAndSetModuleName('$provide', 'provider'),
      factory: invokeLaterAndSetModuleName('$provide', 'factory'),
      service: invokeLaterAndSetModuleName('$provide', 'service'),
      value: invokeLater('$provide', 'value'),
      constant: invokeLater('$provide', 'constant', 'unshift'),
      decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
      animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
      filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
      controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
      directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
      config: config,
      run: function(block) {
        runBlocks.push(block);
        return this;
      }
    };
    if (configFn) {
      config(configFn);
    }
    return moduleInstance;
    function invokeLater(provider, method, insertMethod, queue) {
      if (!queue) queue = invokeQueue;
      return function() {
        queue[insertMethod || 'push']([provider, method, arguments]);
        return moduleInstance;
      };
    }
    function invokeLaterAndSetModuleName(provider, method) {
      return function(recipeName, factoryFunction) {
        if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
        invokeQueue.push([provider, method, arguments]);
        return moduleInstance;
      };
    }
  });
};

});函數

調用隊列 – invokeQueue

數組元素爲[‘provider’, ‘method’, arguments]。
舉例– 添加一個Controller:測試

angular.module('ngAppDemo',[])        
.controller('ngAppDemoController',function($scope) {       
      $scope.a= 1;      
      $scope.b = 2;      
});

這段代碼等於:動畫

invokeQueue.push(['$controllerProvider','register',
    ['ngAppDemoController', function(){
    //code

}]]);

注入器根據這個信息,就會調用$controllerProvider的register方法註冊一個ngAppDemoController。ui

配置隊列 – configBlocks

元素格式爲['$injector', 'invoke', arguments]。

運行隊列 – runBlocks

元素要求是方法,或者是數組,數組最後一個元素是方法。

angular模塊實例屬性和方法

屬性
requires
表示模塊的依賴數組,即angular.module方法的requires參數。
name
模塊的名字。
_invokeQueue、_configBlocks、_runBlocks
分別對應invokeQueue、configBlocks、runBlocks。
方法

模塊的如下方法最後所有會返回模塊實例自己,造成執行鏈。

animation()
調用這個方法表示這個模塊將在$animateProvider中註冊一個動畫服務。
原理:在invokeQueue尾部插入['$animateProvider', 'register', arguments]。
config()
調用這個方法,表示給這個模塊追加一個配置方法。
原理:在configBlocks尾部插入['$injector','invoke', arguments]。
constant()
調用這個方法表示這個模塊將給默認的$provider註冊一個常量。
原理:在invokeQueue首部插入['$provide', 'constant', arguments]。
controller()
調用這個方法表示模塊將在$controllerProvider中註冊一個控制器。
原理:在invokeQueue尾部插入['$controllerProvider', 'register', arguments]。
directive()
調用這個方法表示這個模塊將在$compileProvider中註冊一個指令。
原理:在invokeQueue尾部插入['$compileProvider', 'directive', arguments]。
factory()
調用這個方法表示這個模塊中將生成一個服務工廠(隱式建立一個了服務提供商)。
原理:在invokeQueue尾部插入['$provide', 'factory', arguments]。
filter()
調用這個方法表示這個模塊將在$filterProvider中註冊一個過濾器。
原理:在invokeQueue尾部插入['$filterProvider', 'register', arguments]。
provider()
調用這個方法表示這個模塊將添加一個服務提供商。
原理:在invokeQueue尾部插入['$provide', 'provider', arguments]。
run(block)
調用這個方法表示這個模塊將執行某個功能塊,block能夠是方法,也能夠是數組。
原理:在invokeQueue尾部插入block。
service()
調用這個方法表示這個模塊將註冊一個服務(隱式建立了一個服務提供商)。
原理:在invokeQueue尾部插入['$provide', 'service', arguments]。
value()
調用這個方法表示這個模塊將註冊一個變量(隱式建立了一個服務提供商)。
原理:在invokeQueue尾部插入['$provide', 'value', arguments]。

服務注入器(Service Injector) & 服務提供商(Service Provider)

在Angular中,服務多是對象、方法、或者一個常量值。服務由服務提供商建立,而服務提供商由注入器統一管理。當咱們須要某個服務的時候,注入器負責根據服務名尋找相應的服務提供商,而後由服務提供商的$get()生產工廠建立服務實例。所以,服務提供商必須有一個$get()方法,這個方法就是服務建立單例工廠。

背後原理:注入器中的Providers和Services各自經過一個Map對象保存在緩存(分別對應providerCache和instanceCache)中,只不過Providers的key是serviceName + 「Provider」,而Services的key是serviceName。
服務提供商-Provider

Provider即服務提供商,必須有一個$get()方法,$get()的返回值是Provider在注入器中實際的服務。
注入器

建立注入器的方法只在bootstrap()方法中被調用過,也就是說,每個angular應用對應一個注入器。
注入過程

注入器由angular.injector(modulesToLoad, isStrictDi)方法建立,在angular中其實爲createInjector方法。參數modulesToLoad是數組,元素格式爲如下之一:

‘module’,模塊的名稱。
[‘service1’, ‘service2’, fn]。
fn,方法的返回值必須仍然是方法。

方法angular.injector()的執行過程:

  1. 遍歷modulesToLoad,根據moduleName尋找或生成相應的模塊。
  2. 調用模塊invokeQueue中的全部方法,這一步的目的是建立必需的服務。
  3. 調用模塊configBlocks中的全部方法,目的是用已有的服務配置這個模塊。
  4. 調用注入器的invoke()方法執行模塊runBlocks的全部方法。
  5. 返回服務注入器實例。

不是全部模塊都是對象,若是模塊自己是方法或者是數組(最後一個元素必須是方法),則運行這個方法、或數組的最後一個方法,至關於直接進入了第四步。
注入器與bootstrap的關係

建立注入器的方法在angular.js中只在bootstrap()方法中被調用過,也就是說,每個angular應用對應一個注入器。
注入器方法

在providerCache中和instanceCache中分別內置有一個$injector對象,分別負責給模塊注入服務提供商和爲方法注入服務。通常咱們只談論instanceCache中的$injector對象,由於providerCache和它的$injector是私有的,只在Angular內部代碼使用。

好比,執行模塊調用隊列、配置隊列中的方法時注入的是服務提供商,而當調用運行隊列中的方法時,注入的是服務。
$injector.invoke(fn, self, locals, serviceName)

執行方法fn。locals是可選參數,是對象,表示局部變量。self是fn中的this。

最後一個參數serviceName是可選參數,表示在哪一個服務中調用了fn方法,用於錯誤信息顯示,沒有處理邏輯。
$injector.instantiate(Type, locals, serviceName)

l 若是參數Type爲方法,根據Type的prototype建立一個實例(經過Object.create方法建立),若是Type是數組,使用最後一個元素的prototype。

l 參數Locals是當Type方法的參數出如今locals對象中的時候,取locals[arg]的值從新做爲Type的參數。若是locals中沒有,則等價於調用get(arg,serviceName)獲取service做爲新的參數。

實例化過程能夠簡單歸納爲

Type.apply(Object.create(Type.prototype),locals[argName]|| get(argName, serviceName))。

注意:實例化出來的不必定是對象,也多是方法。

最後一個參數serviceName是可選參數,表示在哪一個服務中實例化了Type,用於錯誤信息顯示,沒有處理邏輯。
$injector.get(name, caller)

從注入器中獲取一個服務實例。

參數name是服務的名稱。參數caller也是字符串,表示調用這個服務的是哪一個方法,用於錯誤信息提示,沒有處理邏輯。
$injector.annotate(fn,strictDi )

返回數組,數組的元素是fn方法須要注入的依賴服務。

在嚴格模式下,方法的依賴注入必須使用顯示的註解加入,也就是說經過fn.$injector可以獲取這個方法的依賴注入。

參數name是可選的,用於錯誤顯示,沒有處理邏輯。

方法annotate()也能夠接受數組,數組的最後一個參數必定是fn,前面的元素則是依賴。
$injector.has(name)

檢查該注入器中是否存在指定的服務。
Provider方法

注入器的providerCache中內置有一個$provider對象,這是注入器的默認服務提供商,$provider有六個固定的方法。這幾個方法的做用主要是爲注入器添加其餘服務提供商。

注意:

如下全部方法的name參數不須要以「Provider」結尾,由於provider()方法會默認把這個後綴加上。
如下任何一個方法不作同名判斷,所以,若是出現同名,後者將覆蓋前者。

$provider.provide(name, provider)

參數provider能夠是方法或數組,也能夠是對象。

l 若是是方法,則是provider的構造函數。調用注入器的instantiate()方法,生成一個provider實例,並以name爲key保存在注入器的providerCache中。

l 若是是數組,最後一個必須是provider的構造函數,前面的就是構造函數的參數名。以後的原理和provider是方法的情形相同。

l 若是是對象,說明這個provider已經被實例化了,只需有$get()方法便可。
$provider.factory(name, factoryFn, enforce)

使用$provider.provide()通常須要定義一個Provider類,若是不想定義Provider類,而是直接定義服務工廠,就可使用這個方法。

背後原理:首先生成一個匿名對象,這個對象的$get屬性就是factoryFn(enforce爲false的狀況下),而後把這個匿名對象做爲$provider.provide()方法的第二個參數。因此,factoryFn其實依然是綁定在一個provider上的。
$provider.service(name, constructor)

調用injector.instantiate()方法,利用參數constructor生成service實例,參數name是這個service的名稱。

衆所周知,service由provider提供,那這個方法是怎麼回事?原理:Angular首先根據constructor生成一個factoryFn,而後調用$provider.factory(name, factoryFn)。因此其實仍是生成了一個provider。

舉例:

$provider.service('filter', constructor)

等於建立了一個filter服務實例,而且在providerCache中保存了一個名稱爲「filterProvider」的服務提供商。
$provider.value(name, value)

這個方法實際調用injector.factory(name,valueFn(value), false)方法實現。因此其實等於建立一個只提供值的服務提供商。
$provider.constant(name, value)

這個方法直接在providerCache中添加一個屬性實現。
$provider.decorate(serviceName, decorFn)

修改舊的服務,改成執行decorFn方法,並把servcieName原來的服務做爲一個參數,參數名爲$delegate。等價於一個靜態代理。

背後原理:首先根據seviceName找到Provider,而後修改provider的$get屬性。

angular內置模塊

ngLocale - 本地化模塊

angular.module('ngLocale',[]).provider('$locale', $LocaleProvider);

結果是invokeQueue.push(['$provide', 'provider',['$locale', $LocaleProvider]]);

ng

angular.module('ng',['ngLocale']).config(['$provide', function(){}]);

結果是configBlocks.push(['$injector', 'invoke',['$provide', function(){}]])。
三個固定模塊

每一個使用bootstrap(element, modules, config)生成的應用,注入器中有三個固定的模塊:

第一個模塊是"ng"。 第二個模塊是[‘$provider’,fn],它的做用是把根元素element做爲變量保存在$provider中。 第三個模塊是[‘$compileProvider’,fn],它的做用是根據config.debugInfoEnabled調用 $conpileProvider.debugInfoEnabled(true)。
相關文章
相關標籤/搜索