Angular中有几种不同类型的services。每一种都有自己的独特用法。
需要记住的非常重要的一点是service总是一个单体,无论是哪种类型的service。
注释:单体是一种设计模式,它限制了每一个类仅能够实例化为一个对象。无论我们在什么地方注入我们的service,将永远使用同一个实例。
Constant
例子:
app.constant('fooConfig',{
config1: true,
config2: "Default config2"
});
Constant是一个非常有用的service,它经常被用来在指令中提供默认配置。因此如果你正在创建一个指令,并且你想要在给指令传递可选参数的同时进行一个默认配置,一个Constant就是一个好办法。
作为一个constant,我们放入其中的值将不会改变。Contant service 基本上回事一个基本类型的值或者是一个对象。
Value
例子:
app.value('fooConfig',{
config1: true,
config2: "Default config2 but it can change"
});
一个value service有点像是一个constant但是它是可以被改变的。它也经常被用在一个指令上面,来进行配置。一个value service有点像是一个factory service的缩小版,它经常用来保存值但是我们不能在其中对值进行计算。
我们可以使用angular对象的extend方法来改变一个value service:
app = angular.module("app", []);
app.controller('MainCtrl', function($scope, fooConfig) {
$scope.fooConfig = fooConfig;
angular.extend(fooConfig, {config3: "I have been extended"});
});
app.value('fooConfig', {
config1: true,
config2: "Default config2 but it can changes"
});
Factory
例子:
app.factory('foo', function() {
var thisIsPrivate = "Private";
function getPrivate() {
return thisIsPrivate;
}
return {
variable: "This is public",
getPrivate: getPrivate
};
});
// or..
app.factory('bar', function(a) {
return a * 2;
});
Factory service是最普遍使用的service。它同样也非常容易理解。
一个Factory是一个能够返回任何数据类型的service。对于你如何创建它并没有什么可选项,你仅仅需要在其中返回一些东西即可。
正如前面所说的,所有的service类型都是单体,因此如果我们在一个地方修改了foo.variable,其他的地方也会相应的发生改变。
Service
例子:
app.service('foo', function() {
var thisIsPrivate = "Private";
this.variable = "This is public";
this.getPrivate = function() {
return thisIsPrivate;
};
});
Service service 和 factory差不多。它们之间的区别在于service会接收一个构造器,因此当你第一次使用它的时候,它将会自动运行newFoo()
来实例化一个对象。一定要记住如果你在其他的地方也使用了这个service,它将返回同一个对象。
事实上,上面的代码和下面的代码等价:
app.factory('foo2', function() {
return new Foobar();
});
function Foobar() {
var thisIsPrivate = "Private";
this.variable = "This is public";
this.getPrivate = function() {
return thisIsPrivate;
};
}
Foobar
是一个类,我们在首次使用它的时候在我们的factory中将它实例化然后将它返回。和service一样,Foobar将只会实例化一次然后下次当我们再次使用factory时它将返回同一个实例。
如果我们已经有了一个类,并且我们想将它用在service中,我们只需要编写如下的代码:
app.service('foo3',Foobar);
Provider
Provider是factory的加强版。事实上,上一个例子中的factory代码等价于下面的provider代码:
app.provider('foo', function() {
return {
$get: function() {
var thisIsPrivate = "Private";
function getPrivate() {
return thisIsPrivate;
}
return {
variable: "This is public",
getPrivate: getPrivate
};
}
};
});
一个provider中应当由一个$get函数,其中的内容就是我们想要注入我们应用中的部分,因此当我们将foo注入一个控制器时,我们实际上注入的是$get函数。
既然factory如此简单,那我们为什么还要使用provider呢?因为我们可以在config阶段配置一个provider。因此我们可以编写下面的代码:
app.provider('foo', function() {
var thisIsPrivate = "Private";
return {
setPrivate: function(newVal) {
thisIsPrivate = newVal;
},
$get: function() {
function getPrivate() {
return thisIsPrivate;
}
return {
variable: "This is public",
getPrivate: getPrivate
};
}
};
});
app.config(function(fooProvider) {
fooProvider.setPrivate('New value from config');
});
在这里我们将thisIsPrivate移到了我们的$get函数的外面,然后我们创建了一个setPrivate来在一个config函数中修改thisIsPrivate。为什么我们需要这样做?这难道不比在factory中添加setter要容易吗?除此之外,还有另外一个原因。
我们想要注入一个特定的对象但是我们想要提供一种方式来根据我们的需求进行一些配置。例如:一个service包含了一个使用jsonp的资源,我们想要配置具体使用的URL,或者我们想要使用一个第三方的service比如restangular来允许我们根据我们的需求来进行配置。
要注意到我们在config函数中放入的是nameProvider
而不是name
。在这里,我们实际上还是对name
进行配置。
看到这里我们其实已经意识到了我们已经在应用中进行过一些配置了,像是$routeProvider
以及$locationProvider
,两者分别用来配置我们的路由了HTML5模式。
Decorator
那么现在已经决定要使用前面的 foo
service,但是其中还是缺少一个你想要的greet
函数。你可以修改factory吗?答案是不行!但是你可以装饰它:
app.config(function($provide){
$provide.decorator('foo',function($delegate){
$delegate.greet = function(){
return "Hello, I am a new function of 'foo'";
}
});
});
$provide是Angular用来在内部创建我们的service的东西。如果我们想要使用它的话可以手动来使用它或者仅仅使用在我们的模块中提供的函数(我们需要使用$provide来进行装饰)。$provide有一个函数,decorator
,它让我们可以装饰我们的service。它接收我们想要装饰的service的名字并且在回调函数中接收一个$delegate来代表我们实际上的service实例。
在这里我们可以做一切我们想要的事情来装饰我们的service。在上面的例子中,我们为我们原来的service添加了一个greet函数。接着我们返回了修改后的service。
经过修改以后,现在我们的factory中已经有了一个叫做greet的函数。
装饰一个service的能力是非常实用的,尤其是当我们想要使用第三方的service时,此时我们不需要将代码复制到我们的项目中,而只需要进行一些修改即可。
注意:constant service不能被装饰。
创建一个实例
我们的services都是单体但是我们可以创建一个单体factory来创建新的实例。在你深入之前,记住Angular中的服务都是单体并且我们不想改变这一点。但是,在极少数的情况下你需要生成一个新的实例,你可以像下面这样做:
//我们的类
function Person(json){
angular.extend(this,json);
}
Person.prototype = {
update: function(){
//更新内容
this.name = "Dave";
this.country = "Canada";
}
};
Person.getById = function(id){
//由id来获取一个Person的信息
return new Person({
name: "Jesus",
country: "Spain"
});
};
//我们的factory
app.factory('personService',function(){
return {
getById: Person.getById
};
});
在这里我们创建了一个Person
对象,它接收一些json数据来初始化对象。然后我们在我们的原型(原型中的函数可以被Person的实例所用)中创建了一个函数,并且在Person上直接创建了一个函数(就像是类函数一样)。
因此现在我们拥有了一个类函数,它将基于我们提供的id来创建一个新的Person
对象,并且每一个对象都可以自我更新。现在我们仅仅需要创建一个能够使用它的service。
当每次我们调用personService.getById时,我们都在创建一个新的Person对象,因此你可以在不同的控制器中使用这个service,即便当factory是一个单体,它也能生成新的对象。
总结
Service是Angular中最酷的特性之一。我们可以使用很多方法来创造它们,我们仅仅需要找到符合我们需求的方法然后实现它。
本文译自understaning service types,原文地址http://angular-tips.com/blog/2013/08/understanding-service-types/
源码解读:
在很多angularjs的教程和文档中,作者时而使用service
factory
,但从来不解释
为什么
。更别说其实你还能用
value
和
constant
了。
我们来看看在什么情况下你应该使用哪个。首先,我们也应该理解providers的工作方式:
provider
这是provieder
方法的源码:
- function provider(name, provider_) {
- if (isFunction(provider_) || isArray(provider_)) {
- provider_ = providerInjector.instantiate(provider_);
- }
- if (!provider_.$get) {
- throw Error('Provider ' + name + ' must define $get factory method.');
- }
- return providerCache[name + providerSuffix] = provider_;
- }
name
是字符串. provider_
可以是这三种东西之一:
-
函数(function)
如果传入的是一个函数,那么这个函数会被 dependency injection调用,并返回一个有
$get
方法的对象 -
数组(array)
数组被视为使用Inline Annotation的函数, 它也必须返回一个有
$get
方法的对象。 -
对象(object)
如果传入的是个对象,那么它应该是有
$get
方法的对象。
无论provider
的第二个参数是什么,你最终都会个到一个拥有$get
方法的对象。以这段代码为例:
- // You can run this
- // Create a module
- var hippo = angular.module('hippo', []);
- // Register an object provider
- hippo.provider('awesome', {
- $get: function() {
- return 'awesome data';
- }
- });
- // Get the injector (this happens behind the scenes in angular apps)
- var injector = angular.injector(['hippo', 'ng']);
- // Call a function with dependency injection
- injector.invoke(function(awesome) {
- console.log('awesome == ' + awesome);
- });
一旦你理解了provider,你就会发现factory
, service
, value
和constant
只是生成provider的快捷方式
factory
这是源代码:
- function factory(name, factoryFn) {
- return provider(name, { $get: factoryFn });
- }
所以你可以简单的把一个名叫awesome
的provider写成这个样子:
- hippo.factory('awesome', function() {
- return 'awesome data';
- })
service
这是源代码:
- function service(name, constructor) {
- return factory(name, ['$injector', function($injector) {
- return $injector.instantiate(constructor);
- }]);
- }
所以这可以让你构建一个factory,用它可以初始化一个“类(class)”,比如说:
- var gandalf = angular.module('gandalf', []);
- function Gandalf() {
- this.color = 'grey';
- }
- Gandalf.prototype.comeBack = function() {
- this.color = 'white';
- }
- gandalf.service('gandalfService', Gandalf);
- var injector = angular.injector(['gandalf', 'ng']);
- injector.invoke(function(gandalfService) {
- console.log(gandalfService.color);
- gandalfService.comeBack()
- console.log(gandalfService.color);
- });
以上的代码会生成一个Gandalf
(译注:即甘道夫,魔戒里的白胡子老头),但是请记住,使用同一个service返回的实例都是同一个实例(这是件好事)。
value
这是源代码:
- function value(name, value) {
- return factory(name, valueFn(value));
- }
用value
可以让你把awesome
的缩短成这样:
- hippo.value('awesome', 'awesome data');
constant
这是源代码:
- function constant(name, value) {
- providerCache[name] = value;
- instanceCache[name] = value;
- }
constant
和value
不同,constant
在config的时候是可达的(accessible),如下例:
- var joe = angular.module('joe', []);
- joe.constant('bobTheConstant', 'a value');
- joe.value('samTheValue', 'a different value');
- joe.config(function(bobTheConstant) {
- console.log(bobTheConstant);
- });
- joe.config(function(samTheValue) {
- console.log(samTheValue);
- });
- // This will fail with "Error: Unknown provider: samTheValue from joe"
- var injector = angular.injector(['joe', 'ng']);
在Modules文档的Module Loading & Dependencies部分可以获得更多信息。
总结
如果你想像调用普通函数那样调用你的函数,用factory
,如果你想用new
操作符来实例化你的函数,那么使用service
。如果你不知道这其中的区别在那里,那就用factory
以下是AngularJS源代码中,每个函数(很棒的)注释文档:
-
factory
A short hand for configuring services if only
$get
method is required. -
service
A short hand for registering service of given class.
-
value
A short hand for configuring services if the
$get
method is a constant. -
constant
A constant value, but unlike {@link AUTO.$provide#value value} it can be injected into configuration function (other modules) and it is not interceptable by {@link AUTO.$provide#decorator decorator}.