黃聰:AngularJS中的$resource使用與Restful資源交互(轉)


原文:http://blog.csdn.net/he90227/article/details/50525836

1.AngularJS中的 $resource

這個服務可以創建一個資源對象,我們可以用它非常方便地同支持RESTful的服務端數據源進行交互,當同支持RESTful的數據模型一起工作時,它就派上用場了。    
  REST是Representational State Transfer(表征狀態轉移)的縮寫,是服務器用來智能化地提供數據服務的一種方式   
     

  1)我們首先需要引入ng-Resource  模塊,在angular之后

  <script src="js/vendor/angular.js"></script> 

  <script src="js/vendor/angular-resource.js"></script>

 

  2) 在我們的應用中需要將其當做依賴進行引用 
  angular.module('myApp', ['ngResource']); 
  
  3)如何使用? 
  $resource服務本身是一個創建資源對象的工廠,返回的$resource對象中包含了同后端服務器進行的交互的高層API. 
  
    var User=$resource('/api/users/:userId',{userId:'@id'}); 

    可以把User對象理解成同RESTful的后端服務進行交互的接口。

 

【HTTP GET類型的方法】

  ①GET請求:  get(params,successFn,errrorFn)

   不定義具體的參數,get()請求通常被用來獲取單個資源。

 

   //GET /api/users 
   User.get(function(resp){ 
    //處理成功 
    
   },function(err){ 
    //處理錯誤 
    
   }); 
     
  如果參數中傳入了具名參數(我們例子中的參數是id),那么get()方法會向包含id的URL發送請求:    
    //發起一個請求:GET-->/api/users/123 
    User.get({id:'1234'},function(resp){ 
       //success 
    },function(error){ 
       //fail 
    }); 
     
  ②QUERY 請求:query向指定URL發送一個GET請求,並期望返回一個JSON格式的資源對象集合。 
  //發起一個請求 
  User.query(function(users){ 
    //讀取集合中的第一個用戶 
    var user=users[0]; 
  }); 
     
  query()和get()方法之間唯一的區別是AngularJS期望query()方法返回數組。   

 

   

【非HTTP GET類型的方法】     

  1. save(params, payload, successFn, errorFn) 
  save方法向指定URL發送一個POST請求,並用數據體來生成請求體。save()方法用來在服務器上生成一個新的資源。 payload:代表請求發送的數據體 

   //發送一個請求 with the body {name: 'Ari'}

   User.save({},{name:'Ari'},function(resp){ 
    
   },function(error){ 
    
   }); 
     
 2. delete(params, payload, successFn, errorFn)    
 delete方法會向指定URL發送一個DELETE請求,並用數據體來生成請求體。它被用來在服務器上刪除一個實例:    
     
    // DELETE /api/users 
    User.delete({}, { 
        id: '123' 
    }, function(response) { 
    // 處理成功的刪除響應 
    }, function(response) { 
    // 處理非成功的刪除響應 
    }); 
     
  3. remove(params, payload, successFn, errorFn) 
    remove方法和delete()方法的作用是完全相同的,它存在的意義是因為delete是JavaScript的保留字,在IE瀏覽器中會導致額外的問題。   
     
    // 發起一個請求: 
    // DELETE /api/users 
    User.remove({}, { 
     id: '123' 
    }, function(response) { 
    // 處理成功的刪除響應 
    }, function(response) { 
    // 處理非成功的刪除響應 
    }); 

 

2.$resource Restful api 與 ngResoruce

 

 

$http服務提供了一個非常低級的實現,可以用來發送XHR請求,同時它還為你提供了很大的可控性和靈活性。但是,在大多數情況下,我們需要處理對象,以及封裝了特定屬性和方法的對象模型,例如一個person對象(帶有詳細信息),或者一個信用卡對象。

        在這些情況下,如果我們能夠創建一個JS對象,而且它可以理解並代表這種對象模型,是不是會很棒?如果我們僅僅編輯這個對象的屬性,例如保存或者更新,那么這些狀態會被持久化到服務端嗎?

        $resource就是為這一功能而設計的。AngularJS中的resource(資源)允許我們用描述性的方式來定義對象模型,它可以描述以下內容:

        1.資源在服務端的URL。

        2.常用的請求參數類型。

        3.一些附加的方法(你可以自動獲得get、save、query、remove和delete方法),這些方法為對象模型包裝了特定的功能和業務邏輯(例如信用卡對象的charge()方法)。

        4.期望獲得的響應類型(一個數組或者一個對象)。

        5.協議頭。

        使用Angular所提供的$resource對象,你可以根據各種需求查詢服務器;除此之外,你還可以把服務端返回的對象當成已經持久好的數據模型,你可以修改它們,並且可以把它們持久化。

        ngResource是一個獨立的、可選的模塊。為了使用它,需要:

a.在加載的腳本文件中包含angular-resource.js

b.在模塊依賴聲明中包含ngResource(例如,angular.module('myModule', ['ngResource']))。

c.在需要的地方使用注入的$resource服務。

        在學習如何使用ngResource方法創建資源之前,我們先來看看使用基本的$http服務創建類似的東西需要做些什么事情。對於我們的信用卡資源來說,除了要能夠對它進行"change"(收費)操作之外,我們還要能夠get(獲取)、query(查詢)以及save(保存)信用卡。

        以下是一種可能的實現:

[js]  view plain  copy
 
  1. myAppModule.factory('CreditCard', ['http', function($http) {  
  2.  var baseUrl = '/user/123/card';  
  3.  return {  
  4.  get: function(cardId) {  
  5.  return $http.get(baseUrl + '/' + cardId);  
  6. },  
  7.  save: function(card) {  
  8.  var url = card.id ? baseUrl + '/' + card.id : baseUrl;  
  9.  return $http.post(url, card);  
  10. },  
  11.  query: function() {  
  12.  return $http.get(baseUrl);  
  13. },  
  14.  charge: function(card) {  
  15.  return $http.post(baseUrl + '/' + card.id, card, {params: {charge: true}});  
  16. }  
  17. };  
  18. }]);  

        除了這種方式之外,還可以簡單地創建一個Angular服務,這個服務將會通過以下方式來描述應用所提供的資源:

[js]  view plain  copy
 
  1. myAppModule.factory('CreditCard', ['$resource', function($resource) {  
  2.  return $resource('/usr/:userId/card/:cardId',  
  3.  {userId: 123, cardId: '@id'},  
  4.  {charge: {method: 'POST', params: {charge: true}, isArray: false});  
  5. }]);  

        現在,只要向我們AngularJS注射器請求一個CreditCard實例,我們就可以獲取一個Angular資源,它默認為我們提供了一些基礎的方法。下表列出了這些方法的內容以及它們的行為,有了這些信息你就知道應該如何配置服務端了。         下面我們來看一個信用卡的實例,這會讓我們的思路更加清晰。

[js]  view plain  copy
 
  1. //假設CreditCard服務被注入到了這里  
  2.   
  3. //我們可以從服務端獲取一個集合,請求的路徑為GET:/user/123/card  
  4. var cards = CreditCard.query();  
  5.   
  6. //我們還可以在回調函數中獲取並使用單張信用卡  
  7. CreditCard.get({cardId: 456}, function(card) {  
  8. //每個實例都是CreditCard類型  
  9.  expect(card instanceof CreditCard).toEqual(true);  
  10.  card.name ="J.Smith";  
  11. //非GET型的方法被映射到了實例上  
  12. card.$save();  
  13.   
  14. //我們自定義的方法也被映射上去了  
  15. card.$charge({amount:9.99});  
  16. //發起一個POST請求:/user/123/card/456?amount=9.99&charge=true  
  17.  //發送請求時傳遞的數據為:{id:456, number: '1234', name: 'J.Smith'}  
  18. });  

        這個例子涉及了比較多的內容,對於其中比較重要的內容依次介紹如下:

一.聲明

        無論是自已定義$resource,還是使用正確的參數來調用注入的$resource函數,操作都非常簡單。

        $resource函數有一個必需的參數,即可用資源的URL地址,還有兩個可選的參數,即默認參數以及你想配置在資源上的額外動作。

        請注意URL是參數化的(用:來標識參數。:userId表示userId將會被替換成對應的文本,:cardId表示將會被cardId實參的值替換掉)。如果沒有傳遞參數,對應的標識符會被替換成空字符串。

        第二個參數負責處理每一個請求中都會被發送的默認值。在當前這個例子中,我們會把常量123傳遞給userId。參數cardId更加有趣,“cardId是"@id."”表示的是,如果我們正在使用一個從服務端返回的對象,那么當調用這個對象上的任意方法時(例如調用對象的$save方法),對象上的id屬性值就會被賦給cardId參數。

        第三個參數是另一個函數,我們希望在自定義的資源上暴露這個函數。

 

二.自定義方法

        調用$resource時,傳遞的第三個參數是一個可選的。我們希望在自已的資源上暴露的方法。

        在前面的例子中,我們指定了一個charge方法,可以通過傳遞一個對象來配置這個方法,對象中的key就是需要暴露的方法名稱。配置項中需要指定的內容有:請求的類型(GET、POST等)、需要作為請求的一部分來傳遞的參數(在這個例子中就是charge=true),以及返回的結果是否是一個數組(在這個例子中不是)。一旦做完這些事情之后,你就可以自由地調用CreditCard.charge()了

        說明:這是一種非常靈活的編碼風格,根據上面的代碼,對於配置對象{charge: {method: 'POST', params: {charge: true}, isArray: false},Angular會將其解析成一個方法,然后把這個方法綁定到返回的Restful對象上,上面的配置對象解釋之后的方法為:

[js]  view plain  copy
 
  1. CreditCard.charge = function(charge, isArray) {  
  2. //這里是方法體  
  3. }  

 

三.別用回調!(除非你真的需要它們)

        第三個需要注意的內容是調用資源時的返回值類型。請再看一下CreditCard.query()調用,我們直接把信用卡對象賦值給了card變量,而並沒有在回調函數里面進和賦值。你可能會擔心在對服務器進行異步請求的情況下,這種代碼能運行嗎?

        你這種擔心是合理的。但事實上,這段代碼完全正確,並且能夠運行。這里發生的事情是,AngularJS賦給了card對象一個引用(一個對象或者數組,具體是什么需要根據所期望的返回值類型而定),在未來的某個時間上,當對服務器的請求返回來之后,這個引用才會被真正賦值。在些期間,引用對象一直是空的。

        對於AngularJS應用來說,最常見的處理流程是:到服務器上獲取數據,然后把數據賦值給變量,再把數據顯示到模板中。這種快捷方式是非常好用的。在控制器代碼中,你唯一要做的事情就是發起對服務端的調用,把返回值賦給正確的作用域變量,然后讓模板自動負責渲染它。由於card變量是使用{{}}這種數據綁定技術綁定到視圖上的,所以一開始給它一個空值並沒有問題,等異步響應返回之后再把結果賦給它。這時候Angular的數據綁定機制會立即發現數據發生了變化,然后會自動通知視圖進行刷新。從這里可以看到,使用Angular框架時,對異步調用的很多處理方式已經發生了細微的變化。

        如果你有一些需要依賴於返回值才能執行的業務邏輯,那么這種方法就不會奏效。在這種情況下,你就需要使用回調函數,這個回調函數會在調用CreditCard.get()的時候被使用。

 

四.簡化服務端操作

        無論你使用返回值的快捷方式,還是使用回調函數,都有一些關於返回對象的注意事項。返回值不是普通的JS對象,而是一個"resource"型的對象。這就意味着,除了服務端返回的數據之外,它上面還帶有一些附加的行為(在這個例子中就是$save()和$charge())。這樣可以讓你更容易進行服務端調用,例如獲取數據、修改數據,以及把修改的內容持久化到服務端(也就是在很多應用中都很常見的CRUD操作)。

 

五.何時可以使用Angular資源

        只有服務端按照RESTful的方式工作的時候,你才可以使用Angular資源。對於信用卡場景,它需要:

        1.一個到/user/123/card的GET請求,它會返回用戶123的信用卡列表。

        2.一個到/user/123/card/15的GET請求,它會返回用戶123的ID為15的信用卡。

        3.一個到/user/123/card的POST請求,在POST的數據中帶有信用卡信息,它將會為用戶123的ID創建一張新的信用卡。

        4.一個到/user/123/card/15的POST請求,POST的數據中帶有信用卡信息,它將會更新用戶123的ID為15的信用卡信息。

        5.一個到/user/123/card/15的DELETE請求,它將會刪除用戶123的ID為15的信用卡信息。

 

 

 

 

我發現一個Angular JS中的關鍵問題是(以我喜歡的代碼工作方式來說)$save方法在ngResource中將只會使用POST沿着有效載荷提交到服務器。新建和更新記錄操作都是這樣的,對來自服務器的新和舊的對象都是如此。這破壞了 RESTful約定的更新操作應該使用PUT或者PATCH操作。我下面建議的解決方案拓展了現有的ngResource實現,提供了更多的默認選項,同時精簡了我們的工作流程。完美的用法(恕我直言)應該像下面這樣:

1
2
3
4
5
6
7
var  user =  new  User;
user.name =  'Kirk Bushell' ;
user.$save();  // POST
 
var  user = User.get( { id: 1 });
user.name =  'John smith' ;
user.$save();  // PUT

    如果我們深入ngResource的代碼中,這樣的需求是可能的,關於怎么樣去簡化它的實現(這應該是由 Angular 的團隊來完成)。不幸的是,它的確意味着如果我們想要同時用POST/PUT來實現保存操作,我們不得不用兩個不同的方法(這不是我的風格)。恕我直言,保存就是保存 --- 讓你的模塊/類 來定義這是什么樣的保存(新建或是更新)操作。我們需要做的是用我們自己的默認實現來拓展ngResource提供的 $resource工廠。讓我們接着看下去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var  module = angular.module(  'my.resource' , [  'ngResource'  ] );
 
module.factory(  'Resource' , [  '$resource' function ( $resource ) {
    return  function ( url, params, methods ) {
     var  defaults = {
       update: { method:  'put' , isArray:  false  },
       create: { method:  'post'  }
     };
     
     methods = angular.extend( defaults, methods );
 
     var  resource = $resource( url, params, methods );
 
     resource.prototype.$save =  function () {
       if  ( ! this .id ) {
         return  this .$create();
       }
       else  {
         return  this .$update();
       }
     };
 
     return  resource;
   };
}]);

    這里我們定義了一個自定義模塊 - my.resource,這個模塊可以被注入到其他你想要這個拓展功能的模塊中。我們接着以一個依賴為我們的Resource工廠注入$resource,並做一些小魔法,讓我們研究下吧。

    首先,我們定義了一個新的默認數組。它包括了為resource的更新update和新建create方法 - create方法將會被定義成一個POST請求,update方法將會被定義成一個PUT請求。我們為什么會想要這兩個額外的方法?因為它允許我們做更明確的請求,正因如此,我們需要重載$save方法!

    我們拓展了任何我們會提供給resource的方法。然后,我們定義我們的新resource和通過重載$save方法拓展它。這個方法會檢查id字段是否包含在一個資源對象中,如果有id字段,它將會調用我們定義在default中的$update方法;如果沒有id字段,它會調用$create方法,很簡單吧!

    但是 - 我們怎么在我們自己的資源中使用它呢?小菜一碟。

1
2
3
4
5
var  module = angular.module(  'services' , [  'my.resource'  ] );
 
module.factory(  'User' , [  'Resource' function ( $resource ) {
   return  $resource(  'users/:id' , { id:  '@id'  } );
}]);

    現在你可以看到 - 我們對待它就像對待其他的資源一樣注入,唯一的區別是 - 我們定義了我們的 $resource依賴於我們自己進行拓展ngResource 后的Resource。

 

 

 

3..AngularJS Resource 與 Restful API的交互

 

REST(表征性狀態傳輸,Representational State Transfer)是Roy Fielding博士在2000年他的博士論文中提出來的一種軟件架構風格。RESTful風格的設計不僅具有更好的可讀性(Human Readable),而且易於做緩存以及服務器擴展(scalability)。REST風格體現在URL設計上:

  • 每個URL對應一個資源
  • 對資源的不同操作對應於HTTP的不同方法
  • 資源表現形式(representation)通過AcceptContent-Type指定

AngularJS提供了$resourceService來更方便地與RESTful服務器API進行交互,可以方便地定義一個REST資源,而不必手動所有的聲明CRUD方法。

參考文檔: https://docs.angularjs.org/api/ngResource/service/$resource

Resource Factory

$resourceService定義在ngResourceModule中,需要在你的HTML中引入這個Module對應的JS,同時在你的APP中添加這樣一個依賴:

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

然后為資源建立一個Factory:

app.factory('Notes', ['$resource', function($resource) { return $resource('/notes/:id'); }]); 

當然,你也可以不把$esource的實例放到Factory里,直接在控制器中存起來:var Notes = $resource('/notes/:id)

CRUD

在你的控制器中就可以對資源進行增刪改查了:

app.controller('NotesCtrl', ['$scope', 'Notes', function($scope, Notes) { var notes = Notes.query(function(){ // GET: /notes // Response: [{id: 1, content: 'hello'}, {id: 2, content: 'world'}]; var first = notes[0]; first.content = 'halo'; first.$save(); // POST: /notes/1 {id: 1, content: 'halo'} // Response: {id: 1, content: 'halo'} second.$delete(); // DELETE: /notes/2 }); var note = new Notes({content: 'xxx'}); note.$save(); // POST: /notes // Response: {id: 3, content: 'xxx'} }]); 

PUT 操作

$resource提供了五種默認操作:getquerysaveremovedelete。你可以配置一個update操作來完成HTTP PUT:

app.factory('Notes', ['$resource', function($resource) { return $resource('/notes/:id', null, { update: { method:'PUT' } }); }]); 

現在,你可以在控制器中獲取一個note並更新它:

var note = Notes.get({ id: 3}), $id = note.id; note.content = 'yyy'; Notes.update({ id:$id }, note); // PUT /notes/3 {id: 3, content: 'yyy'} 

現在你的Notes有六種操作了。這些操作有兩種調用方式:

  1. 通過資源類調用,例如:Notes.update({id: xxx})
  2. 通過資源實例調用,例如:note.$update(),此時操作名需加前綴$

具體的調用參數可參考文檔:

HTTP GET "class" actions: Resource.action([parameters], [success], [error])

non-GET "class" actions: Resource.action([parameters], postData, [success], [error])

non-GET instance actions: instance.$action([parameters], [success], [error])

其中,success參數為(value, responseHeaders),error參數為(httpResponse)

屬性/URL映射

上述例子中,我們看到note對象的id屬性會映射到URL中的:id/notes/:id)。如果你的業務更加復雜,可以手動配置這個映射關系。例如:

var Notes = $resouce('/users/:userId/notes/:noteId', { noteId: '@id', userId: '@owner' } 

將會讀取noteownerid屬性來生成URL,比如刪除note時:

// note === {id: 123, owner: 'alice', content: 'hello'} note.$delete(); // DELETE: /users/alice/notes/123 

在構造$resource時,多於的屬性映射會成為URL Query。例如:

var Notes = $resouce('/notes/:id', { id: '@id', user: '@owner' }); // note === {id: 123, owner: 'alice', content: 'hello'} note.$delete(); // DELETE: /notes/123?user=alice 

REST操作的聲明和調用中,多於的屬性會成為URL Query。例如:

var Notes = $resouce('/notes/:id', {id: '@id'}, { update: {method: 'PUT', operator: 'bob'} }); // note === {id: 123, content: 'hello'} note.$update({trusted: true}); // PUT: /notes/123?operator=bob&trusted=true {id: 123, content: 'hello'} 

響應轉換

有時基於既定的后台設計,無法提供完全RESTful的API,比如/notes返回的是一個分頁器對象,而非數組。此時,我們仍然可以使用$resource,但需要設置響應轉換回調。例如:

var Notes = $resouce('/notes/:id', null, { pager: { method: 'GET', transformResponse: function(data, headers){ // Server respond: // data = {currentPage: 1, // totalPage: 20, // pageSize: 2, // content: [{id: 1, content: 'hello'}, {id: 2, content: 'world'}]} var pager = JSON.parse(data); return pager.content; } } }); var notes = Notes.query(function(){ // GET: /notes // notes === [{id: 1, content: 'hello'}, {id: 2, content: 'world'}] }); 

類似響應重寫,你還可以設置請求轉換transformRequest

雖然$resource的設計可以支持絕大多數的URL和內容表示設計,但如果你發現$resource的使用過程極其復雜,那可能是你的服務器API並不滿足RESTful風格。


免責聲明!

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



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