本文主要為你介紹了 AngularJS XHR 和依賴注入,這里整理了詳細(xì)資料和示例代碼
在硬編碼的數(shù)據(jù)集中有三款手機(jī)的數(shù)據(jù),建立一個(gè)應(yīng)用程序足夠了!讓我們使用Angular內(nèi)建的服務(wù)之一,$http從服務(wù)器上取得更大的數(shù)據(jù)集我們將使用Angular的依賴性注入(DI)來為PhoneListCtrl
控制器提供服務(wù)。
把工作空間重置到第五步
git checkout -f step-5
刷新你的瀏覽器或在線檢查這一步:Step 5 Live Demo
下面列出了第四步和第五步之間的最重要的區(qū)別。你可以在GitHub里看到完整的差異。
在你的項(xiàng)目中,app/phones/phones.json
文件是一個(gè)數(shù)據(jù)集,包含了一個(gè)更大的手機(jī)列表,以JSON格式存儲(chǔ)。
遵照以下文件示例:
[
{
"age": 13,
"id": "motorola-defy-with-motoblur",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
"snippet": "Are you ready for everything life throws your way?"
...
},
...
]
我們將在控制器中使用Angular的$http服務(wù)向你的Web服務(wù)器發(fā)出HTTP請(qǐng)求,取回app/phones/phones.json
文件中的數(shù)據(jù)。$http
是幾個(gè)用Web應(yīng)用中來處理常見的操作的內(nèi)建Angular服務(wù)之一。Angular在你需要的地方為你注入了這些服務(wù)。
Angular的DI子系統(tǒng)負(fù)責(zé)管理這些服務(wù)。依賴性注入有用助于你的web應(yīng)用既結(jié)構(gòu)完好(例如,分離表現(xiàn)層、數(shù)據(jù)和控制三者)以及松弛的耦合(不能由組件自身解決的組件之間的依賴性問題,由DI子系統(tǒng)解決)。
app/js/controllers.js:
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
});
$http
向你的Web服務(wù)器發(fā)出一個(gè)HTTP GET請(qǐng)求,要求phones/phones.json
(該url相對(duì)于我們的index.html
文件)。服務(wù)器在json文件中提供該數(shù)據(jù),以響應(yīng)該請(qǐng)求。(響應(yīng)可能是由后端服務(wù)器動(dòng)態(tài)生成的。但是在瀏覽器和我們的應(yīng)用看來,它們沒什么不同。為了簡單起見,我們?cè)诒窘坛讨惺褂昧艘粋€(gè)json文件。)
該$http
服務(wù)返回了一個(gè)promise對(duì)象?,帶有success
方法。我們調(diào)用這個(gè)方法以處理異步響應(yīng),并假定該作用域的手機(jī)數(shù)據(jù)由該控制器控制,作為一個(gè)模塊,稱為phones
。注意Angular偵測(cè)了該json響應(yīng),并為我們解析了它。
要想在Angular中使用一個(gè)服務(wù),你只要聲明你所需要的依賴性的名字,作為控制器的構(gòu)造函數(shù)的參數(shù),如下所示:
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...}
在構(gòu)造控制器時(shí),Angular的依賴性注入器會(huì)把這些服務(wù)注入到你的控制器中。這些依賴性控制器還負(fù)責(zé)創(chuàng)建該服務(wù)可能需要的任何傳遞依賴性(一個(gè)服務(wù)通常會(huì)依賴于其它服務(wù))。
注意,參數(shù)的名稱非常重要,因?yàn)樽⑷肫鲿?huì)用這些名稱去查閱依賴性。
$
前綴名稱約定你可以創(chuàng)建你自己的服務(wù),而且實(shí)際上我們將在第十一步 AngularJS REST和自定義服務(wù)做這個(gè)。作為一個(gè)命名約定,Angular的內(nèi)建服務(wù),作用域方法以及一些別的Angular API在命名前面使用一個(gè)$
前綴。
Angular提供的服務(wù)的命名空間有$
前綴。要想避免沖突,最好避免把你的服務(wù)和模塊命名成帶有$
前綴。
如果你檢查一個(gè)作用域,你可能還會(huì)注意到一些屬性以$$
開頭。這些屬性被視為是私有屬性,不能訪問或者修改。
因?yàn)锳ngular從參數(shù)的名稱調(diào)用控制器的依賴性到控制器構(gòu)造器的函數(shù),如果你打算為PhoneListCtrl
控制器縮小JavaScript代碼,所有的函數(shù)參數(shù)都會(huì)被壓縮,而且依賴性注入器將不能正確的識(shí)別服務(wù)。
我們可以克服這個(gè)問題,通過用依賴性的名稱注釋這個(gè)函數(shù),作為字符串提供,它不會(huì)被壓縮。提供這種注入注釋有兩種方法:
在控制器函數(shù)中創(chuàng)建一個(gè)$inject
屬性,它可攜帶一個(gè)字符串?dāng)?shù)組。在數(shù)組中的每個(gè)字符串都是要注入到對(duì)應(yīng)的參數(shù)上的服務(wù)的名稱。我們可以在自己的示例中這樣寫:
function PhoneListCtrl($scope, $http) {...}
PhoneListCtrl.$inject = ['$scope', '$http'];
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
在那里使用一個(gè)內(nèi)聯(lián)注釋,并非是只提供這個(gè)函數(shù),你還提供了一個(gè)數(shù)組。這個(gè)數(shù)組包含了一系列服務(wù)名稱,后跟著函數(shù)本身。
function PhoneListCtrl($scope, $http) {...}
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
兩種方法都能與Angular注入的任何函數(shù)完美協(xié)作,因此要選用哪種方法完全取決于你的項(xiàng)目的編程風(fēng)格。
如果使用第二種方法,在注冊(cè)控制器時(shí),通常以匿名函數(shù)的形式提供內(nèi)聯(lián)的構(gòu)造器函數(shù)。
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {...}]);
從此刻開始,我們將在本教程中使用內(nèi)聯(lián)方法。考慮到這一點(diǎn),讓我們把注釋加到PhoneListCtrl
上:
app/js/controllers.js:
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
test/unit/controllersSpec.js
:
因?yàn)槲覀冮_始使用依賴性注入,而且我們的控制器包含了依賴性,在我們的測(cè)試中構(gòu)造控制器就變得有點(diǎn)復(fù)雜了。我們可以使用new
操作符,并提供帶有某種假的$http
實(shí)現(xiàn)的構(gòu)造器。然而,Angular提供了一個(gè)模擬$http
服務(wù),我們可以用在單元測(cè)試中。我們通過調(diào)用一個(gè)稱為$httpBackend
服務(wù)上的方法,為服務(wù)器請(qǐng)求配置了“假的”響應(yīng)。
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
// 在每次測(cè)試之前載入我們的應(yīng)用模塊定義
beforeEach(module('phonecatApp'));
// 注入器會(huì)忽略前面和后面的下劃線(例如_$httpBackend_)。
// 這允許我們注入一個(gè)服務(wù),然后把它附加到同名變量上,以避免名稱沖突
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller('PhoneListCtrl', {$scope: scope});
}));
注意:因?yàn)槲覀冊(cè)跍y(cè)試環(huán)境中載入了Jasmine以及angular-mocks.js
,我們得到了兩個(gè)輔助方法module和inject,用來訪問和配置注入器。
我們?cè)跍y(cè)試環(huán)境中創(chuàng)建控制器,如下所示:
inject
輔助方法,向Jasmine的beforeEach
函數(shù)注入$rootScope、$controller和$httpBackend服務(wù)的實(shí)例,這些實(shí)例來自于一個(gè)注入器,在每一個(gè)測(cè)試內(nèi)部都會(huì)被重新創(chuàng)建這個(gè)注入器。這保證了每次測(cè)試都從一個(gè)眾所周知的起點(diǎn)開始,每次測(cè)試與其它測(cè)試相互獨(dú)立。$rootScope.$new()
來為我們的控制器創(chuàng)建一個(gè)新的作用域。$controller
函數(shù),以參數(shù)的形式傳入PhoneListCtrl
控制器的名稱和創(chuàng)建范圍。因?yàn)槲覀兊拇a現(xiàn)在使用$http
服務(wù)以取回我們的控制器中的手機(jī)列表數(shù)據(jù),在我們創(chuàng)建PhoneListCtrl
子作用域之前,我們需要告訴測(cè)試套件等待一個(gè)后面的請(qǐng)求,來自控制器。我們可以這樣做:
請(qǐng)求把$httpBackend
服務(wù)注入到我們的beforeEach
函數(shù)中。這是一個(gè)在產(chǎn)品環(huán)境中的服務(wù)的模擬版本,可以響應(yīng)各種XHR和JSONP請(qǐng)求。該服務(wù)的模擬版本允許你編寫測(cè)試,不需要處理原生的API和與它相關(guān)的全局狀態(tài)——本來這兩者都會(huì)使測(cè)試變成一個(gè)噩夢(mèng)。
$httpBackend.expectGET
方法規(guī)定$httpBackend
服務(wù)等待之后的HTTP請(qǐng)求,并告訴它如何響應(yīng)它。注意,直到我們調(diào)用$httpBackend.flush
方法,才會(huì)返回響應(yīng)。現(xiàn)在我們作了斷言以核實(shí)在響應(yīng)到達(dá)之前,作用域上不存在手機(jī)模塊:
it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(scope.phones).toBeUndefined();
$httpBackend.flush();
expect(scope.phones).toEqual([{name: 'Nexus S'},
{name: 'Motorola DROID'}]);
});
通過調(diào)用$httpBackend.flush()
,我們清空了瀏覽器中的請(qǐng)求隊(duì)列。這導(dǎo)致$http
服務(wù)返回的promise對(duì)象由規(guī)范的應(yīng)答來處理??梢栽?a rel="nofollow" rel="external nofollow" target="_blank" target="_blank">模擬$httpBackend文檔中了解為什么必須“清空HTTP請(qǐng)求”的完整解釋。
最后,我們核實(shí)已經(jīng)正確設(shè)置了orderProp
的默認(rèn)值。
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});
現(xiàn)在在Karma標(biāo)簽卡中,你應(yīng)該看到以下的輸出:
Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)
index.html
的底部,添加一個(gè)<pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>
綁定以查看以json格式顯示的手機(jī)列表。PhoneListCtrl
控制器中,通過限制手機(jī)的數(shù)量為列表的前五個(gè)來預(yù)處理http響應(yīng)。在$http
回調(diào)中使用以下的代碼:$scope.phones = data.splice(0, 5);
現(xiàn)在你已經(jīng)知道了使用Angular服務(wù)是多么容易(幸虧Angular的依賴性注入),前往第六步 模板連接和圖像,在那里你將添加一些手機(jī)的縮略圖以及一些鏈接。
更多建議: