AngularJS深入(5)——provider

简介: 太精彩,不得不全文引用。 到这个层次,可能才敢说自己懂了吧。。。 http://syaning.com/2015/07/21/dive-into-angular-5/ 在使用AngularJS的时候,可能需要创建各种各样的服务,这个时候,需要告诉AngularJS如何创建这些服务,这便是Provider。

太精彩,不得不全文引用。

到这个层次,可能才敢说自己懂了吧。。。

http://syaning.com/2015/07/21/dive-into-angular-5/

在使用AngularJS的时候,可能需要创建各种各样的服务,这个时候,需要告诉AngularJS如何创建这些服务,这便是Provider。在实际使用的时候,会有providerfactoryservicevalueconstant,事实上,它们都是Provider,从factoryconstant,只不过是对provider的一步步封装。相关源码都在函数createInjector中。


1. provider

源码如下:

function provider(name, provider_) { assertNotHasOwnProperty(name, 'service'); if (isFunction(provider_) || isArray(provider_)) { provider_ = providerInjector.instantiate(provider_); } if (!provider_.$get) { throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); } return providerCache[name + providerSuffix] = provider_; } 

首先,一个Provider的名字不可以是hasOwnProperty,然后,如果provider_是函数或者数组的话,调用providerInjector.instantiate将其实例化。之后进行判断,如果provider_没有$get属性,则报错。然后将provider_缓存在providerCache中。

下面来分析对参数provider_的约束。

(1) 如果provider_是函数,那么会调用provider_ = providerInjector.instantiate(provider_)对其再次赋值,相当于以provider_作为构造函数来创建实例。因此在构造函数中,必须要有对$get的操作,因此如下形式都是可以的:

app.provider('greeting', function() { this.$get = function() { return { sayHello: function() { console.log('hello world'); } }; }; }); // or app.provider('greeting', function() { return { $get: function() { return { sayHello: function() { console.log('hello world'); } }; } }; }); 

(2) 如果provider_是数组,同样的接下来会调用provider_ = providerInjector.instantiate(provider_),所以数组必须是[deps, fn]的形式,其中deps为依赖,fn与(1)中函数的限制相同。例如:

app.provider('greeting', ['$injector', function($injector) { this.$get = function() { return { sayHello: function() { console.log('hello world', $injector); } }; }; }]); 

(3)如果provider_是一个对象字面量,则它必须要有$get属性,例如:

app.provider('greeting', { $get: function() { return { sayHello: function() { console.log('hello world'); } }; } }); 

接下来讨论对$get的约束。在定义instanceInjector的时候,代码如下:

instanceInjector = (instanceCache.$injector = createInternalInjector(instanceCache, function(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); })); 

由于会被invoke调用,因此$get的值必须为一个函数或者[deps, fn]形式的数组。而函数的返回值,则可以为原始数据类型、对象、函数等等,并无限制。

总之,在调用provider(name, provider_)的时候,会将(name + 'Provider', {$get:function(){/* ... */}})键值对缓存在providerCache中,在注入的时候,则会调用$get函数,将其返回值进行注入,并缓存在instanceCache中。

2. factory

源码如下:

function factory(name, factoryFn, enforce) { return provider(name, { $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn }); } 

可以看到,factory事实上是调用了provider方法,而第二个参数factoryFn,事实上也就是上面的$get的值,因此应当符合对$get的约束。

因此上面的例子可以这样写:

app.factory('greeting', function() { return { sayHello: function() { console.log('hello world'); } }; }); 

注意到factory的源码中函数有第三个参数enforce,也就是说是否强制返回值,如果函数没有返回值(包括显式返回值为undefined)且第三个参数不为false的时候,就回报错,相关源码如下:

function enforceReturnValue(name, factory) { return function enforcedReturnValue() { var result = instanceInjector.invoke(factory, this); if (isUndefined(result)) { throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); } return result; }; } 

然而在使用的过程中,会发现,即使显示声明了enforcefalse,还是会报错,例如:

app.factory('greeting', function() { console.log('hello world'); }, false); // Error: [$injector:undef] Provider 'greeting' must return a value from $get factory method. 

这主要是由supportObject引起的,因为providerCache.$provide.factory的值实际上是supportObject(factory),而supportObject源码如下:

function supportObject(delegate) { return function(key, value) { if (isObject(key)) { forEach(key, reverseParams(delegate)); } else { return delegate(key, value); } }; } 

可以发现,经过supportObject处理后,函数的有效参数值最多只有两个了,因此第三个参数false也就被忽略掉了。

3. service

源码如下:

function service(name, constructor) { return factory(name, ['$injector', function($injector) { return $injector.instantiate(constructor); }]); } 

可以看到,实质上是调用了factory,第二个参数constructor是作为构造函数,最终返回的是该构造函数表示的类的一个实例。由于会调用$injector.instantiate(constructor),因此constructor必须是一个函数或者[deps, fn]的形式。

因此上面的例子可以写成:

app.service('greeting', function() { this.sayHello = function() { console.log('hello world'); }; }); // or app.service('greeting', function() { return { sayHello: function() { console.log('hello world'); } }; }); 

4. value

源码如下:

function value(name, val) { return factory(name, valueFn(val), false); } 

可以看到,也是调用了factory。其中valueFn的作用是将一个值包装为函数,源码如下:

function valueFn(value) { return function() { return value; }; } 

5. constant

源码如下:

function constant(name, value) { assertNotHasOwnProperty(name, 'constant'); providerCache[name] = value; instanceCache[name] = value; } 

可以看到,该方法只是对providerCacheinstanceCache进行了属性设定。

6. value & constant

(1)value设定的值是可以改变的,而constant设定的值是不可变的,例如:

app.value('greeting', 'hello value') .value('greeting', 'hello world ') .controller('ctrl', ['greeting', function(greeting) { console.log(greeting); // hello world }]); //////////////////// app.constant('greeting', 'hello constant') .constant('greeting', 'hello world ') .controller('ctrl', ['greeting', function(greeting) { console.log(greeting); // hello constant }]); 

实现机制在函数setUpModuleLoader中。部分相关源码如下:

var moduleInstance = {
    _invokeQueue: invokeQueue, value: invokeLater('$provide', 'value'), constant: invokeLater('$provide', 'constant', 'unshift'), // ... ... }; function invokeLater(provider, method, insertMethod, queue) { if (!queue) queue = invokeQueue; return function() { queue[insertMethod || 'push']([provider, method, arguments]); return moduleInstance; }; } 

在调用value的时候,用的是数组的push方法;而调用constant的时候,用的是数组的unshift方法。在加载模块的时候,会依次执行_invokeQueue中的内容。对于通过value添加的值,会按照声明的顺序进行设定,因此后面的值会覆盖掉前面的值;而对于通过constant添加的值,会按照声明的逆序进行设定,因此最后得到的值为第一次设定的值,从而就实现了常量的效果。

(2)config中,value不可以被注入,而constant可以被注入,例如:

app.constant('greeting', 'hello world') .config(function(greeting) { console.log(greeting); // hello world }); //////////////////// app.value('greeting', 'hello world') .config(function(greeting) { console.log(greeting); // Error: [$injector:unpr] Unknown provider: greeting }); 

这是因为在使用value的时候,实际上是将greetingProvider换存在了providerCache中;而使用constant的时候,则是将greeting换存在了providerCache中。然后在执行模块的_configBlocks的时候,会在providerCache中查找注入的依赖greeting,因此对于value来说,自然是找不到了。

7. provider, factory & service

这三者的关系非常密切,看如下例子:

function Greeting() {
	this.sayHello = function() { console.log('hello world'); }; } app.provider('greetingFromProvider', function() { this.$get = function() { return new Greeting(); } }) .factory('greetingFromFactory', function() { return new Greeting(); }) .service('greetingFromService', Greeting) .controller('ctrl', ['greetingFromProvider', 'greetingFromFactory', 'greetingFromService', function(gp, gf, gs) { gp.sayHello(); gf.sayHello(); gs.sayHello(); } ]); 

可以简单理解为service接收的参数是构造函数,factory接收的参数是工厂函数,而该工厂函数是作为provider中的$get属性而存在的。但是providerfactory更加灵活可配置,因此可以理解为可配置的工厂函数。例如:

app.provider('greeting', function() { var name = 'world'; this.$get = function() { return { sayHello: function() { console.log('hello ' + name); } }; }; this.setName = function(newName) { name = newName; }; }) .config(function(greetingProvider) { greetingProvider.setName('alex'); }) .controller('ctrl', function(greeting) { greeting.sayHello(); // hello alex });
目录
相关文章
|
6月前
|
设计模式 JavaScript 前端开发
Angular 懒加载模块与 Combined Injector
Angular 懒加载模块与 Combined Injector
33 0
|
JavaScript
AngularJs错误http://errors.angularjs.org/1.2.9/$injector/unpr?p0=...
AngularJs错误http://errors.angularjs.org/1.2.9/$injector/unpr?p0=...
63 0
|
Java C#
angularJS学习小结——service
angularJS学习小结——service
71 0
|
JSON 前端开发 JavaScript
总结—angularjs项目
总结—angularjs项目
220 0
总结—angularjs项目
Angular NgModule里定义的注解和NgModuleRef$1运行时
Angular NgModule里定义的注解和NgModuleRef$1运行时
90 0
Angular NgModule里定义的注解和NgModuleRef$1运行时
|
前端开发
angular-cli 使用 bootstrap_angular 4.0 怎样使用 bootstrap
转载自  http://www.ngui.cc/index.html angular 使用bootstrap ngx-bootstrap  文档地址  http://ngx-bootstrap.
1017 0
|
JavaScript 容器 前端开发
|
JSON 前端开发 API
|
JavaScript 前端开发