创建服务
虽然AngularJS提供了很多有用的服务,但是如果你要创建一个很棒的应用,你可能还是要写自己的服务。你可以通过在模块中注册一个服务工厂函数,或者通过Module#factory api或者直接通过模块配置函数中的$provide api来实现。
所有的服务都符合依赖注入的原则。它们用一个唯一的名字将自己注册进AngularJS的依赖注入系统(injector),并且声明需要提供给工厂函数的依赖。它们的依赖在测试中可以是虚拟的,这使得它们能很好地被测试。
注册服务
要注册服务,你首先要有一个包含该服务的模块。然后你就能通过模块的api或者使用模块配置函数中的$provide服务来注册你的服务了。下面的伪代码显示了这两种方法。
使用angular.Module api:
var myModule = angular.module('myModule', []);
myModule.factory('serviceId', function() {
var shinyNewServiceInstance;
//factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
});
使用$provide服务:
angular.module('myModule', [], function($provide) {
$provide.factory('serviceId', function() {
var shinyNewServiceInstance;
//factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
});
});
注意,你不应该注册一个服务实例,而是一个会在被调用时创建实例的工厂函数。
依赖
服务不仅可以被依赖,还可以有自己的依赖。依赖可以在工厂函数的参数中指定。参阅AngularJS的依赖注入系统,和使用依赖的数组表示法和$inject属性来让依赖表示精简化。
下面是一个很简单的服务的例子。这个服务依赖于$window服务(会被当成参数传递给工厂函数),并且只是个函数。这个服务的任务是存储所有的通知;在第三个通知以后,服务会用window的alert来输出所有的通知。
angular.module('myModule', [], function($provide) {
$provide.factory('notify', ['$window', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}]);
});
实例化AngularJS的服务
所有服务都是延迟实例化的。这意味着所有的服务只有在需要时,或者被依赖时才会实例化。换句话说,AngularJS不会实例化服务,除非被请求了或者被应用直接或间接依赖了。
要注意的是所有的AngularJS服务都是单例的。这意味着在每一个注入器中都只有一个需要的服务的实例。
将服务注入到控制器中
将服务用作控制器的依赖和将服务用作其他服务的依赖很类似。
显式依赖注入
因为Javascript是一种动态语言,依赖注入系统无法通过静态类型来知道应该注入什么样的服务(静态类型语言就可以)。所以,你应该用$inject的属性来指定服务的名字,这个属性是一个包含需要注入的服务的名字字符串的数组。名字要和服务注册到系统时的名字匹配。服务的名称的顺序也很重要:当执行工厂函数时传递的参数是依照数组里的顺序的。但是工厂函数中参数的名字不重要,但最好还是和服务本身的名字一样。举个例子:
angular.
module('MyServiceModule', []).
factory('notify', ['$window', function(win) { //定义了一个服务notify
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}]); function myController(scope, notifyService) { //这里的scope是$scope,notifyService是notify服务
scope.callNotify = function(msg) {
notifyService(msg);
};
} myController.$inject = ['$scope','notify']; //显式定义依赖注入
隐式依赖注入
AngularJS依赖注入系统的新特性使得AngularJS可以通过参数名称来判断依赖。下面的例子展示一下隐式地依赖$window, $scope:
angular.
module('MyServiceModuleDI', []).
factory('notify', function($window) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
$window.alert(msgs.join("\n"));
msgs = [];
}
};
}); function myController($scope, notify) { //通过参数名字,来隐式的定义依赖注入
$scope.callNotify = function(msg) {
notify(msg);
};
但是如果你要压缩你的代码,你的变量名会被重命名,所以这时你就只能显示地指定依赖了。
管理服务依赖
要声明服务的依赖,你可以在工厂方法参数中隐式指明他们,也可以将$inject属性设置成包含了依赖名称的数组,或者是使用数组表示法。不推荐使用$inject属性的这种方法。
使用数组表示法:
function myModuleCfgFn($provide) {
$provide.factory('myService', ['dep1', 'dep2', function(dep1, dep2) {}]); //依赖dep1,dep2服务
}
使用$inject属性:
function myModuleCfgFn($provide) {
var myServiceFactory = function(dep1, dep2) {};
myServiceFactory.$inject = ['dep1', 'dep2']; //依赖dep1,dep2服务
$provide.factory('myService', myServiceFactory);
}
使用隐式依赖(使用代码压缩时会失效):
function myModuleCfgFn($provide) {
$provide.factory('myService', function(dep1, dep2) {});
}
举个例子来说明下服务之间的依赖:
function batchLogModule($provide){
$provide.factory('batchLog', ['$timeout', '$log', function($timeout, $log) {
var messageQueue = [];
function log() {
if (messageQueue.length) {
$log('batchLog messages: ', messageQueue);
messageQueue = [];
}
$timeout(log, 50000);
}
log();
return function(message) {
messageQueue.push(message);
}
}]);
$provide.factory('routeTemplateMonitor',
['$route', 'batchLog', '$rootScope',
function($route, batchLog, $rootScope) {
$rootScope.$on('$routeChangeSuccess', function() {
batchLog($route.current ? $route.current.template : null);
});
}
]
);
}
angular.injector([batchLogModule]).get('routeTemplateMonitor');
上例中有几点要注意:
- batchLog服务依赖内建的$timeout和$log服务,并且允许消息批量地使用console.log记录。
- 和batchLog服务一样,routeTemplateMonitor服务依赖内建的$route服务。
- 自定义的服务都使用隐式表示和数组法来表示自己的依赖。最重要的是数组中的服务的名字顺序要和工厂函数参数的名字顺序对应。除非依赖是隐式地通过函数参数名表示的,那么就是有声明依赖的数组名称顺序决定依赖注入的顺序。
总结
AngularJS服务是一种能执行一个常见操作的单例,比如$http服务是用来操作浏览器的XMLHttpRequest对象的。
要使用AngularJS服务,你只需要在需要的地方(控制器,或其他服务)指出依赖就行了。AngularJS的依赖注入系统会帮你完成剩下的事情。它负责实例化,查找左右依赖,并且按照工厂函数要求的样子传递依赖。
AngularJS web框架提供了一组常用操作的服务。和其他的内建变量或者标识符一样,内建服务名称也总是以"$"开头。另外你也可以创建你自己的服务。
加油!