3.5 服务类组件
在本书前面的3.3.3节提到过,AngularJS的控制器组件的实现不应该有复杂或者可共用的业务代码,它们应当使用服务进行封装。本节将介绍的3种主要服务类组件,除了一些细微的差别外,它们都可以用于封装可复用的业务逻辑代码。
AngularJS提供了3种方式来定义(注册)开发者自己的服务:
● Provider
● Factory
● Service
由于Service与Factory除了代码写法上有差异而作用与实现能力都基本一致,本节将重点介绍Provider和Factory, Service方式将简单带过。
提示
由于Service的中文翻译就是服务,而Provider和Factory直接使用其中文对应名词不但不能反映组件实质的作用,反而容易引起初学者思维的混乱。有鉴于此,笔者在本书将直接使用这3个英文单词本身而不使用中文对应名词来表示它们。
3.5.1 Provider服务组件详解
Provider是唯一一种可以传进.config()函数的服务组件。如果想要服务组件对象在启用之前具有先进行初始化配置的能力,则必须使用Provider。
在AngularJS中自行开发并使用Provider来创建服务的过程分为3个阶段,由于涉及AngularJS框架本身的注入服务,以示例的方式来解说将更加易懂。
【示例3-7】以Provider方式开发并使用一个简单的问候greet服务的过程。
//第一阶段:定义阶段 - 使用provider()方法定义greet服务 angular.module('myApp'). provider('greet', function greetProvider() { //将被调用用来获取greet服务的AngularJS约定方法,只会在创建时被调用一次 this.$get = ['$http', //注入$http服务 function($http) { var greeting = this.greeting; return function(name) { alert(greeting + ', ' + name); } }]; //问候语初始设置为Hello this.greeting = 'Hello'; //用于设置问候语的方法 this.setGreeting = function(greeting) { this.greeting = greeting; } }); //第二阶段:配置阶段 - 在应用的配置代码里配置greet服务的问候语 angular.module('myApp').config( //注入greet服务的provider配置对象 ["greetProvider", function(greetProvider) { //调用greet服务provider的配置方法 greetProvider.setGreeting('你好'); }); //第三阶段:使用阶段 - 在控制器里使用greet服务 angular.module('myApp').controller('TestController', [ 'greet', function (greet) { // 使用已配置好的greet服务实例,弹出的提示框的输出结果将是"你好,Word" greet('Word'); }]);
【代码解析】代码块的执行顺序按时间轴的分布可以分为3个阶段:
● 首先,使用provider()方法定义greet服务必须要实现一个具有$get方法的构造函数,该函数需要返回未来可以作为服务组件使用的对象。$get方法支持依赖注入其他服务,样例代码中为了演示的目的注入了$http服务,虽然并没有使用到它。$get方法只会被AngularJS的注入服务组件调用一次,随后返回的对象将被缓存下来供整个应用全局使用。
● 然后(这一步是可选的),在AngularJS的应用模块myApp的config方法里对Provider服务组件进行配置。注意引用配置对象时需要加上Provider后缀,如本示例中的配置对象名为greetProvider。示例代码中把原本设为'Hello'的问候语前缀覆盖改为了'你好'。
● 最后的阶段就是使用了经过配置的greet服务了。还是通过依赖注入的方式,其他模块(控制器、其他服务组件等)都可以用标准方式使用greet服务,访问服务对象返回的方法或属性。示例这里调用最终显示的问候语前缀就是中文的'你好',覆盖了原来默认的'Hello'。
3.5.2 Factory服务组件详解
使用Factory方式定义服务组件相比Provider方式更为简单直观,其主要做法就是创建一个对象,为它添加属性,然后把这个对象返回出来。
【示例3-8】以Factory方式开发并使用类似示例3-7的问候greet服务的过程。
//定义阶段 - 使用factory()方法定义greet服务 angular.module('myApp'). factory('greet', ['$http', function($http) { //将被调用用来获取greet服务的AngularJS约定方法,只会在创建时被调用一次 return { sayHello: function(name){ alert('Hello, '+name); }, sayHelloChinese: function(name){ alert('你好,'+name); } } }]); //使用阶段 - 在控制器里使用greet服务 angular.module('myApp').controller('TestController', [ 'greet', function(greet) { // 调用greet服务实例的sayHello方法,弹出的提示框的输出结果将是"Hello, Word" greet.sayHello('Word'); //调用greet服务实例的sayHelloChinese方法,弹出的提示框的输出结果将是"你好,Ionic" greet.sayHelloChinese ('Ionic'); }]);
【代码解析】代码块的执行顺序就只分为了定义和使用两个阶段。定义阶段的主要任务是返回一个包含函数或属性的对象;而使用阶段更加简单,通过依赖注入的方式声明对服务的使用后,直接调用服务组件实例的方法或属性即可。从代码里读者可以看到要分析Factory方式定义的服务组件也很简单,直接找到代码里被最终返回的对象即可知道服务对外暴露的函数或属性接口了。
3.5.3 Service服务组件简介
使用Service方式定义服务组件与Factory方式相比主要差异在于前者不需要返回对象,而是直接更改函数对象本身。而分别使用两种方式定义出来的服务组件其使用方式是一模一样的。
【示例3-9】以Service方式开发并使用类似示例3-7的问候greet服务的过程。
//定义阶段 - 使用service()方法定义greet服务 angular.module('myApp'). service('greet', ['$http', function($http) { //将被调用用来获取greet服务的AngularJS约定方法,只会在创建时被调用一次 this.sayHello = function(name){ alert('Hello, '+name); }; this.sayHelloChinese: function(name){ alert('你好,'+name); } }]); //使用阶段 - 与使用factory()方法定义的greet服务完全一致,略
【代码解析】定义阶段的主要任务是为函数对象本身增加函数或者属性,使用阶段与Factory服务组件完全一致。
3.5.4 服务类组件特性总结
分别介绍完使用Provider、Factory和Service三种方式定义和使用服务组件后,有必要总结一下服务类组件的特点,帮助读者在未来开发和使用时查阅和把握:
● 服务都是单例的,一旦创建则被缓存起来循环使用。
● 服务由AngularJS特殊的服务组件$injector负责实例化。
● 服务一旦实例化,将在整个应用的生命周期中存在,可以用来共享数据。
● 在需要使用的地方利用依赖注入机制注入服务。
● 依赖注入时,自定义的服务需要写在内置的服务后面。
● 内置服务的命名以$符号开头,自定义服务应该避免使用$前缀。
提示
更灵活而复杂的依赖注入做法是使用上面列表中提到的$injector,不过由于平常时用不上,因此,笔者这里不介绍了。有兴趣的读者可以参考官方文档的说明。