Angular服務器通信之:$http


$http服務提供了瀏覽器XMLHttpRequest對象的封裝,並且作為Angular中和后台服務通信的底層服務,在此之上Angular還提供了一個可選模塊ngResource支持與RESTFul的后端數據源進行交互。除了Angular本身提供的通信方式外,還有一個封裝良好,更加優雅的第三方庫Restangular可供選擇使用。

在本篇文章中,主要介紹$http的使用,同時也會提及到和它相關的$httpProvider, $q, $cacheFactory等服務。

注意:本文依據Angular v1.4.1進行撰寫。

快速上手

$http服務只接收一個配置對象作為參數,並且返回一個promise對象,具有success和error兩個方法。

// 使用success()和error()
$http({
  method: 'GET',
  url: '/api/user/1'
}).success(function(rep) {
  // ...
}).error(function(err) {
  // ...
});

// 使用then()方法
$http({
  method: 'GET',
  url: '/api/user/1'
}).then(function(rep) {
  // 成功
}, function(err) {
  // 失敗
});

// 也可以分開寫
var promise = $http({method: 'GET', url: '/api/user/1'});
promise.success(function(rep) {});
promise.error(function(err) {});
promise.then(function(rep) {}, function(err) {});

如果響應狀態在200和299之間,會認為響應是成功的,success回調或者then()方法的第一個函數會執行,否者error回調或者then()方法的第二個函數會得到調用。在實際開發過程中除了404外,我們會對每個客戶端請求發送一個響應json數據,並帶有一個狀態碼字段,然后客戶端根據不同的狀態碼來做后續的數據處理。這里給出一個status的定義結構,粗略的概括了幾種常見情況:

var rep_status = [
  {key: 'SUCCESS', value: 1, desc: '交互成功'},
  {key: 'NOT_LOGIN', value: 2, desc: '未登錄'},
  {key: 'INVALID_REQUEST', value: 3, desc: '非法請求'},
  {key: 'INVALID_PARAM', value: 4, desc: '參數錯誤'},
  {key: 'INNER_ERROR', value: 5, desc: '服務器內部錯誤'},
  {key: 'UNKNOWN', value: 6, desc: '未知錯誤'}
]

對於返回api這里也統一約定如下:

// 響應成功返回
{
  status: 1,
  message: 'ok',
  data: {
    total: 200,
    object_list: [
      {
        // ....
      }
    ]
  }
}

// 出現錯誤后端返回
{
  status: 4,
  message: '參數錯誤'
}

通過success()error()得到的響應數據只包含服務器響應數據的主體,如果想得到一個完整的對象,需要使用then()方法。一個完整的響應對象或者叫promise對象包含如下字段:

  • data - {string | Object} 響應主體
  • status - {number} 響應狀態
  • headers - {function([headerName])} 獲取響應頭部的函數
  • config - {Object} 請求觸發時提供的配置對象,后面會詳細介紹
  • statusText - {string} 響應狀態描述
promise.then(function(rep) {
  console.log(rep.data.data.name); // jenemy
  console.log(rep.status); // 200
  console.log(rep.statusText); // OK
  console.log(rep.config.url); // /api/user/5
  console.log(rep.headers()['x-powered-by']); // Express
});

HTTP請求快捷方式

如果每次發起請求都要去配置$http一次參數略顯麻煩,因此Angular對於常見的HTTP請求方式都提供了對應的快捷方法:

  • GET: $http.get(url, config)
  • POST: $http.post(url, data, config)
  • PUT: $http.put(url, data, config)
  • DELETE: $http.delete(url, config)
  • HEAD: $http.head(url, config)
  • JSONP: $http.jsonp(url, config)

注意:上述快捷方式中config為可選參數。

使用方式如下:

var url = '/api/user/5';
var params = {id: 5};
var data = {name: 'jenemy'};

// GET
$http.get(url).then(function(rep) {
  console.log(rep.data.data.name); // jenemy
});

$http.get(url, {params: params}).then(function(rep) {
  console.log(rep.data.data.name); // jenemy
});

// POST
$http.post(url, data).then(function(rep) {
  console.log(rep.message); // ok
});

// PUT
$http.put(url, data).then(function(rep) {
  console.log(rep.message); // ok
});

// DELETE
$http.delete(url, {params: params, data: data}).then(function(rep) {
  console.log(rep.message); // ok
})

// HEAD
$http.head(url, {params: params});

使用$http.jsonp()方法,只需要在請求url后面加上參數callback=JSON_CALLBACK,然后調用success()回調方法獲取返回的數據,這里JSON_CALLBACK必需全部大寫,當然也可以指定其它回調函數,但必須是定義在window下的全局函數。

var params = {name: "jenemy"};

// 使用默認的JSON_CALLBACK回調
// 服務的響應體:angular.callbacks._0({"name":"jenemy","age":25,"gender":"M"})
$http.jsonp('/api/user/5?callback=JSON_CALLBACK', {params: params}).success(function(rep) {
  console.log(rep.name); // jenemy
});

// 自定義回調方法
function handleJsonpCallback(data) {
  console.log(data.name); // jenemy
}
// 服務的響應體:handleJsonpCallback({"name":"jenemy","age":25,"gender":"M"})
$http.jsonp('/api/user/5?callback=handleJsonpCallback', {params: params}).success(function(rep) {
  alert(rep.name); // 只有callbac=JSON_CALLBACK才會執行
})

這里使用node作為jsonp響應后台舉例:

app.get('/api/user/:id', function(req, res) {
  var data = {name: 'jenemy', age: 25, gender: 'M'};
  var str = req.query.callback + '(' + JSON.stringify(data) + ')';
  res.send(str);
});

$http配置屬性

相對於jQuery的$.ajax()的配置來說,Angular中$http()的配置更加簡潔,而又不失功能性,使用起來也相對容易些。這里會對每個配置項進行詳細的說明。

method - {string}

配置希望發送的請求HTTP方法。它的值是下列各項其中之一:'GET'、'POST'、'PUT'、'DELETE'、'HEAD'、'JSONP'。

url - {string}

請求路徑,可以是相對或者絕對地址。

params - {string | Object}

URL請求參數,一個字符串map或對象,會被轉換成查詢字體串追加在URL后面。如果值不是字體串,會被JSON序列化。

// 參數會轉化為 ?name=jenemy&age=25
$http({
  method: 'GET',
  url: '/api/user/5',
  params: {
    name: 'jenemy',
    age: 25
  }
});

data - {string | Object}

作為消息體將要被發送到服務器的數據,通常在發送POST或者PUT時使用。

headers - {Object}

HTTP請求頭部,可以為一個字符串map或對象,也可以是一個函數返回一個對象。

$http({
  method: 'POST',
  url: '/api/user/2',
  data: {name: "jenemy"},
  headers: {
    'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==',
    'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
  }
});

transformRequest與transformResponse

在Angular中可以使用轉換函數對請求和響應數據進行轉換,默認情況下,設置了如下轉換規則:

  • transformRequest:如果發送的請求配置對象中data為一個對象或者包含一個對象,則將其格式化。

  • transformResponse:如果檢測到時了XSRF前綴,則直接丟棄。如果檢測到了JSON響應,則使用JSON解析器(JSON.parse())對它進行序列化。

如果需要在全局修改其默認值,只需要修改$httpProvider.defaults.transformRequest$HttpProvider.defaults.transformResponse值即可。如果想要添加自定義的轉換規則可以通過push和``unshift`添加到轉換鏈( transformation chain)。最后覺得默認的轉換方式不好用,也可以通過直接為默認轉換函數賦新值的方式,而不是采用數組包裝器的方式來完全重寫默認轉換規則。

除了在$httpProvider中設置全或者修改全局的規則外,也可以在運行時通過$http來修改轉換規則,這里只需要在請求方法中的配置對象中設置transformRequesttransformResponse屬性的值即可。

cache - {boolean | Cache}

默認情況下cache機制並沒有開啟,我們需要將請求配置對象中的cache屬性設置為true(使用默認cache)或者自定義一個cache對象(使用cacheFactory構建)。當cache機制激活后,$http會緩存指定的請求url,下一次向同一個url發送的請求的時候會直接加載緩存中的數據,不會再產生一次http請求發出。

$http.get('/api/user', {
  cache: true
}).success(function(rep) {}); // 處理成功的數據

注意后續數據雖然是以緩存的方式讀取的,但是響應依然是異步的。

如果想通過新建一個對象(使用$cacheFactory)來更改默認cache機制設置,簡單更新一下$http.defaults.cache的值即可。一旦更新后,所有已設置cache的請求都將使用新定義的緩存對象。

var myCache = $cacheFactory('myCache');

$http({
  method: 'GET',
  url: '/api/cards',
  cache: myCache
});

如果將默認cache的值轉為false,那么將只有那些自己定義了緩存對象的請求才會被緩存。

timeout - {number | Promise}

如果cache被設置為一個數值,那么請求將會在推遲timeout指定的毫秒數后再發送。如果被設置為一個promise對象,那么當該promise對象被resolve時請求會被中止。

withCredentials - {boolean}

如果該屬性設置為true,那么請求對象中會設置withCredentials標記。

默認情況下,CORS請求不會發送cookie,而withCredentials標記會在請求中加入Access-Control-Allow-Credentials頭,這樣請求就會將目標域的cookie包含在請求中。

responseType - {string}

該選項會在請求中設置XMLHttpRequest屬性,具體有那些屬性可以參考XMLHttpRequest

xsrfHeaderName與xsrfCookieName

在了解這兩個配置作用前,首先要了解什么是CSRF,CSRF (Cross-site request forgery) 跨站請求偽造,也被稱為“One Click Attack”或者Session Riding,通常縮寫為CSRF或者XSRF,是一種對網站的惡意利用。CSRF 則通過偽裝來自受信任用戶的請求來利用受信任的網站。

Angular提供了一種簡單通用的機制來防御XSRF攻擊。首先,在客戶端第一次發送get請求時,服務器響應數據會攜帶一個會話cookie(XSRF-TOKEN)一同發送到客戶端,在后續請求發送前,$http服務會從cookie中讀取一個tocken(默認為XSRF-TOKEN)並且將其設置到HTTP頭部(X-XSRF-TOKEN)一同發送到服務器。然后,服務器端會判斷HTTP頭部是否攜帶X-XSRF-TOKEN值,如果該值與之前發送的會話cookie值相同,就可以判定為來自己同一domain請求,否者會攔截該請求。

明白了Angular如何處理XSRF,理解這兩個配置也就一目了然了。xsrfHeaderName保存XSRF令牌的HTTP頭部名稱, xsrfCookieName保存XSRF令牌的cookie名稱。

angular.module('app', ['ui.router'], function($httpProvider) {
  $httpProvider.defaults.xsrfCookieName = 'csrftoken';
  $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
});

注意:關於這兩個屬性以及CSRF不是本文討論范圍,本人也沒有相關經驗,更多關於CSRF請查看CSRF Token 的設計是否有其必要性?

設置HTTP請求頭部

Angular默認使用application/json提交數據到服務器,如果需要修改默認設置為application/x-www-form-urlencoded的話可以在按下面的方式修改:

var app = angular.module('app', []);
app.config(function($httpProvider) {
  // POST
  $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
  // PUT
  $httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
  // PATCH
  $httpProvider.defaults.headers.patch['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
  // DELETE
  $httpProvider.defaults.headers.delete = { 'Content-Type' : 'application/x-www-form-urlencoded;charset=utf-8' };
})

注意在$httpProvider.defaults.headers中默認是沒有DELETE方式的配置,只有PUTPOSTPATCH,因此需要為其添加一個delete字段來擴展原有對象。通過調用$http.delete()時會發現其默認使用text/plain;charset=UTF-8提交數據,這是因為我們沒有定義請求頭部,Content-Type使用了默認的text/plain;charset=UTF-8

如果想每次發起請求時都帶一個自定義頭部,那么$httpProvider.defaults.headers.common可以派上用場,Angular默認為其默認定義了一個Accept屬性,這里只需要將我們需要的值添加進這個common對象即口。

$httpProvider.defaults.headers.common['myHeader'] = 'myValue';

如果想要刪除頭部某個字段,只需要使用 delete 操作即可

// 刪除后請求頭里不再有 X-Requested-With 屬性
delete $httpProvider.defaults.headers.common['X-Requested-With'];

實際上$httpProvider$http按照C++語言來講是指向同一個指針的兩個對象,因此也可以使用$http來設置頭部,只是兩者適用場景不一樣。

$http.defaults.headers.common['myHeader'] = 'myValue';

上述講的都是全局(使用$httpProvider)或者某個controller中(使用$http)設置頭部,如果需要為某一個單獨的請求設置頭部怎么做呢?其實只需要在其設置對象中的headers字段中添加即可,添加方式如下:

$http({
  method: 'POST',
  url: '/api/user/2',
  data: {name: "jenemy"},
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
  }
});

補充一點,如果請求未設置Content-Type,那么請求參數或者消息體在Chrome的開發都工具下面顯示的是request payload形式,包括application/json類型也是。如果類型設置為application/x-www-form-urlencoded,那么顯示為form data。稍加不注意就會影響最終數據提交,不光是Angular,jQuery也有類似的問題。

$.post()與$http.post()區別

在jQuery中使用$.post()提交數據時其默認Content-Typeapplication/x-www-form-urlencoded,在Chrome的開發都工具下面顯示的是'Form Data',而在Angular中,其默認Content-Typeapplication/json,在Chrome的開發都工具下面顯示的是'Request Payload'形式。這樣會導致在PHP中無法使用$_REQUEST/$_POST獲取到$http.post()的數據,而需要用json_decode()方法。

其實原因很簡單,因為在$.post()中會序列化要提交的數據,而$http.post()不會。

// jQuery會在提交前將數據轉換成字符串:"name=jenemy&age=25&addr=shanghai"
var data = {name: 'jenemy', age: 25, addr: 'shanghai'};

序列化Angular表單數據

當我們修改了$http的默認提交方式為application/x-www-form-urlencoded后,接下要做就是如何將數據序列化。實際上我們有很多種方式來實現數據序列化,可以選擇使用jQuery提交的方法,也可以使用Angular內置的方法,最后也可以自己寫一個序列化的方法來達到目的。

使用jQuery的$.param()方法

var data = {name: 'jenemy', age: 25, addr: 'shanghai', date: '2015-12-12 12:21:00'};

// 序列化后:name=jenemy&age=25&addr=shanghai&date=2015-12-12+12%3A21%3A00
$http({
  method: 'POST',
  url: '/api/user/2',
  data: $.param(data),
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
  }
});

注意:$.param()方法可以正常序列化嵌套子對象,但不能正確的序列化數組。

var data = {foo: "hi there", bar: { blah: 123, quux: [1, 2, 3] }};

// 輸出:foo=hi+there&bar[blah]=123&bar[quux][]=1&bar[quux][]=2&bar[quux][]=3
// 很明顯解析后的數組部分不是我期待的結果:foo=hi+there&bar[blah]=123&bar[quux][0]=1&bar[quux][1]=2&bar[quux][2]=3
console.log(decodeURIComponent($.param(data)));

使用Angular的$httpParamSerializer服務和$httpParamSerializerJQLike服務

var data = {name: 'jenemy', age: 25, addr: 'shanghai', date: '2015-12-12 12:21:00'};

// 序列化后:addr=shanghai&age=25&date=2015-12-12+12:21:00&name=jenemy
$http({
  method: 'POST',
  url: '/api/user/2',
  data: $httpParamSerializer(data),
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
  }
});

$httpParamSerializerJQLike服務從名字就可以看出來它和jQuery的$.param()作用是一樣的,唯一不同點在於它會將字段按字母順序排序。$httpParamSerializer服務同樣會對字段進行排序,不同在於處理序列化時只會處理第一級數據,如果存在嵌套或者數組都會將其看作一個字符串。

var data = {foo: "hi there", bar: { blah: 123, quux: [1, 2, 3] }};

// 序列化后:bar={"blah":123,"quux":[1,2,3]}&foo=hi+there
console.log(decodeURIComponent($httpParamSerializer(_data)));

使用transformRequest在最后發送前使用自定義序列化方法

由於jQuery和Angular提供的方法都無法滿足我們的需求,所以我們需要手工來處理序列化問題,下面是目前所在公司使用的序列化方案:

var app = angular.module('app', ['ui.router'], function($httpProvider) {
  // 設置 Content-Type
  $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
  // 序列化方法
  var param = function(obj) {
    var query = '', name, value, fullSubName, subName, subValue, innerObj, i;

    for(name in obj) {
      value = obj[name];

      if(value instanceof Array) {
        for(i=0; i<value.length; ++i) {
          subValue = value[i];
          fullSubName = name + '[' + i + ']';
          innerObj = {};
          innerObj[fullSubName] = subValue;
          query += param(innerObj) + '&';
        }
      }
      else if(value instanceof Object) {
        for(subName in value) {
          subValue = value[subName];
          fullSubName = name + '[' + subName + ']';
          innerObj = {};
          innerObj[fullSubName] = subValue;
          query += param(innerObj) + '&';
        }
      }
      else if(value !== undefined && value !== null)
        query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
    }
    return query.length ? query.substr(0, query.length - 1) : query;
  };

  $httpProvider.defaults.transformRequest = [function(data) {
    return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;
  }];
});

// 配置路由
app.config(function($stateProvider, $$urlRouterProvider) {
  // ...
});

接下來我們在另外一個controller中發送一個請求:

angular.module('demoController', [])
  .controller('demoPageCtrl', ['$scope', '$http', function($scope, $http) {
    // 要提交的數據
    var data = {foo: "hi there", bar: { blah: 123, quux: [1, 2, 3] }};

    $http({
      method: 'POST',
      url: '/api/user/2',
      data: data,
    }).success(function(rep) {
      alert('數據提交成功!');
    });
  }]);

最終我們提交的數據被序列化成了:foo=hi%20there&bar%5Bblah%5D=123&bar%5Bquux%5D%5B0%5D=1&bar%5Bquux%5D%5B1%5D=2&bar%5Bquux%5D%5B2%5D=3,我們使用decodeURIComponent()解碼后為foo=hi there&bar[blah]=123&bar[quux][0]=1&bar[quux][1]=2&bar[quux][2]=3,格式完全Ok。

回顧上面所做的工作,在app中把POST提交數據格式改成了application/x-www-form-urlencoded,然后在數據發送前進行了序列化。接下我們在隸屬app下面的所有controller中提交數據都是以 Form Data 的形式發送,但是如果想在某個單獨提交時還是使用application/json格式,我們立刻能想到的是再在每個config中設置一次頭部,方式如下:

var data = {foo: "hi there", bar: { blah: 123, quux: [1, 2, 3] }};

$http({
  method: 'POST',
  url: '/api/user/2',
  data: data,
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  }
}).success(function(rep) {
  alert('數據提交成功!');
});

哎呀,怎么失敗了,馬上查看服務器返回的錯誤信息:Error: invalid json,然后再看看發送過去的數據。哦!原來我們發送過去的是序列化后的數據。然后回頭看看我們下面這三行代碼后知道怎么做了

$httpProvider.defaults.transformRequest = [function(data) {
    // PS: 只有當傳入的參數為一個對象時才會序列化
    return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;
  }];

然后解決辦法是使用JSON.stringify()直接把要發送的數據序列化為JSON字符串,這樣服務器就能夠正常解析了

var data = {foo: "hi there", bar: { blah: 123, quux: [1, 2, 3] }};

$http({
  method: 'POST',
  url: '/api/user/2',
  data: JSON.stringify(data), // 序列化為json字符串
  headers: {
    'Content-Type': 'application/json;charset=utf-8'
  }
}).success(function(rep) {
  alert('數據提交成功!');
});

使用攔截器(interceptors)

攔截器盡管名字聽起來有點高大上,但是只是一個$http服務的基礎中間件。首先我們將$httpProvider對象輸出到控制台

{
  "defaults": {
    "transformResponse": [null],
    "transformRequest": [null],
    "headers": {
      "common": {
          "Accept": "application/json, text/plain, */*"
      },
      "post": {
          "Content-Type": "application/json;charset=utf-8"
      },
      "put": {
          "Content-Type": "application/json;charset=utf-8"
      },
      "patch": {
          "Content-Type": "application/json;charset=utf-8"
      }
    },
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "paramSerializer": "$httpParamSerializer"
  },
  "interceptors": [],
  "$get": ["$httpBackend", "$$cookieReader", "$cacheFactory", "$rootScope", "$q", "$injector", null]
}

可以看到$httpProvider中有一個interceptors數組,而所謂的攔截器也就只是一個簡單注入到了該數組中的常規服務工廠。

var app = angular.module('app', []);

app.factory('myInterceptor', function($log) {
    $log.debug('$log輸出啦。。。');

    var myInterceptor = {
      response: function(rep) {
        console.log(rep);
        return rep;
      }
    }
  });

app.config(function($httpProvider) {
  $httpProvider.interceptors.push('myInterceptor');
});

當我們在頁面中向后端發起http請求時就可以看控制台中輸出了我們的調試信息和服務器返回的數據。

一共有四種攔截器,兩種成功攔截器,兩種失敗攔截器。

  • request

該方法會在$http發送請求到后台之前執行,因此可以在這里對設置對象進行修改,或者創建一個新的設置對象,它需要返回一個更新過的設置對象,或者一個可以返回新的設置對象的promise。

app.factory('myInterceptor', function($q) {
  var interceptors = {
    request: function(req) {
      // 修改 Content-Type
      req.headers['Content-Type'] = 'application/json;charset=utf-8';
      // 替換掉要發送的數據
      req.data = {'so', 'magic'};

      // 返回修改后的對象
      return req; // 或者 $q.when(req)
    }
  };

  return interceptors;
});
  • response

該方法會在$http接收到從后台過來的響應之后執行,它可以對響應進行修改,或者創建一個新的響應,它需要返回一個更新過的響應,或者一個可以返回新響應的promise。

  • requestError

Angular會在上一個請求攔截器拋出錯誤,或者promise被reject時調用此攔截器。

  • responseError

Angular會在上一個響應攔截器拋出錯誤,或者promise被reject時調用此攔截器。

由於作者本人經驗不足,對攔截器就介紹一點,感興趣的讀者可以移步angularjs中的interceptor和挺好的例子深入了解。

封裝$http服務,統一管理請求url

Angular為我們提供了一個輕量級的Promise API實現:$q服務,它很好的和$http,'$resource'以及Angular的渲染體系結合在一起。在文章前部分提到過$http調用會返回一個promise對象,因此可以充分利用$q服務提供的方法對返回數據做更加靈活的處理。

在實際項目中,我們還可以對$http服務作一層封裝,比如封裝成HttpService,然后可以將服務器返回的錯誤信息進行一次攔截處理,提供統一的錯誤提示。封裝示例如下:

angular.module('httpService', [])
  .service('HttpService', function($http, $q) {

    var service = {
      get: function(url, config) {
        return handleRepData('get', url, null, config);
      },
      post: function(url, data, config) {
        return handleRepData('post', url, data, config);
      },
      put: function(url, data, config) {
        return handleRepData('post', url, data, config);
      },
      delete: function(url, config) {
        return handleRepData('delete', url, null, config);
      }
    }

    function handleRepData(method, url, data, config) {
      var promise;
      var defer = $q.defer();
      switch (method) {
        case 'get':
          promise = $http.get(url, config);
          break;
        case 'post':
          promise = $http.post(url, data, config);
          break;
        case 'put':
          promise = $http.put(url, data, config);
          break;
        case 'delete':
          promise = $http.delete(url, config)
      }

      promise.then(function(rep) {
        if (rep.data.success || rep.data.status === 1) {
          defer.resolve(rep.data);
        } else {
          var errorMsg = rep.data.message || '哦,出錯啦!但是后端沒有給任何信息。';
          // 彈出錯誤信息,或者重定向到404頁面
          alert(errorMsg);
        }
      }, function() {
        defer.reject('出錯了');
      })

      return defer.promise;
    }

    return service;
  });

然后,將所有的請求url按項目統一放置在一個服務下面:

angular.module('urlService', [])
  .factory('UrlService', function(HttpService) {
    var service = {
      // 數據源
      cms: {
        dataSource: {
          // 新建
          new: function(params_) {
            return HttpService.post('/api/datasource/', params_).then(function(data_) {
              return data_;
            });
          },
          // 更新
          update: function(params_, name_) {
            return HttpService.put('/api/datasource/' + name_ +'/', params_).then(function(data_) {
              return data_;
            });
          },
          // 詳情
          detail: function(params_, name_) {
            return HttpService.get('/api/datasource/'+ name_ +'/', {params: params_}).then(function(data_) {
              return data_;
            });
          },
          // 刪除
          delete: function(params_, name_) {
            return HttpService.delete('/api/datasource/'+ name_ +'/', params_).then(function(data_) {
              return data_;
            });
          },
          {
            // ...
          }
        }
      },
      // 店鋪管理
      store: {
        shop: {
          // 新建
          create: function(params_) {
            return HttpService.post('/api/shop/create/', params_).then(function(data_) {
              return data_;
            });
          },
          // 查重
          checkDuplicate: function(params_) {
            return HttpService.post('/api/shop/search_duplicate/', params_).then(function(data_) {
              return data_;
            });
          },
          // 搜索
          search: function(params_) {
            return HttpService.get('/api/shop/search/', params_).then(function(data_) {
              return data_;
            });
          },
          {
            // ...
          }
        }
      },
      {
        // ...
      }
    };

    return service;
  });

最后我們在控制器中引入UrlService服務:

angular.module('cmsDataSourceController', [])
  .controller('cmsDataSourcePageCtrl', ['$scope', 'UrlService', function($scope, UrlService) {

    var params = {name: name, alias: alias, cate: cate, conds: conds, tags: tags};

    UrlService.cms.dataSource.new(params).then(function(rep) {
      tips('數據源創建成功!');
      $('.btn-update').text('修改數據源');
    });
  }]);

總結

在處理ajax的過程中,涉及到的東西還遠遠不止這些,本文只是初步了探討了Angular中$http服務使用方法,分享了自己在公司中學到的一點點實戰經驗。

文章參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM