最近在公司使用用AngularJS(1.3.9)完成了一个项目,在此记录一下过程中遇到的问题及解决方案。
$http
服务发送ajax请求时后端无法判断请求是 XMLHttpRequest
问题及场景:
有时候后端会读取请求中 header
的 X-Requested-With
字段判断前端的请求是否为异步请求 XMLHttpRequest
,在使用 $http
服务发送请求时后端却判断为 false
。
原因:
'X-Requested-With' : 'XMLHttpRequest'
并不属于标准的 header
内容,因此Angular不会在 header
中默认设置该字段。
解决方案:
手动在 $httpProvider
中设置该字段,代码如下:
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
note:
可以创建一个公用服务,在配置方法做这个修改,公用服务注入到每个module里就一劳永逸了。
$http.post()
方法参数类型不正确 问题及场景:
在angular中,使用 $http.post()
方法提交数据时,发现所带参数并非 Form Data
,而是JSON对象,导致服务器无法使用一般方法正确获取参数,而使用jQuery的 $.post()
方法却可以正确获取。
原因:
两者的post对header的处理有所不同,jQuery会把作为JSON对象的myData序列化,例如:
var myData = { a : 1, b : 2 }; // jQuery在post数据之前会把myData转换成字符串:"a=1&b=2"
angular显然没做这个处理。
解决方案:
修改Angular的 $httpProvider
的默认处理,代码如下:
$httpProvider.defaults.transformRequest = function(obj){ var str = []; for(var p in obj) { str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p])); } return str.join("&"); }; $httpProvider.defaults.headers.post = { 'Content-Type': 'application/x-www-form-urlencoded' };
note:
同样应该在公用服务中做此修改。
$scope.$apply(fn)
更新视图时报错 $rootScope:inprog
问题及场景:
在更新$scope上的model数据时,如果是在 digest
监听外,会发现视图并没有自动更新,于是手动调用 $scope.$apply(fn)
方法通知视图进行更新,却发现有时候会报错 $rootScope:inprog
。
原因:
该错误原因是在进程当中 $scope.$apply(fn)
正在执行,不能在此基础上重复调用该方法。
解决方案:
可以在调用该方法时做一个安全检测,封装代码如下:
$scope.safeApply = function(fn) { var phase = this.$root.$$phase; if (phase === '$apply' || phase === '$digest') { if (fn && (typeof(fn) === 'function')) { fn(); } } else { this.$apply(fn); } };
note:
$$phase
变量是 scope
中的一个内部属性,如果为 null
或者 undefined
则说明进程中没有 $apply
方法在运行,则可以直接调用,否则直接执行入参方法。
问题及场景:
我遇到的场景是在发送异步请求时,后端会先判断用户是否已经登录,如果未登录则会在 response
的 header
中添加一个 redirecturl
字段,前端读取该字段控制页面跳转到该url,我首先想到的解决方案就是前端需要做一个统一拦截器,对所有异步请求的 response
进行拦截。
解决方案:
通过看Angular中 $httpProvider
部分的源码注释,发现了拦截器的几种写法,我选择的写法源码注释如下:
* // register the interceptor as a service * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { * return { * // optional method * 'request': function(config) { * // do something on success * return config; * }, * * // optional method * 'requestError': function(rejection) { * // do something on error * if (canRecover(rejection)) { * return responseOrNewPromise * } * return $q.reject(rejection); * }, * // optional method * 'response': function(response) { * // do something on success * return response; * }, * * // optional method * 'responseError': function(rejection) { * // do something on error * if (canRecover(rejection)) { * return responseOrNewPromise * } * return $q.reject(rejection); * } * }; * }); * $httpProvider.interceptors.push('myHttpInterceptor');
我拦截 response
的代码如下:
commonService.factory('redirectInterceptor', function(){ return { 'response': function(response) { if(response.headers().redirecturl) { window.location.href = response.headers().redirecturl; } return response; } }; }); $httpProvider.interceptors.push('redirectInterceptor');
note:
最后一行表示将该拦截器登记到 $httpProvider
的拦截器中,同样应该写在公用服务的配置方法当中。
controller
之间的通信问题 问题及场景:
当有两个视图分别由两个 controller
控制时,其中一个视图发生变化,需通知另一个视图产生了此变化。
解决方案:
总的来说,Angular中控制器通信有三种处理方法:
利用作用域继承的方式 即子控制器继承父控制器中的内容;
基于事件的方式 即 $on
, $emit
, $boardcast
这三种方法;
服务方式 写一个服务的单例然后通过注入来使用。
我选择了最后一种方法,示例代码如下:
//JS var app = angular.module('myApp', []); app.factory('instance', function(){ return {}; }); app.controller('MainCtrl', function($scope, instance) { $scope.change = function() { instance.name = $scope.test; }; }); app.controller('sideCtrl', function($scope, instance) { $scope.add = function() { $scope.name = instance.name; }; });
//html <div ng-controller="MainCtrl"> <input type="text" ng-model="test" /> <div ng-click="change()">click me</div> </div> <div ng-controller="sideCtrl"> <div ng-click="add()">my name </div> </div>
note:
在Angular中服务是一个单例,所以在服务中生成一个对象,该对象就可以利用依赖注入的方式在所有的控制器中共享。
如果不是通过点击产生变化,还可结合 $scope.$watch()
方法来进行通信。
其他两种方法可参考站内文章: AngularJS控制器controller如何通信?
以上为我在编写一个angular应用时遇到的问题及解决方案,记录并分享出来,欢迎大家指正!