angular學習筆記(二十八)-$http(6)-使用ngResource模組構建RESTful架構

詩&遠方發表於2014-08-13

ngResource模組是angular專門為RESTful架構而設計的一個模組,它提供了'$resource'模組,$resource模組是基於$http的一個封裝.下面來看看它的詳細用法

1.引入angular-resource.min.js檔案

2.在模組中依賴ngResourece,在服務中注入$resource

var HttpREST = angular.module('HttpREST',['ngResource']);
HttpREST.factory('cardResource',function($resource){
    return $resource('/card/user/:userID/:id',{userID:123,id:'@id'},{charge:{method:'POST',params:{charge:true},isArray:false}})
});

3.$resource的引數:

$resource(url,{url引數},{自定義方法})

url: 必填,資源的基礎url

url中帶有 ':' 項的是根據第二個引數來進行配置的. 

url引數: 選填,配置url中的帶有 ':' 項的引數

eg:

('/card/user/:userID/:id',{userID:123,id:'@id'}),那麼userID會被配置為123.

另外,在呼叫$resource()的方法的時候(比如get,query...),可以傳入引數覆蓋這裡對url引數的配置,這在後面說得到它的方法的時候再詳解

而id屬性在後面講第三個引數的時候講解

自定義方法: 

使用$resource獲取到的資源,或者通過$resource例項化的資源,資源本身會具有一些方法,比如$save,第三個引數用於給資源新增自定義的方法:詳見:http://www.cnblogs.com/liulangmao/p/3907032.html

4.$resource()的方法:

$resource()一共有以下5個方法:

get: 

{method:'GET'}

一般用於獲取某個資源:

query:

{method:'GET',isArray:true}

一般用於獲取一整套的資源,以陣列形式返回

save:

{method:'POST'}

一般用於儲存某個資源,有可能是新建的資源,也有可能是更新現有的資源

remove:

{method:'DELETE'}

一般用於刪除某個資源

delete:

{method:'DELETE'}

一般用於刪除某個資源

 

eg:

1.首先通過$resource建立一個服務:

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

HttpREST.factory('cardResource',function($resource){
    return $resource('/card/user/:userID/:id',{userID:123,id:'@id'},{charge:{method:'POST',params:{charge:true},isArray:false}})
});

這個cardResource服務,返回的是一個物件,物件有get,query,save,remove,delete五個方法

 

2.然後我們通過cardResource這個服務,來建立另外一個獲取資源的服務:

HttpREST.factory('httpCard',function($q,cardResource){
    return {
        getById:function(cardID){
            var defer = $q.defer();
            cardResource.get({id:cardID},function(data,headers){
                defer.resolve(data);
            },function(data,headers){
                defer.reject(data);
            });
            return defer.promise
        },
        query:function(){
            var defer = $q.defer();
            cardResource.query(function(data,headers){
                defer.resolve(data);
            },function(data,headers){
                defer.reject(data);
            });
            return defer.promise
        }
    }
});
httpCard這個服務返回的物件有兩個方法,一個getById方法,用於通過id載入資源,一個query方法,用於獲取全部的資源.

然後來講解一下get方法和query方法的用法:

get和query方法都是GET型別的請求,他們的呼叫方式是相同的:

cardResource.action([parameters], [success], [error])

[parameters]: 可選. 一個json物件,用於配置url裡的引數,比如這裡寫了{id:cardID},那麼提交的請求url就是 '/card/user/123/cardID'.

                    可以不填,不填就直接按照$resource()裡的url來提交,注意,不填的話,不需要給個空,可以直接寫success回撥,angular能夠判斷出它沒有填第一個引數,而不是死板的按照順序來解讀引數.

[success]:可選. 請求成功後的回撥函式.回撥接受2個引數(注意這裡和$http有所不同):

              function(data,headers){

              //data是請求到的內容

              //headers是響應頭

              }

[error]:可選. 請求失敗後的回撥.回撥接受1個引數

              function(httpResponse){

              //httpResponse暫不知道是什麼. 反正是和響應有關

              }

凡是通過$resource返回的物件,一定是json格式的,如果後臺返回的資料不是json,$resource也會按照自己的方式處理成json格式,比如後臺返回字串'我愛你',那麼如果是get方法,得到的資料就是:

{
0:我,
1:愛,
2:你
}

而query方法定義了isArray為true,所以他的返回值必須是陣列,並且陣列裡的每個值都必須是json格式的物件.如果返回的是字串'我愛你',那麼如果是query方法,得到的資料就是:

[

{0:'我'},

{0:'愛'},

{0:'你'}

]

如果返回的是一個json物件{name:'code_bunny'},那麼得到的資料就是:

[{name:'code_bunny'}]

所以,在後臺最好就做好相應的處理,按照規範格式返回資料

 

3. 在控制器中使用httpCard服務來獲取資源:

HttpREST.controller('Card',function($scope,httpCard,cardResource){
    //通過id獲取銀行卡
    $scope.card_1 = httpCard.getById(1);
    $scope.card_2 = httpCard.getById(2);
    $scope.card_3 = httpCard.getById(3);
    //獲取所有的銀行卡
    $scope.cards = httpCard.query();

});
  <span>{{card_1['name']}}</span>
  <span>{{card_1['amount']}}</span>
  <br/>
  <span>{{card_2['name']}}</span>
  <span>{{card_2['amount']}}</span>
  <br/>
  <span>{{card_3['name']}}</span>
  <span>{{card_3['amount']}}</span>
  <br/>

node:

var cards = [
    {
        id:1,
        name:'建設銀行',
        amount:0
    },
    {
        id:2,
        name:'中國銀行',
        amount:0
    },
    {
        id:3,
        name:'上海銀行',
        amount:0
    }
];

app.get('/card/user/123/:id',function(req,res){
    var data = cards[req.params.id-1];
    setTimeout(function(){res.send(data)},2000)
});
app.get('/card/user/123',function(req,res){
    res.send(cards)
});

關於card_1...cards明明是promise物件,但檢視中卻正確顯示了資源的問題,請參考:http://www.cnblogs.com/liulangmao/p/3907307.html

  

4. 在控制器中使用cardResource服務的save方法來更新資源:

HttpREST.controller('Card',function($scope,httpCard,cardResource){
    $scope.card_3 = httpCard.getById(3);
    //更新id為3的銀行卡
    $scope.updataCard = function(){
        $scope.card_3.then(function(data){
            data.name='工商銀行';
            cardResource.save(data);
            //data.$save()
        });
    };
});

  <button ng-click="updataCard()">更新id為3的銀行卡</button>
  <br/>

<span>{{card_3['name']}}</span>
  <span>{{card_3['amount']}}</span>

node:(這段nodejs同時處理了charge和save)

var url = require('url');
app.post('/card/user/123/:id',function(req,res){ var index = req.params.id-1; var query = url.parse(req.url,true)['query']; if (query.charge){ cards[index]['amount']+= Number(query['amount']) } else { cards[index] = req.body; } res.send(cards[index]); });

  

點選後→


cardResource.save(data) 和 data.$save()在這裡,兩者是等價的.但是在有引數的時候,他們接受的引數其實是不同的:

使用cardResource.save([parameters], postData, [success], [error])方法時,可以接受四個引數:

[parameters]: 可選.用於配置url引數,比如配置{userID:124},那麼請求url就會變成 'card/user/124/3',其中的3,還是從請求體的id屬性獲取的.

                    同樣,如果沒有引數需要配置,是不要填空的.不存在順序一一對應.可以直接把postData作為第一個引數.

postData: 必填. 傳送的請求體. save方法是post請求,必須要帶有請求體.

[success]:選填. 響應成功後的回撥函式,引數同get方法成功回撥裡的引數

[error]:選填. 響應失敗後的回撥函式.引數同get方法失敗回撥裡的引數

使用data.$save([parameters], [success], [error])方法時,可以接受三個引數:

[parameters]: 可選.注意它不是用於配置url的引數的.它是用來設定url?後面的引數的! 比如設定{name:'code_bunny'},那麼請求url就會變成'card/user/123/3?name=code_bunny',

                    通過$save方法來呼叫save方法,是不能夠配置url引數的.它直接就是提交資源自己.

                    同樣,如果沒有引數需要配置,是不要填空的.不存在順序一一對應.可以直接把[success]作為第一個引數.

[success]:選填. 響應成功後的回撥函式,引數同get方法成功回撥裡的引數

[error]:選填. 響應失敗後的回撥函式.引數同get方法失敗回撥裡的引數

至於為什麼要在then回撥裡處理,為什麼save後檢視會自動更新,請檢視:http://www.cnblogs.com/liulangmao/p/3907307.html 以及 http://www.cnblogs.com/liulangmao/p/3907032.html

 

5. 在控制器中使用cardResource服務的save方法來新建資源:

(1)新建帶有id的資源:

    //新增id為4的銀行卡
    $scope.addCard4 = function(){
        var card_4 = new cardResource();
        card_4['id'] = 4;
        card_4['name'] = '浦發銀行';
        card_4['amount'] = 0;
        card_4.$save(function(data){$scope.card_4=data});
//cardResource.save(card_4,function(data){$scope.card_4=data}); };
  <span>{{card_4['name']}}</span>
  <span>{{card_4['amount']}}</span>

node:(這段nodejs同時處理了charge和save)

app.post('/card/user/123/:id',function(req,res){
    var index = req.params.id-1;
    var query = url.parse(req.url,true)['query'];
    if (query.charge){
        cards[index]['amount']+= Number(query['amount'])
    }
    else {
        cards[index] = req.body;
    }
    res.send(cards[index]);
});

點選後→

新建資源需要通過new cardResource(), 這樣它就是$resource()的例項,就擁有了$save,$charge等資源的方法:

card_4.$save(function(data){$scope.card_4=data});

cardResource.save(card_4,function(data){$scope.card_4=data});

上面已經說過了,這兩種寫法自然是等價的.但這裡的回撥裡需要給$scope_card_4進行賦值,因為原來的$scope下是沒有card_4這個變數的.

(2)新建沒有id的資源:

    //新增沒有id的銀行卡
    $scope.addCard = function(){
        var newCard = new cardResource();
        newCard['name'] = '農業銀行';
        newCard['amount'] = 0;
        newCard.$save(function(data){$scope.card_5=data});
cardResource.save(newCard,function(data){$scope.card_5=data});
//newCard.$save(function(data){$scope.card_5=data});
    };
<button ng-click="addCard()">新增一張不指定id的銀行卡</button>
<br/>
<span>{{card_5['name']}}</span>
<span>{{card_5['amount']}}</span>

node:

app.post('/card/user/123',function(req,res){
    var index = cards.length;
    cards[index] = req.body;
    res.send(cards[index]);
});

點選後→


cardResource.save(newCard,function(data){$scope.card_5=data});
newCard.$save(function(data){$scope.card_5=data});
上面已經說過了,這兩種寫法自然是等價的.但這裡的回撥裡需要給$scope_card_5進行賦值,因為原來的$scope下是沒有card_5這個變數的.

 (1)和(2)的區別在於:

  資源是否有id,他們請求的路徑是不同的.沒有id的資源會請求'card/user/123',而有id的資源會請求'card/user/123/id',這在node裡的處理是不同的.

  在$resource中,基本url裡面的 :id 這類通過引數指定的值,如果沒有傳入引數,那麼它提交的路徑裡就不會包含這一項,比如:

  return $resource('/card/user/:userID/:id',{userID:123,id:'@id'})
  當使用query方法時,是沒有id值的,get請求的路徑就是/card/user/123

  但是在node裡面不是這樣的:

app.get('/card/user/123/:id',function(req,res){
//這個只能處理帶有id值的get請求,沒有id請求不能被匹配到處理
});
app.get('/card/user/123',function(req,res){
//這個用來處理沒有id的get請求
});
app.post('/card/user/123/:id',function(req,res){
//這個只能處理帶有id值的post請求,沒有id請求不能被匹配到處理
});
app.post('/card/user/123',function(req,res){
//這個用來處理沒有id的post請求
});

所以,處理有id的資源的post請求和處理沒有id的資源的post請求,在node裡需要寫不同的匹配規則,在這裡,對於沒有id的資源,我們僅作模擬,就把它按照順序儲存在陣列最後,然後當然還是把它返回給客戶端.

 

6. 在控制器中使用cardResource服務的自定義的charge方法來給資源進行充值操作:

    //id為1的建設銀行卡增加100元
    $scope.addCharge = function(){
        $scope.card_1.then(function(card){
            card.$charge({amount:100});
        })
    }
  <button ng-click="addCharge()">給建設銀行的卡充值100元</button>
  <br/>
  <span>{{card_1['name']}}</span>
  <span>{{card_1['amount']}}</span>

node:(這段nodejs同時處理了charge和save)

app.post('/card/user/123/:id',function(req,res){
    var index = req.params.id-1;
    var query = url.parse(req.url,true)['query'];
    if (query.charge){
        cards[index]['amount']+= Number(query['amount'])
    }
    else {
        cards[index] = req.body;
    }
    res.send(cards[index]);
});

這裡的charge充值方法需要帶有請求引數,?amount=100,所以它不能使用cardResource.charge([params],data,[success],[error])這種方式來請求,只能通過呼叫資源自身的$charge方法來進行請求.

card.$charge({amount:100})相當於請求了url: 'card/user/123/1?charge=true&amount=100'

即使是RESTful架構,不表示就不可以在url中帶有引數,帶有引數不影響express對路徑的匹配.獲取路徑引數的方法也就是使用url模組一樣獲取.
無論POST請求還是GET請求,都可以帶有引數,也都可以獲取引數.

這裡對'/card/user/123/:id'這個url的請求做了判斷,獲取它的引數,然後判斷引數裡的charge是否等於true,是的話表示充值操作.不是的話表示更新資源操作.分別對資源進行處理,最後都是把操作過的資源返回給客戶端.

其中,url.parse(req.url,true),如果不帶有true,不會把引數解析成json格式.

點選後→

 

7. 在控制器中使用cardResource服務的delete方法來刪除資源:

    //刪除id為1的銀行卡
    $scope.delCard1 = function(){
        $scope.card_1.then(function(card){
            card.$delete();
        })
    }
  <button ng-click="delCard1()">將id為1的銀行卡刪掉</button>
  <br/>
  <span>{{card_1['name']}}</span>
  <span>{{card_1['amount']}}</span>

node:

app.delete('/card/user/123/:id',function(req,res){
    var index = req.params.id-1;
    cards[index] = null;
    res.send({})
});

點選後→

這裡有兩點需要注意:

1.card.$delete()不等同於cardResource.delete(card),因為它這裡是'DELETE'方式的請求,不是POST方式的請求,當我們使用card.$delete()的時候,它是把card作為請求體傳送給後臺的(相當於POST請求體).但是當我們使用cardResource.delete(card)的時候,它是把card物件解析成url引數一起傳給後臺的(相當於GET請求裡url後面的引數).也就是說,card.$delete()請求的url是'card/user/123/1',附帶請求體為card. 而cardResource.delete(card)請求的url是'card/user/123/1?amount=0&name=建設銀行'.

還有很重要的一點,其實請求url是'card/user/123/1?amount=0&name=建設銀行'在這個例子中並不會改變後臺的處理和返回,但是對於angular來說,card.$delete()會使用返回值去填充card,更新檢視,但是cardResource.delete(card),它並非請求體就是card,所以返回值不會去填充card,不會更新檢視.所以,在使用delete方法時,應該直接使用$delete,而不是delete.(remove亦然)

2.刪除以後,cards[0]就變成空的null,但是不能返回null,因為$resource必須返回json格式,所以要返回{}

 

完整程式碼地址:https://github.com/OOP-Code-Bunny/angular/tree/master/OREILLY/18.6%20%24http(ngResource)

 

相關文章