原文地址:http://angular-tips.com/blog/2013/08/understanding-service-types/
Angular中有几种不同类型的services。每一种都有自己的独特用法。
需要记住的非常重要的一点是service总是一个单体,无论是哪种类型的service,这是个很被期望的行为。
注释:单体是一种设计模式,它限制了每一个类仅能够实例化为一个对象。无论我们在什么地方注入我们的service,将永远使用同一个实例。
Provider
Provider是几乎所有service的根源(除了contant),但也是最复杂并且可以配置。我们先看下一个基本用法:
app.provider(‘foo‘, function() { return { $get: function() { var thisIsPrivate = "Private"; function getPrivate() { return thisIsPrivate; } return { variable: "This is public", getPrivate: getPrivate }; } };});
一个provider,最简单的形式是返回一个$get方法,p rovider注册的即是我们在其他组件中可以注入的部分。如果我们有一个controller,想把foo作为一个provider注入,我们注入的部分就是$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方法的外部,为的是在config中可以配置。为神马我们需要这么做呢?将setter方法增加到$get是不是更加简单?这是因为不同的目的。
想象一下你想创建一个通用库用来管理models和REST请求。如果你将目标URLs硬编码,这样的库不再通用,所以比较好的想法是将URLs可以配置,我们可以创建一个provider使得URLs在config方法中可以配置。
需要注意的是我们将nameProvider注入到config而不是name,如果是注入到$get中可以使用name(后半句是个人理解,不知道是否合理,留作一个问题,后续验证下)。
看到这里,我们意识到,我们已经在应用中配置过一些服务,像$routeProvider和$locationProvider配置routes和html5mode。
Providers有两种注入方式,provider constructor 和$get方法。provider constructor仅能注入其他providers和constants(在配置方面有相同的限制)。在$get方法中可以注入除了其他的provider的服务(但是可以注入其他provider的$get方法)
注意:注入一个provider使用:name+‘Provider‘, 如果注入到它的$get方法可以使用name
Factory
Provider非常棒,但是相当灵活也比较复杂,我们只是想使用它的$get方法?我的意思是,根本不用配置,这样的话我们嗨可以使用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; });
正如你看到的,把provider的$get方法移到了一个factory中,和第一的provider相比简单多了。事实上,factory内部只用了一个$get方法。
正如前面介绍的,所有的service都是单例的,如果我们在一个地方修改foo.variable的值,其他地方的值也将会修改。
我们可以注入所有的服务(除了providers)到factory。可以注入(如上面的foo,bar)到任何的地方除了provider constructor 和 config方法。(具体的例子可以参考原文)
Value
Factory很好用,但是如果我们仅存储一个简单的值呢?我的意思是,没有注入,只是一个简单的值或对象,angular提供一个叫做value的service。
app.value(‘foo‘, ‘A simple value‘);
value的内部实现只是一个fa ctory。jiran它是一个factory那么仍然要遵循它的注入方法。AKA不能被注入到provider constructor 和config方法
Service
到现在我们提到了复杂的provider,比较简单多factory和value 服务,service服务时什么样子的?我们先看下这个例子:
app.service(‘foo‘, function() { var thisIsPrivate = "Private"; this.variable = "This is public"; this.getPrivate = function() { return thisIsPrivate; }; });
service服务的工作方式可以factory很相似,区别也是很简单:factory接受一个方法,这个方法在创建时调用,service接受一个构造方法(内部使用Object.create创建而不是使用new),
事实上,下面的代码将实现相同的功能:
app.factory(‘foo2‘, function() { return new Foobar(); }); function Foobar() { var thisIsPrivate = "Private"; this.variable = "This is public"; this.getPrivate = function() { return thisIsPrivate; }; }
Foobar是一个构造方法,在angular处理过程中被实例化然后返回。Foobar会被初始化一次,接下来我们使用factory会返回同样的实例。
有了构造方法,可以使用service服务了,如下:
app.service(‘foo3‘, Foobar);
Constant
constant是provider的子类型,和value 服务的用法比较相似:
app.constant(‘fooConfig‘, { config1: true, config2: "Default config2" });
和value有什么区别呢?constant可以被注入到任何地方,包括provider constructor 和config 方法。这就是为什么使用constant 服务为directive设置默认配置,因为可以在config方法中修改这些配置。
你可能正在考虑为什么命名为constant,这只是一个设计决定,并不知道它背后的原因。
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中最酷的特性之一。我们可以使用很多方法来创造它们,我们仅仅需要找到符合我们需求的方法然后实现它。