在写Angular应用时,我们通常把与视图逻辑无关的公共代码片段写在service组件中,然后以依赖注入的方式进行使用. Angular service是惰性初始化的(Lazily instantiated),也就是说只有应用组件发现依赖某个service组件时才会实例化依赖的服务组件,angular服务是单例的, 我们可以联想到Spring MVC框架中的service组件。
AngularJS框架中已经提供很多可重用的service组件,例如$http, $scope, $rootScope等,我们将在后面的api详解中具体介绍这些内建的服务组件,内建的组件都以'$'符号开头,因此我们在实现自己的service组件时应当避免在service的名字中用‘$’字符作为前缀。
如何使用Service
只需在组件(controller, service, filter或者directive)的构造方法中声明所依赖的服务组件,Angular的依赖注入子系统便会自动注入服务组件。
让我们通过具体的代码来理解:
index.html
Let's try this simple notify service, injected into the controller...
(you have to click 3 times to see an alert)
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 = []; } }; }]);
代码解析:
1. 在html模板中我们通过ng-app以及ng-controller绑定myServiceModule和MyController,在输入框中通过ng-model绑定message模型,并通过ng-init初始化message的值为“test”。
2. 通过ng-click给NOTIFY按钮添加了callNotify(message)的行为。
3. 在js代码中我们分别实现了MyController控制器和notify服务,notify服务通过factory的工厂方法来实现,依赖于$window服务,在构造函数的形参中声明“factory('notify', ['$window', function(win) {...”, 在MyController构造方法中添加了‘notify'依赖“controller('MyController', ['$scope','notify', function ($scope, notify)”,注意所声明的依赖的顺序必须与最后一个方法中的参数顺序保持一致,这样我们就可以在控制器中使用服务器组件了。
运行结果如图:
创建服务
Anguar应用开发者可以在模块中通过注册服务的名字和实现服务构造方法定义服务组件。服务工厂方法会生成单例或者代表服务的函数,并注入到依赖该服务的组件中。
注册服务
通过Module API注册服务。一般使用Module#factory api注册服务:
例如:
var myModule = angular.module('myModule', []); myModule.factory('serviceId', function() { var shinyNewServiceInstance; // factory function body that constructs shinyNewServiceInstance return shinyNewServiceInstance; });
这里我们通过facotry在'myModule'中实现了服务的构造方法,并没有实例化。
服务的依赖
服务组件也可以依赖其他组件,我们可以在factory的方法签名中声明服务所依赖的组件。我们将在后续章节中详细介绍angular的依赖注入原理。
让我们看如下两个服务的示例代码:
var batchModule = angular.module('batchModule', []); /** * The `batchLog` service allows for messages to be queued in memory and flushed * to the console.log every 50 seconds. * * @param {*} message Message to be logged. */ batchModule.factory('batchLog', ['$interval', '$log', function($interval, $log) { var messageQueue = []; function log() { if (messageQueue.length) { $log.log('batchLog messages: ', messageQueue); messageQueue = []; } } // start periodic checking $interval(log, 50000); return function(message) { messageQueue.push(message); } }]); /** * `routeTemplateMonitor` monitors each `$route` change and logs the current * template via the `batchLog` service. */ batchModule.factory('routeTemplateMonitor', ['$route', 'batchLog', '$rootScope', function($route, batchLog, $rootScope) { $rootScope.$on('$routeChangeSuccess', function() { batchLog($route.current ? $route.current.template : null); }); }]);
在上面两段代码中,我们实现了batchModule模块并分别实现了两个服务 batchLog和routeTemplateMonitor.
1. batchLog服务依赖angular自带的$interval和$log服务,同时用messageQueue实现了对消息的缓存,通过$interval服务周期性的打出日志。
2. routeTemplateMonitor服务依赖 $route, batchLog, $rootScope服务。
3. 这两个服务都是通过数组标记来声明依赖的。
4. 数组中依赖的顺序与工厂方法中的参数顺序需保持一致。
通过$provide注册服务
我们还可以通过$provide服务在模块的config方法中注册服务:
angular.module('myModule', []).config(['$provide', function($provide) { $provide.factory('serviceId', function() { var shinyNewServiceInstance; // factory function body that constructs shinyNewServiceInstance return shinyNewServiceInstance; }); }]);
这种方式主要用于单元测试。
单元测试
在这个例子中我们通过Jasmine框架来测试上述的notify服务.
var mock, notify; 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.callCount).toEqual(2); expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]); });