其實每次看到這些代碼的時候,我就迫不及待的想知道:什么時候才能把它們改成動態獲取的。
~~~
this.phones = [{
name: 'Nexus S',
snippet: 'Fast just got faster with Nexus S.',
age: 1
}, {
name: 'Motorola XOOM? with Wi-Fi',
snippet: 'The Next, Next Generation tablet.',
age: 2
}, {
name: 'MOTOROLA XOOM?',
snippet: 'The Next, Next Generation tablet.',
age: 3
}];
~~~
很簡單的道理,我們的應用上線后,總不能靠改源代碼來新增或是刪除手機信息吧。本節中,我們將使用一個叫做`$http`的服務,來實現數據的動態獲取。
#XHR
我們先看一下什么是XHR:
XMLHttpRequest是一個瀏覽器接口,使得Javascript可以進行HTTP(S)通信。 最早,微軟在IE5引進了這個接口。 因為它太有用,其他瀏覽器也模仿部署了,ajax操作因此得以誕生。
如果以前我們接觸過jquery,相信你對XHR已經不再陌生。
沒有XHR以前,我們的瀏覽器向服務器發送數據的話,只能重新觸發一個新的URL,比如我們在form標簽中寫入action的地址,然后在點擊submit按鈕時,就會將form中的數據提交到action規定好的地址上。
有了XHR,我們可以做到在不刷新頁面的前提下,使用JS在后面偷偷的向服務器傳送數據了。當然了,更重要的,這使我們的瀏覽器真正的在不刷新頁面的前提下動了起來。
html5中,XHR已經發展到XMLHttpRequest Level 2,我們可以閱讀這篇文章來對XHR有個更深入的認識: http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html
認識了XHR后,我們共同學習下`angularjs`為我們封裝好的XHR ---- `$http`。
# $http
前面,我們說$http可以在后臺請求數據。那么這句話應該是基于以下前提的:服務器上需要存在數據。
## 數據準備
在這,我們已經準備好了本節需要的數據:
你需要打開:http://guide.mengyunzhi.com/angular-phonecat/app/phones/ ,然后找到里面的`phones.json`,將其下載(點擊打開,全選,復制,再粘貼到自己的文件中)后,保存至當前程序的根目錄下的`phones`文件夾中。
最終,你的目錄結構應該是這樣的:
~~~
├── app.moudle.js
├── bower_components
│?? ├── angular
│?? ├── bootstrap
│?? └── jquery
├── index.html
├── phones
│?? └── phones.json
├── test.html
└── yun-zhi
├── hello-yunzhi.component.js
├── hello-yunzhi.template.html
├── phone-list.component.js
├── phone-list.template.html
└── yun-zhi.module.js
~~~
TODO:當然了,你還可以使用以下的GIT命令來查看我們為你準備好的代碼,并將其直接復制到文件夾中。
## 請求數據
請求數據以前,我們需要簡單的講一下什么是依賴注入(DI Dependency Injection)。在學習ThinkPHP的時候,我們學過命名空間。
比如以下Hello類
~~~
<?php
namespace app\yunzhi;
class Hello {
}
~~~
我們如果在自己的類中去實例化Hello,需要先use,然后才可以直接NEW。
~~~
<?php
....
use app\yunzhi\Hello;
....
public function index()
{
$Hello = new Hello();
}
~~~
否則。ThinkPHP會告訴我們,找不到這個`Hello`。
這樣做的好處是什么呢?
我們雖然規定了很多類,但是ThinkPHP只有在使用到他們的時候,才將它們加載到內存,從而提升了運行的效率。當然了,有了命名空間后,我們再也不用擔心類名重復的問題了。
angularjs使用一種叫做依賴注入的方法來解決這個問題,其實我們可以簡單的理解為是use在angularjs中的表現形式而已。
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
console.dir($http);
this.orderProp = 'age';
}
});
~~~
沒錯,`$http`做為`controller`的參數傳入后,我們就可以在方法體中使用這個`$http`了。
我們在控制臺中,看看它的廬山真面目。

沒錯,正如你看到的,它是一個`function`,有人說老師它怎么是個`function`呢,為什么不是個`object`呢?這是由于在`javascript`中,對象與我們以前學習的有所區別。以前的其它的面向對象的語言中,我們一般先去建立一個`class`,然后實例化這個`class`后,得到一個對象。但javascript沒有`class`這個東西。并不是說它不夠強大,因為`function`已經完全可以替代`class`。總之,我們需要記住的是。如果我們看到的是一個object,那么這個object,并不是基于哪個類創建的。如果我們看到的返回值是一個function,那么你可以暫時認為它是一個對象。
當然了,你也可以使用下面的代碼,再簡單的測試一下,它可以有助于你的理解。
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
// 實例化一個對象。
var a = new Object();
console.log(a);
// 實例化一個對象的另一種方法
var b = {};
console.log(b);
// 定義一個函數(這個才是以前我們理解的類)
var man = function(name){
var self = {}; // 定義一個對象,用來給它添加屬性
self.name = name, // 屬性1
self.sex = 1, // 屬性2
self.sayHello = function(){ // 方法1
alert('hello' + self.name);
}
self.helloYunzhi = function(){
console.log('hello yunzhi');
}
return self;
};
// 實例化前面我們定義好的函數man
var zhangsan = new man('zhangsan');
console.dir(zhangsan);
// 調用方法
zhangsan.sayHello();
zhangsan.helloYunzhi();
console.dir($http);
this.orderProp = 'age';
}
});
~~~
測試:

有點暈是吧,沒關系。反正知道它是一個function就可以了。這個function中,有屬性還有小function。我們可以調用它的屬性,也可以調用它的小function。
## 調用$http
前面,我們發現`$http`中有個屬性是`get`,這個屬性的類型是`function`,這個`function`接收兩個參數,分別為`url`和`config`。
簡單猜一下,應該可以知道:用get的方式向url請求數據,config是本次請求數據的配置信息。
> 官方地址:https://docs.angularjs.org/api/ng/service/$http , https://docs.angularjs.org/api/ng/service/$http#get

原來config是個選填項,那我們暫時不寫。 返回值是個HttpPromise的家伙。
1
`$http.get(url);`
2
~~~
var url = 'phones/phones.json';
var HttpPromise = $http.get(url);
console.dir(HttpPromise);
~~~

原來返回的類型是Promise,里面有兩個方法供我們使用,一個是success,另一個是error。
> 其實Promise是為了解決同步鏈式操作的一個特定的類型,我們以后會深入的學習它。
下面,我們分別使用這個兩個方法,來定義數據正確與錯誤傳輸。
~~~
var HttpPromise = $http.get(url);
HttpPromise.success(function(response){});
HttpPromise.error(function(response){});
~~~
~~~
var HttpPromise = $http.get(url);
HttpPromise.success(function(response){
console.log(response);
});
HttpPromise.error(function(response){
// 返回錯誤碼,或是超時觸發。
});
~~~

沒錯,成功請求后,數據被傳入success方法的第一個參數中。
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
var url = 'phones/phones.json';
var HttpPromise = $http.get(url);
HttpPromise.success(function(response){
console.log(response);
});
HttpPromise.error(function(response){
// 返回錯誤碼,或是超時時觸發。
});
this.orderProp = 'age';
}
});
~~~
### then()
實際使用中,我們還可以使用Promise的then方法。這個方位位于Promise的父類中,用JS的說法,是位于其原型鏈上。

下面,我們使用then方法進行改寫:
`$http.get(url).then(function(){}, function(){});`
我們給它傳兩個參數,第一個參數:請求成功后執行的function。第二個參數:請求失敗后執行的function。
~~~
$http.get(url).then(
function(response){
},
function(response){
}
);
~~~
發現了吧,其實上面兩種寫法只是斷行不行,代碼沒有任何的區別。所以以后當我們再看到這種寫法的時候,需要心里很清楚:function(){}作為一個對數傳進去了,這個function將會被執行。
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
var url = 'phones/phones.json';
$http.get(url).then(
function(response){
console.log(response);
},
function(response){
// 失敗的時候被執行,在此省略
}
);
this.orderProp = 'age';
}
});
~~~

此時,我們發現,和直接使用success方法不同,then方法的response中,不但返回了數據,還返回狀態碼等信息。當然了,此時如果我們想獲取數據,那么需要使用`reponse.data`,以后,我們將更多的使用then方法。
## 賦值
數據取回來了,下一步,我們將其傳給這個controller。
我們把then()中失敗執行的代碼刪除掉后:
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
var url = 'phones/phones.json';
$http.get(url).then(
function(response){
this.phones = response.data;
}
);
this.orderProp = 'age';
}
});
~~~
測試:

發現竟然沒有生成任何數據。
這是由于this的作用域不同導致的。
我們再簡單測試一下:
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
this.name = 'father';
var url = 'phones/phones.json';
$http.get(url).then(
function(response){
this.phones = response.data;
this.name = 'son';
console.log('son:' + this.name);
}
);
console.log('father: ' + this.name);
this.orderProp = 'age';
}
});
~~~
可能我們期待的結果是:
son: son
father: son
但實際上卻是這樣:

發現我們認為的子函數中,沒有將name覆蓋為son,而且執行順序也和我們想的完全不一致。至于執行順序,這是由于js異步處理的原因。在這里我更想說的是,在$http.get()中的this.name,并不是我們的子函數,也是function參數中的一部分,并不是我們心里想的this。
鑒于此,我們進行如下改寫:
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
var self = this;
var url = 'phones/phones.json';
$http.get(url).then(
function(response){
self.phones = response.data;
}
);
this.orderProp = 'age';
}
});
~~~
測試:

沒錯,這才是我們想要的。
## 為壓縮代碼做準備
我們在進行開發時,往往會看到兩個js版本,一個是.js,另一個是.min.js。前面的看著很方便,但代碼體積大。后面的看著不方便,但代碼體積小。一個是人能看的懂的,另一個是機器能看的懂的。在正式的生產環境下,項目上線前我們會對js代碼進行壓縮。
那么都是什么被壓縮了呢?
1、刪除空格
2、刪除注釋
3、刪除換行
4、將變量名變短
比如:
~~~
var yunzhi = function(){
var self = {};
self.name = 'yunzhidreamer';
return self;
};
var zhangsan = new Yunzhi();
name = zhangsan.name;
~~~
經過四步壓縮后,會變成這個樣子。
~~~
var yunzhi=function(){var a={};return a.name="yunzhidreamer",a},zhangsan=new Yunzhi;name=zhangsan.name;
//# sourceMappingURL=test.min.js.map
~~~
那如果參數中包含$http呢? 我們共同來測試一下:
~~~
var yunzhi = function($http){
var self = {};
self.http = $http;
self.name = 'yunzhidreamer';
return self;
};
var http = new Object();
var zhangsan = new Yunzhi();
name = zhangsan.name;
~~~
壓縮后:
~~~
var yunzhi=function(a){var b={};return b.http=a,b.name="yunzhidreamer",b},http=new Object,zhangsan=new Yunzhi;name=zhangsan.name;
//# sourceMappingURL=test.min.js.map
~~~
沒錯,`$http`會做為一個普通的參數被壓縮。但是angularjs卻只認識$http,比如:
我們的`phone-list.component.js`壓縮前:
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: function PhoneListController($http) {
var self = this;
var url = 'phones/phones.json';
$http.get(url).then(
function(response){
self.phones = response.data;
}
);
this.orderProp = 'age';
}
});
~~~
壓縮后:
~~~
angular.module("yunZhi").component("phoneList",{templateUrl:"yun-zhi/phone-list.template.html",controller:function(a){var b=this,c="phones/phones.json";a.get(c).then(function(a){b.phones=a.data}),this.orderProp="age"}});
//# sourceMappingURL=test.min.js.map
~~~
此時,我們使用壓縮后的代碼,就會報一個這樣錯誤的信息:

是的,由于$http變成了a,angularjs又不認識這個a,所以報錯了。
但我們發現,像angular.module這個字眼,卻沒有被壓縮。這是由于壓縮工具很聰明的知道類還類的屬性,以及做為字符串傳入的值,是不能隨便壓縮的。基于此,我們對現有的代碼進行改寫,讓壓縮對我們不造成影響。
我們使用定義controller的第二種寫法 `controller: []`
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: ['$http',
function PhoneListController($http) {
var self = this;
self.orderProp = 'age';
$http.get('phones/phones.json').then(function(response) {
self.phones = response.data;
});
}
]
});
~~~
上面的寫法等同于下面這樣:
~~~
angular.
module('yunZhi').
component('phoneList', {
templateUrl: 'yun-zhi/phone-list.template.html',
controller: ['$http',
function PhoneListController(param1) {
var self = this;
self.orderProp = 'age';
param1.get('phones/phones.json').then(function(response) {
self.phones = response.data;
});
}
]
});
~~~
是的,在實際的執行中,param1會被做為$http來執行。所以即使壓縮后的代碼是這個樣子:`controller:["$http",function(a){`也不會影響到程序的正確執行了。
前臺這門學問,真的是越學感覺它越偉大,angularjs當然也是一樣。
- 前言
- 第一章:準備知識
- 第一節: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
- 第十節:完善手機詳情頁
- 第十一節:自定義過濾器
- 第十二節:行為處理
- 第十三節:封裝請求
- 第十四節:應用動畫
- 第十五節:總結
- 第三章:菜譜管理示例
- 第四章:總結