AngularJS与服务端协作及登录
$http服务是AngularJS中的核心服务之一,我们可以扩展它,从而实现web应用中的各种常用需求,包括:
1.带有一个通用的错误处理点
2.处理授权和登录重定向
3.与那些无法理解或者返回JSON的服务端交互
4.通过JSONP与外部服务(指同一来源之外的)交互
所以,在这个例子中,我们将会看到一个完整的应用骨架,它具有以下特点:
1.在butterbar指令中显示所有不可恢复的错误(非401),如果存在错误,则将错误显示在所有页面上
2.有一个ErrorService服务,它用来在指令、视图以及控制器之间进行交互
3.每当服务器返回一个401响应时,就会触发一个事件(event:loginRequired)。这个事件会被根控制器处理,根控制器是负责监视并管理整个应用的
4.处理发送给服务器的请求,对于当前特定的用户来说,这些请求需要带有一些授权头信息
我们不分析整个应用(路由、模板等),因为大部分内容都相当简单,我们只重点关注那些重要的部分。
我们首先来看一看Error服务:
var servicesModule = angular.module('myApp.services', []); servicesModule.factory('ErrorService', function(){ return { errorMessage: null, setError: function(msg) { this.errorMessage = msg; }, clear: function() { this.errorMessage = null; } }; });
error message指令是独立于Error服务的,它会查找alert message属性并绑定到此信息上。然后当alert message信息存在时,就显示自身。
//用法:<div alert-bar alertMessage="myMessageVar"></div> angular.module('myApp.directives', []). directive('alertBar', ['$parse', function($parse){ return { restrict: 'A', template : '<div class="alert alert-error alert-bar"' + 'ng-show="errorMessage">' + '<button type="button" class="close" ng-click="hideAlert()">' + 'x</button>' + '{{errorMessage}}</div>', link: function(scope, elem, attrs) { var alertMessageAttr = attrs['alertmessage']; scope.errorMessage = null; scope.$watch(alertMessageAttr, function(newVal){ scope.errorMessage = newVal; }); scope.hideAlert = function() { scope.errorMessage = null; //同时清除绑定变量上的错误信息。 //这样做之后,当同样的错误再次出现时,alert bar会再次显示出来。 $parse(alertMessageAttr).assign(scope, null); }; } }; }]);
然后我们把alert bar添加到HTML中,示例如下:
<div alert-bar alertmessage="errorService.errorMessage"></div>
在添加以上HTML之前需要保证ErrorService被命名为"errorService"并存储在控制器的作用域中。也就是说,如果RootController是负责添加AlertBar的控制器,那么需要使用以下代码:
app.controller('RootController', ['$scope', 'ErrorService', function($scope, ErrorService) { $scope.errorService = ErrorService; });
如我在控制器中增加如下一行代码:
$scope.errorService.errorMessage = '请求服务失败!';
运行结果:
这样就给了我们一种非常优雅的显示和隐藏错误及警告信息的框架。现在,我们来看看如何通过一个拦截器来处理服务器扔给我们的各种状态码。
servicesModule.config(function($httpProvider) { $httpProvider.responseInterceptors.push('errorHttpInterceptor'); }); //把拦截器注册成一个服务,拦截所有Angular ajax HTTP调用 servicesModule.factory('errorHttpInterceptor', function($q, $location, ErrorService, $rootScope) { return function(promise) { return promise.then(function(response) { return response; }, function(response) { if(response.status === 401) { $rootScope.$broadcast('event:loginRequired'); }else if(response.status >= 400 && response.status < 500) { ErrorService.setError('Server was unable to find' + ' what you were looking for... Sorry!!'); } return $q.reject(response); }); }; });
现在唯一缺少的东西就是一些控制器,我们需要用它们来监听loginRequired事件,然后重定向到登录页(或者做一些更复杂的事情,例如显示一个带有登录选项的模态对话框)。
$scope.$on('event:loginRequired', function() { $location.path('/login'); });
接下来的内容就是如何处理需要授权的请求了。举例来说,假设所有需要授权的请求都需要一个头信息“Authorization",它的值为当前登录的用户。由于这个值每次都会改变,所以我们不能使用默认的transformRequests函数,因为这些东西需要在配置层面上进行修改。作为替代 ,我们需要封装$http服务,利用它来创建我们自已的AuthHttp服务。
我们还需要一个Authentication服务,用它来存储用户的授权信息(获取任何你所需要的信息,一般作为登录过程的一部分)。AuthHttp服务将会指向这个Authentication服务,然后添加必要的头信息来为请求授权。
//这个factory只会被执行一次,然后authHttp就会被存储下来。也就是说,后面对authHttp服务的请求将会返回同一个authHttp实例 servicesModule.factory('authHttp', function($http, Authentication) { var authHttp = {}; //在请求上添加正确的头信息 var extendHeaders = function(config) { config.headers = config.headers || {}; config.headers['Authorization'] = Authentication.getTokenType() + '' + authentication.getAccessToken(); }; //针对每个$http调用都需要做以下事情 angular.forEach(['get', 'delete', 'head', 'jsonp'], function(name) { authHttp[name] = function(url, config) { config = config || {}; extendHeaders(config); return $http[name](url, config); }; }); angular.forEach(['post', 'put'], function(name) { authHttp[name] = function(url, data, config) { config = config || {}; extendHeaders(config); return $http[name](url, data, config); }; }); return authHttp; });
所在需要授权的请求都是通过authHttp.get()( 而不是$http.get())发出的。一旦为Authentication服务设置了正确的信息之后,你的调用就可以轻松地发出去了。我们对Authentication也使用了一个服务,所以信息在整个应用中都是可用的,没有必要在每次路由发生变化时都重新获取。
资料来源:《用AngularJS开发下一代Web应用》