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)。
相关文章
相关标签/搜索