Angular开发者指南(五)服务
服务
AngularJS服务是使用依赖注入(DI)连接在一起的可替代对象。 可以使用服务在整个应用程式中整理和分享程式码。
AngularJS服务有:
- 延迟初始化 - AngularJS只在应用程序组件依赖它时实例化服务。
- 单例 - 依赖于服务的每个组件获取对服务工厂生成的单个实例的引用。
AngularJS提供了几个有用的服务(如$http),但对于大多数应用程序,你也想创建自己的。像其他核心的AngularJS标识符一样,内置服务总是以$开头(例如$http)。
使用服务
要使用AngularJS服务,请将其添加为依赖于依赖于服务的组件(控制器,服务,过滤器或指令)的依赖项。 AngularJS的依赖注入子系统负责其余的。
index.html
<div id="simple" ng-controller="MyController" ng-app="myServiceModule">
<p>让我们尝试这个简单的通知服务,注入到控制器</p>
<input ng-init="message='test'" ng-model="message" >
<button ng-click="callNotify(message);">NOTIFY</button>
<p>(必须点击3次才能看到提醒)</p>
</div>
script.js
angular.
module('myServiceModule', []).
controller('MyController', ['$scope', 'notify', function($scope, notify) {
$scope.callNotify = function(msg) {
notify(msg);
};
}]).
factory('notify', ['$window', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length === 3) {
win.alert(msgs.join('\n'));
msgs = [];
}
};
}]);
protractor.js
it('should test service', function() {
expect(element(by.id('simple')).element(by.model('message')).getAttribute('value'))
.toEqual('test');
});
需要点击三次NOTIFY按钮才会出现弹出框内容
创建服务
应用程序开发人员可以使用AngularJS模块注册服务的名称和服务工厂函数来自由定义自己的服务。
服务工厂函数生成表示对应用程序其余部分的服务的单个对象或函数。 服务返回的对象或函数被注入到指定对服务的依赖性的任何组件(控制器,服务,过滤器或指令)中。
注册服务
服务通过Module API注册到模块。 通常您使用Module factory API注册服务:
var myModule = angular.module('myModule', []);
myModule.factory('serviceId', function() {
var shinyNewServiceInstance;
// 构造shinyNewServiceInstance的工厂函数体
return shinyNewServiceInstance;
});
此时不是注册服务实例,而是一个在调用时将创建此实例的工厂函数。
依赖
服务可以有自己的依赖。 就像在控制器中声明依赖项一样,可以通过在服务的工厂函数签名中指定依赖性来声明它们。
下面的示例模块有两个服务,每个具有各种依赖关系:
var batchModule = angular.module('batchModule', []);
/**
*`batchLog'服务允许消息在内存中排队,并且每50秒刷新到console.log
*/
batchModule.factory('batchLog', ['$interval', '$log', function($interval, $log) {
var messageQueue = [];
function log() {
if (messageQueue.length) {
$log.log('batchLog messages: ', messageQueue);
messageQueue = [];
}
}
// 开始定期检查
$interval(log, 50000);
return function(message) {
messageQueue.push(message);
}
}]);
/**
*`routeTemplateMonitor`监视每个`$ route`更改,并通过`batchLog`服务记录当前模板
*/
batchModule.factory('routeTemplateMonitor', ['$route', 'batchLog', '$rootScope',
function($route, batchLog, $rootScope) {
return {
startMonitoring: function() {
$rootScope.$on('$routeChangeSuccess', function() {
batchLog($route.current ? $route.current.template : null);
});
}
};
}]);
在示例中,需要注意的是:
- batchLog服务取决于内置的$interval和$log服务。
- routeTemplateMonitor服务取决于内置的$route服务和$rootscope和我们的自定义batchLog服务。
- 两个服务都使用数组符号来声明它们的依赖关系。
- 数组中标识符的顺序与工厂函数中参数名称的顺序相同。
还可以通过模块的配置函数中的$provide服务注册服务:
angular.module('myModule', []).config(['$provide', function($provide) {
$provide.factory('serviceId', function() {
var shinyNewServiceInstance;
// 构造shinyNewServiceInstance的工厂函数体
return shinyNewServiceInstance;
});
}]);
这种技术通常用于单元测试中来模拟服务的依赖。
单元测试
以下是来自上面的创建服务示例的通知服务的单元测试。 单元测试示例使用Jasmine spy(mock),而不是真正的浏览器alert。
var mock, notify;
beforeEach(module('myServiceModule'));
beforeEach(function() {
mock = {alert: jasmine.createSpy()};
module(function($provide) {
$provide.value('$window', mock);
});
inject(function($injector) {
notify = $injector.get('notify');
});
});
it('should not alert first two notifications', function() {
notify('one');
notify('two');
expect(mock.alert).not.toHaveBeenCalled();
});
it('should alert all after third notification', function() {
notify('one');
notify('two');
notify('three');
expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
});
it('should clear messages after alert', function() {
notify('one');
notify('two');
notify('third');
notify('more');
notify('two');
notify('third');
expect(mock.alert.calls.count()).toEqual(2); expect(mock.alert.calls.mostRecent().args).
toEqual(["more\ntwo\nthird"]);
});