AngularJs 入门(二)--模块
模块模块是指写Angular应用的代码片段,这样可以使代码分离开来,因此代码会更好维护,可读和测试。还可以在module里定义代码依赖关系,可以调用一个模块,再在代码中定义这个模块依赖于另外两个。 angular模块通过angular.module(name,requires,configFn)方法生成: angular.module("MetronicApp") .controller('AppController',['$scope','$rootScope','AppCommonService','DictService',function ($scope,$rootScope,AppCommonService,DictService) { // code }); }]); AngularJS允许我们使用angular.module()方法来声明模块,这个方法能够接受两个参数,第一个是模块的名称,第二个是依赖列表,也就是可以被注入到模块中的对象列表。 已经初始化的angular模块保存在一个叫modules的缓存对象中,key是模块名,value是模块对象。所以,定义一个同名的模块,等于覆盖之前的模块。 服务注入angular模块只保留服务的定义,现在再来理解服务是如何加入注入器的。 模块定义服务、服务提供商; 注入器根据模块依赖关系加载模块,实例化所有服务提供商; 应用需要服务,注入器根据服务名寻找服务提供商,服务提供商实例化服务。 以上只是理论,现在从代码层面来看Angular是如何实现的。 每个angular模块内置有三个数组,invokeQueue保存如何注入服务提供商和值的信息;configBlocks保存模块的配置信息;runBlocks保存这个模块的执行信息。模块被使用的时候,注入器根据invokeQueue中的信息,实例化服务提供商;根据configBlocks中的信息对服务提供商做一些额外的处理;根据runBlocks中提供的信息,调用前面的服务提供商提供的服务执行模块需要完成的工作。 var angular = ensure(window,'angular',Object); angular.$$minErr = angular.$$minErr || minErr; return ensure(angular,'module',function() { var modules = {}; return function module(name,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',controller: invokeLaterAndSetModuleName('$controllerProvider',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,arguments]); return moduleInstance; }; } function invokeLaterAndSetModuleName(provider,method) { return function(recipeName,factoryFunction) { if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; invokeQueue.push([provider,arguments]); return moduleInstance; }; } }); }; }); 调用队列 – invokeQueue数组元素为[‘provider’,‘method’,arguments]。 angular.module('ngAppDemo',[]) .controller('ngAppDemoController',function($scope) { $scope.a= 1; $scope.b = 2; }); 这段代码等于: invokeQueue.push(['$controllerProvider','register',['ngAppDemoController',function(){ //code }]]); 注入器根据这个信息,就会调用$controllerProvider的register方法注册一个ngAppDemoController。 配置队列 – configBlocks元素格式为['$injector',arguments]。 运行队列 – runBlocks元素要求是方法,或者是数组,数组最后一个元素是方法。 angular模块实例属性和方法属性 模块的以下方法最后全部会返回模块实例本身,形成执行链。 animation() config() constant() controller() directive() factory() filter() provider() run(block) service() value() 服务注入器(Service Injector) & 服务提供商(Service Provider)在Angular中,服务可能是对象、方法、或者一个常量值。服务由服务提供商创建,而服务提供商由注入器统一管理。当我们需要某个服务的时候,注入器负责根据服务名寻找相应的服务提供商,然后由服务提供商的$get()生产工厂创建服务实例。因此,服务提供商必须有一个$get()方法,这个方法就是服务创建单例工厂。 背后原理:注入器中的Providers和Services各自通过一个Map对象保存在缓存(分别对应providerCache和instanceCache)中,只不过Providers的key是serviceName + “Provider”,而Services的key是serviceName。 Provider即服务提供商,必须有一个$get()方法,$get()的返回值是Provider在注入器中实际的服务。 创建注入器的方法只在bootstrap()方法中被调用过,也就是说,每一个angular应用对应一个注入器。 注入器由angular.injector(modulesToLoad,isStrictDi)方法创建,在angular中其实为createInjector方法。参数modulesToLoad是数组,元素格式为以下之一: ‘module’,模块的名称。 [‘service1’,‘service2’,fn]。 fn,方法的返回值必须仍然是方法。 方法angular.injector()的执行过程:
不是所有模块都是对象,如果模块本身是方法或者是数组(最后一个元素必须是方法),则运行这个方法、或数组的最后一个方法,相当于直接进入了第四步。 创建注入器的方法在angular.js中只在bootstrap()方法中被调用过,也就是说,每一个angular应用对应一个注入器。 在providerCache中和instanceCache中分别内置有一个$injector对象,分别负责给模块注入服务提供商和为方法注入服务。一般我们只谈论instanceCache中的$injector对象,因为providerCache和它的$injector是私有的,只在Angular内部代码使用。 比如,执行模块调用队列、配置队列中的方法时注入的是服务提供商,而当调用运行队列中的方法时,注入的是服务。 执行方法fn。locals是可选参数,是对象,表示局部变量。self是fn中的this。 最后一个参数serviceName是可选参数,表示在哪个服务中调用了fn方法,用于错误信息显示,没有处理逻辑。 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,用于错误信息显示,没有处理逻辑。 从注入器中获取一个服务实例。 参数name是服务的名称。参数caller也是字符串,表示调用这个服务的是哪个方法,用于错误信息提示,没有处理逻辑。 返回数组,数组的元素是fn方法需要注入的依赖服务。 在严格模式下,方法的依赖注入必须使用显示的注解加入,也就是说通过fn.$injector能够获取这个方法的依赖注入。 参数name是可选的,用于错误显示,没有处理逻辑。 方法annotate()也可以接受数组,数组的最后一个参数一定是fn,前面的元素则是依赖。 检查该注入器中是否存在指定的服务。 注入器的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.provide()一般需要定义一个Provider类,如果不想定义Provider类,而是直接定义服务工厂,就可以使用这个方法。 背后原理:首先生成一个匿名对象,这个对象的$get属性就是factoryFn(enforce为false的情况下),然后把这个匿名对象作为$provider.provide()方法的第二个参数。所以,factoryFn其实依然是绑定在一个provider上的。 调用injector.instantiate()方法,利用参数constructor生成service实例,参数name是这个service的名称。 众所周知,service由provider提供,那这个方法是怎么回事?原理:Angular首先根据constructor生成一个factoryFn,然后调用$provider.factory(name,factoryFn)。所以其实还是生成了一个provider。 举例: $provider.service('filter',constructor) 等于创建了一个filter服务实例,并且在providerCache中保存了一个名称为“filterProvider”的服务提供商。 这个方法实际调用injector.factory(name,valueFn(value),false)方法实现。所以其实等于创建一个只提供值的服务提供商。 这个方法直接在providerCache中添加一个属性实现。 修改旧的服务,改为执行decorFn方法,并把servcieName原来的服务作为一个参数,参数名为$delegate。等价于一个静态代理。 背后原理:首先根据seviceName找到Provider,然后修改provider的$get属性。 angular内置模块ngLocale - 本地化模块 angular.module('ngLocale',[]).provider('$locale',$LocaleProvider); 结果是invokeQueue.push(['$provide',['$locale',$LocaleProvider]]); ng angular.module('ng',['ngLocale']).config(['$provide',function(){}]); 结果是configBlocks.push(['$injector',['$provide',function(){}]])。 每个使用bootstrap(element,modules,config)生成的应用,注入器中有三个固定的模块: 第一个模块是"ng"。 第二个模块是[‘$provider’,fn],它的作用是把根元素element作为变量保存在$provider中。 第三个模块是[‘$compileProvider’,fn],它的作用是根据config.debugInfoEnabled调用 $conpileProvider.debugInfoEnabled(true)。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |