使用AngularJS構建應用時遇到的問題及解決方案(版本為1.3.9)
最近在公司使用用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方法在運行,則可以直接調用,否則直接執行入參方法。
添加統一攔截器對ajax的請求或返回做處理
問題及場景:
我遇到的場景是在發送異步請求時,后端會先判斷用戶是否已經登錄,如果未登錄則會在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應用時遇到的問題及解決方案,記錄并分享出來,歡迎大家指正!