在普通的應用中,我們通過GET方法來獲取查詢數據,使用POST方法來提前數據。在angularjs的世界里,我們使用了內部封裝$http來獲取我們的信息。它的大概的代碼如下:
`$http.get(url).then(function(response){...})`
每次請求都需要指定URL。有沒有一種方法,可以使我們負責phone-detail.component.js的團隊成員可以不必關心實際觸發的是哪個URL呢?
從另一個層面說,有沒有更簡潔的語法可以替代$http來完成數據請求呢?
答案當然是有的。
* * * * *
**擴展信息:**
具象狀態傳輸(英文:Representational State Transfer,簡稱REST)
> https://zh.wikipedia.org/wiki/REST
其實,我們早早的就接觸了rest ,應該在以前我們所接觸的應用中,我們就是這種架構。如果你和我們共同學習了ThinkPHP5入門實例教程的話,相信對CURD一定并不陌生。
C:創建數據 ,我們使用post的方法進行數據的創建.
U:更新數據,我們使用post的方法進行數據的創建.
R:讀取數據,我們使用get的方法來進行數據的讀取.
D:刪除數據,我們使用get的方法進行數據的刪除。
比如我們請求第二頁信息時,會生成如下的URL:xxx?p=2,如果我們以關鍵字yunzhi搜索的話,會生成如下的URL xxx?keyword=yunzhi。這些設計都是REST架構。
> 其實你并不需要完全的明白什么是REST架構,特別是當很多資料描述的特別抽象的時候。我們只需要知道每個URL都對應特定的內容就可以了。
在標準的REST架構中,CURD,應該是這樣:
C:創建數據 ,我們使用post的方法進行數據的創建.
U:更新數據,我們使用put的方法進行數據的創建.
R:讀取數據,我們使用get的方法來進行數據的讀取.
D:刪除數據,我們使用delete的方法進行數據的刪除。
但并不是所有的瀏覽器都支持put與delete,所以,在實際的使用過程中,我們只會出現get與post。但很明顯的是,如果我們使用標準的rest,那么,只需要判斷請求方法便可以獲知用戶當前需要的操作類型。
比如url為 http://example.com/resources/142,如果為get方法,則說明操作為獲取當前資源的內容;如果為post方法,則為新增資源;如果為put方法,則為更新資源;如果為delete方法,則為刪除資源。
維基百科中為我們做了如下總結:

我們發現,在相同的URL(資源)的條件下,由于請求方法的不同,服務器做出的響應也不同。
這樣做的好處是顯而易見的,在客戶端:我們通過資源地址與請求方法便可以直接判斷出當前代碼的需求;在服務端,我們可以根據請求地址與方法,判斷客戶需求后,做出正確的動作。
**擴展信息結束**
* * * * *
## 無處不在的面向對象
在面向對象的世界時,我們往往更希望這樣做。
新增數據:
1.新建一個空對象。
2.為這個新對象賦值。
3.執行這個對象的新增方法。
更新數據
1.獲取要更新的對象
2.更新這個對象的值。
3.執行這個對象的更新方法。
刪除數據:
1.獲取要刪除的對象。
2.執行這個對象的刪除方法。
## 統一接口方法
在angularjs中,ngResource的出現,可以使我們:使用xxx.put()來更新數據, xxx.delete()來刪除數據...。的確,如果可以這樣寫代碼的話,簡直太易讀了,不是嗎?
當然了,我們在這僅僅使用到了CRUD中的R的操作,這時候使用ngResource給我們帶來的好處并不明顯。雖然是這樣,使用ngResourcer后代碼量及維護的難度也遠遠小于$http。
我們再來看下維基百科給出的示例:

按上面的示例,結合我們現有的項目,我們抽象出如下結論:
1. 在獲取手機列表時,資源地址應該為http://127.0.0.1:8080/phones/ 請求的動作為get.
2. 在獲取某個手機信息時,資源地址應該為http://127.0.0.1:8080/phones/phoneid 請求的動作的get。
現在的文件地址:
1. 手機列表文件地址為:http://127.0.0.1:8080/phones/phones.json.
2. 某個手機信息的文件地址為: http://127.0.0.1:8080/phones/phoneid.json
目標:
1. 資源地址應該為http://127.0.0.1:8080/phones/ 請求的動作為get時,實際請求的地址為:http://127.0.0.1:8080/phones/phones.json
2. 資源地址應該為http://127.0.0.1:8080/phones/phoneid 請求的動作的get時,實際請求的地址為:http://127.0.0.1:8080/phones/phoneid.json
下面,我們使用ngResource來完成這個目標。
# 下載ngResource
`bower install angular-resource#1.5.7 --save
`
~~~
panjiedeMacBook-Pro:angularjs panjie$ bower install angular-resource#1.5.7 --save
bower cached https://github.com/angular/bower-angular-resource.git#1.5.7
bower validate 1.5.7 against https://github.com/angular/bower-angular-resource.git#1.5.7
~~~
--save,會在安裝angular-resource庫的同時,將安裝的信息寫入bower.json中。這樣做的好處時,在共享你引用的第三庫的時候,我們只需要供享bower.json就可以了,而不需要共享所有的第三方庫。
# 引入ngResource
`index.html`
~~~
+ <script src="bower_components/angular-resource/angular-resource.js"></script>
~~~
# 定制服務
前面我們接觸了組件,過濾器,本節中,我們將使用angularjs中另一個核心的功能--factory
* * * * *
**選學開始**
在angularjs中,如果我們想定義一個服務,那么可以使用provider, value, constant, service, factory。雖然它們的名字有區別,使用的時候也不完全全相同,但可以確認的一點是:他們都是provider!
那什么是provider呢?從字面意思上來看,是個『提供者』。我們可以理解為是個服務,一個我們可以調用的服務。其實我們雖然沒有建立過provider,但是卻早早的就使用過很多的provider了。
比如我們找到angular.js的第10624行,就可以見到$HttpProvider, 這個便是我們使用到的$http; 第18610行,還有我們前面使用的$SceProvider。不管是$http,還是$sce,它們都為我們**提供**了一系統的功能供我們使用,可能這就是provider這個名稱的由來吧。
**選學結束**
* * * * *
## 注入模塊
`core/core.module.js`
~~~
// 定義一個core模塊,供模塊內的組件使用。
angular.module('core', ['ngResource']);
~~~
## 新建factory
`core/phone/phone.server.js`
有人說,老師不應該是`phone.factory.js`嗎?怎么是`phone.server.js`呢?
嚴格意義上來說service是factory的一種,factory又是是provider的一種。只是輸出的格式不一樣而已。能用service實現的,必然能夠使用factory來實現,能用factory實現在,必須能夠使用provider來實現。
我們在這只所以用server做為后綴名,只是想讓使用的用戶更易懂。所以無論是angularjs的server,還是factory,或是provider,我們統一都使用server做為后綴。
> 這是我見過對provider講的最好的文章: http://hellobug.github.io/blog/angularjs-providers/
ngResource為我們封裝好了5個動作。

get:獲取某條記錄資源
save: 保存某條記錄資源
query: 獲取資源列表
remove:delete: 刪除某條資源
除此以外,我們還可以自己定義其它的動作類型。
`core/phone/phone.service.js`
~~~
angular.
module('core').
factory('Phone', ['$resource', function($resource) {
// 直接返回一個$resource, 并按資源示例格式給出資源地址。
return $resource('phones/:phoneId.json', {}, {
});
}]);
~~~
`:phoneId`將被替換為傳入的變量
## 添加依賴注入
`yun-zhi/yun-zhi.module.js`
~~~
// 定義一個yunZhi模塊,供組件使用。
// 注入ngRoute core模塊
angular.module('yunZhi', ['ngRoute', 'core']);
~~~
## 修改數據獲取方式
`yun-zhi/phone-detail.component.js`
~~~
angular.
module('yunZhi').
component('phoneDetail', {
templateUrl: 'yun-zhi/phone-detail.template.html',
controller: ['$routeParams', 'Phone',
function PhoneListController($routeParams, Phone) {
var self = this;
// 設置大圖
self.setImage = function setImage(imgUrl){
self.mainImageUrl = imgUrl;
};
// 打印到控制臺
console.dir(Phone);
// 使用$resource獲取手機信息
self.phone = Phone.get(
// 傳入參數
{phoneId:$routeParams.phoneId}
);
}
]
});
~~~
刪除了對原來的$http的依賴,增加了對'Phone'的依賴(Phone是一個存在于core模塊中的provider)。
## 引用JS文件,并測試
`index.html`
~~~
+ <script src="core/phone/phone.service.js"></script>
~~~
打開: http://127.0.0.1:8080/#!/phones/motorola-xoom

沒有,它的類型就是factory的返回值--Resource

它里面有delte get query remove save方法,還有一個我們此次用不到的bind方法。
我們再打開網絡選項卡:

發現正確的加載了對應的數據文件。
回顧目標:
+ 資源地址應該為http://127.0.0.1:8080/phones/ 請求的動作為get時,實際請求的地址為:http://127.0.0.1:8080/phones/phones.json ---- 完成
我們看get中有四個參數,我們說另外三個是作用是什么呢?后面兩個,我也不知道,但第2個,我可以告訴你。它是回調函數。
下面,我們利用這個回調函數,給左側的大圖進行初始化。
`yun-zhi/phone-detail.component.js`
~~~
self.phone = Phone.get(
// 傳入參數
- {phoneId:$routeParams.phoneId}
+ {phoneId:$routeParams.phoneId},
+ function(phone){
+ self.setImage(phone.images[0]);
+ }
);
~~~
測試:

## 修改數據獲取方式
`yun-zhi/phone-list.component.js`
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: ['Phone',
function PhoneListController(Phone) {
console.dir(Phone);
// 獲取列表數據
this.phones = Phone.query();
// 初始化排序字段
this.orderProp = 'age';
}
]
});
~~~
我們刪除了原有的$http, 引入了core模塊中的Phone。將嘗試使用query()獲取整個數據列表;
測試:

是的,正如我們所見,在這種配置下:`return $resource('phones/:phoneId.json',`,使用query()方法時,會自動的觸發phones.json文件。
結束:resource為了封裝的query(),會自動的忽略到變量的信息,然后拼接成一個新的資源地址。
回顧目標:
+ 資源地址應該為http://127.0.0.1:8080/phones/phoneid 請求的動作的get時,實際請求的地址為:http://127.0.0.1:8080/phones/phoneid.json
為了實現這個目標,我們自定義一個動作 `list`
`core/phone/phone.service.js`
~~~
angular.
module('core').
factory('Phone', ['$resource', function($resource) {
return $resource('phones/:phoneId.json', {}, {
list:{
method:'GET',
params:{phoneId: 'phones'},
isArray:true
}
});
}]);
~~~
使用這個動作:
`yun-zhi/phone-list.component.js`
~~~
- this.phones = Phone.query();
+ this.phones = Phone.list();
~~~
測試:

在學習的過程中,我們要嘗試去學習官方文檔,雖然在前期我們的確會很費勁,但如果有一天你突然的知道官方文檔的知識怎么使用的時候。你會感覺到事半功倍。如果我們一直讀不懂官方文檔,但只能說明,我們距離熟悉這門語言還遠的很。
> 官方地址:https://docs.angularjs.org/api/ngResource/service/$resource
我們可以創建一個新的動作,那么當然也可以去重寫query()動作。
`core/phone/phone.service.js`
~~~
- list:{ // 動作名
+ query:{ // 動作名
~~~
`yun-zhi/phone-list.component.js`
~~~
- this.phones = Phone.list();
+ this.phones = Phone.query();
~~~
# 重構代碼
重構的目的:
1. 代碼結構更清晰,更容易被人理解。(永遠不要寫只有電腦和你能看懂的代碼)
2. 分解成更細小的輪子,也便在其它的應用中重復使用造過的輪子。
現在我們的phone.service在core這個模塊中。如果我們可以把它拆下來,而且還不影響core,是不是一件非常美好的事情呢?
## 新建core.phone模塊
`core/phone/phone.module.js`
~~~
// 新建core.phone模塊,命名方法使我們能辨認出它是core的子模塊
angular.module('core.phone', ['ngResource']);
~~~
## 修改`core/core.module.js`
~~~
// 定義一個core模塊,供模塊內的組件使用。
// 依賴 ngResource core.phone
angular.module('core', ['ngResource', 'core.phone']);
~~~
## 修改`core/phone/phone.service.js`
~~~
- module('core').
+ module('core.phone').
~~~
修改后,這個factory就已經屬于core.phone模塊了.
## 引入js文件
`index.html`
~~~
<script src="core/phone/phone.module.js"></script>
~~~
在引入JS文件時,我們需要注意引用的順序。
由于`phone.service.js`的factory屬于core.phone,所以在引入`phone.service.js`之前,需要先引入`core/phone/phone.module.js`,否則將會出下如下的錯誤:

提示說,沒有找到`core.phone`這個模塊。如果出現這個錯誤,必然是有組件、過濾器、provider....被定義在這個模塊上了。在定義到這個模塊上以前,我們必須保證已經定義了這個模塊。JS代碼是順序執行了,也就是說,必須提前引用`core/phone/phone.module.js`
> 由于依賴注入屬于惰性加載(不使用不加載),所以即使我們在模塊的定義中,依賴了其它模塊,也不并提前對其它模塊進行引入。
`index.html`
~~~
<!DOCTYPE html>
<html lang="en" ng-app="phonecatApp">
<head>
<head>
<meta charset="UTF-8">
<title>hello</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="app.moudle.js"></script>
<script src="app.config.js"></script>
<script src="yun-zhi/yun-zhi.module.js"></script>
<script src="yun-zhi/phone-list.component.js"></script>
<script src="yun-zhi/hello-yunzhi.component.js"></script>
<script src="yun-zhi/phone-detail.component.js"></script>
<script src="core/core.module.js"></script>
<script src="core/checkmark/checkmark.filter.js"></script>
<script src="core/phone/phone.module.js"></script>
<script src="core/phone/phone.service.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>
~~~
- 前言
- 第一章:準備知識
- 第一節:GIT
- 第二節:Node.js
- 第三節:http-server
- 第四節:bower
- 第五節:firefox+chrome
- 第二章:官方示例教程
- 第零節:Hello Yunzhier
- 第一節:靜態模板
- 第二節:MVC
- 回調函數
- 第三節:組件
- 第四節:重構組件
- 2.4.1 調用組件
- 2.4.2 規劃目錄結構
- 2.4.3 剝離V層
- 2.4.4 大話測試
- 第五節:循環過濾器
- 第六節:雙向數據綁定
- 第七節:XHR與依賴注入
- 第八節:添加縮略圖
- 第九節:模擬頁面跳轉
- 2.9.1 使用bower
- 2.9.2 使用grunt
- 第十節:完善手機詳情頁
- 第十一節:自定義過濾器
- 第十二節:行為處理
- 第十三節:封裝請求
- 第十四節:應用動畫
- 第十五節:總結
- 第三章:菜譜管理示例
- 第四章:總結