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)