在angular中,模块可以是一个对象、一个方法或一个数组(数组的最后一个元素必须是方法)。后面要讲的模块属性和方法,都是针对通过angular.module()方法定义的模块而言的,我称之为angular模块。
通过angular.module()方法定义的模块是唯一的,如果重复定义,后面的就会覆盖前面的定义。不是通过angular.module()定义的模块,比如一个方法或一个数组,这些模块也是唯一的,但是这些模块一旦定义无法被修改。
angular模块可以说是一个空对象,因为它本身不包含任何服务。服务由服务提供商提供,而所有的服务提供商都由注入器负责管理。angular模块的属性和方法只是告诉注入器在这个模块中,我需要这些服务提供商,需要服务提供商的这些服务,然后用这些服务在这个模块中做一些事情(之所以这样认为,是因为虽然服务提供商和服务的定义离不开angular模块的方法,但他们确实只保存在注入器中)。所以,angular模块更像是一个的应用,而服务提供商才是我们一般理解的功能模块。比如,指令ng-app指向的就是一个angular模块,而它本身已经其实是一个应用。
angular模块
angular模块通过angular.module(name,requires, configFn)方法生成:
- 参数name是模块名称;
- 参数requires表示依赖模块数组。没有requires参数,调用angular.module()方法等于获取这个module;因此,如果这个模块确实没有依赖关系,必须设置为空数组[];
- 参数configFn是方法或数组,负责在模块初始化时做一些配置,如果是数组,最后一个元素必须是方法。
注意:参数configFn并不是在angular.module()方法中执行,而是当这个模块被第一次使用时(也就是这个时候才开始真正执行configFn),由$injector.invoke()方法调用。而且,这个方法只会执行一次,因此同一个angular模块不会重复配置。
另外需要注意的是,参数requires中的字符串表示依赖的模块名称(如果不是字符串,是方法或数组,那么,这个方法和数组本身就表示一个模块),而configFn方法的参数注入的是服务提供商(provider)。
同名模块
已经初始化的angular模块保存在一个叫modules的缓存对象中,key是模块名,value是模块对象。所以,定义一个同名的模块,等于覆盖之前的模块。
充实模块
前面已经讲了,angular模块一开始相当于一个空对象,它的实际功能相当于只负责告诉注入器这个模块需要某些服务提供商提供的某些服务,最后使用这些服务做一些事情。现在来看它是如何告诉注入器的。
每个angular模块内置有三个数组,invokeQueue保存如何注入服务提供商和值的信息;configBlocks保存模块的配置信息;runBlocks保存这个模块的执行信息。模块被使用的时候,注入器根据invokeQueue中的信息,实例化服务提供商;根据configBlocks中的信息初始化模块;根据runBlocks中提供的信息,调用前面的各种服务执行这个模块需要执行的动作。
angular模块提供了很多方法来填充这三个数组,比如config()、run()等。三个数组的元素也是数组,具体元素格式参考后面的说明。
调用队列 – invokeQueue
数组元素为[‘provider’, ‘method’, arguments]。
举例– 添加一个Controller:
angular.module(‘ngAppDemo‘,[]) .controller(‘ngAppDemoController‘,function($scope) { $scope.a= 1; $scope.b = 2; });
这段代码等于:
invokeQueue.push([‘$controllerProvider‘,‘register‘, [‘ngAppDemoController‘, function(){}]]);
注入器根据这个信息,就会调用$controllerProvider的register方法注册一个ngAppDemoController。
配置队列 – 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][,name] )
返回数组,数组的元素是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属性。
服务实例化
所有服务都由Provider管理,除了常量值,其它一般都是还没有创建出来的。了解了注入器的所有方法,也应该可以看出,只有两个方法:get()和invoke()真实的调用了服务。其实,服务的实例化实际是在注入器的get()方法中完成的,而invoke()只是在需要的时候调用了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)。