本文主要為你介紹了 AngularJS XHR 和依賴注入,這里整理了詳細(xì)資料和示例代碼
在硬編碼的數(shù)據(jù)集中有三款手機(jī)的數(shù)據(jù),建立一個應(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里看到完整的差異。
在你的項目中,app/phones/phones.json
文件是一個數(shù)據(jù)集,包含了一個更大的手機(jī)列表,以JSON格式存儲。
遵照以下文件示例:
[
{
"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請求,取回app/phones/phones.json
文件中的數(shù)據(jù)。$http
是幾個用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ā)出一個HTTP GET請求,要求phones/phones.json
(該url相對于我們的index.html
文件)。服務(wù)器在json文件中提供該數(shù)據(jù),以響應(yīng)該請求。(響應(yīng)可能是由后端服務(wù)器動態(tài)生成的。但是在瀏覽器和我們的應(yīng)用看來,它們沒什么不同。為了簡單起見,我們在本教程中使用了一個json文件。)
該$http
服務(wù)返回了一個promise對象?,帶有success
方法。我們調(diào)用這個方法以處理異步響應(yīng),并假定該作用域的手機(jī)數(shù)據(jù)由該控制器控制,作為一個模塊,稱為phones
。注意Angular偵測了該json響應(yīng),并為我們解析了它。
要想在Angular中使用一個服務(wù),你只要聲明你所需要的依賴性的名字,作為控制器的構(gòu)造函數(shù)的參數(shù),如下所示:
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...}
在構(gòu)造控制器時,Angular的依賴性注入器會把這些服務(wù)注入到你的控制器中。這些依賴性控制器還負(fù)責(zé)創(chuàng)建該服務(wù)可能需要的任何傳遞依賴性(一個服務(wù)通常會依賴于其它服務(wù))。
注意,參數(shù)的名稱非常重要,因為注入器會用這些名稱去查閱依賴性。
$
前綴名稱約定你可以創(chuàng)建你自己的服務(wù),而且實際上我們將在第十一步 AngularJS REST和自定義服務(wù)做這個。作為一個命名約定,Angular的內(nèi)建服務(wù),作用域方法以及一些別的Angular API在命名前面使用一個$
前綴。
Angular提供的服務(wù)的命名空間有$
前綴。要想避免沖突,最好避免把你的服務(wù)和模塊命名成帶有$
前綴。
如果你檢查一個作用域,你可能還會注意到一些屬性以$$
開頭。這些屬性被視為是私有屬性,不能訪問或者修改。
因為Angular從參數(shù)的名稱調(diào)用控制器的依賴性到控制器構(gòu)造器的函數(shù),如果你打算為PhoneListCtrl
控制器縮小JavaScript代碼,所有的函數(shù)參數(shù)都會被壓縮,而且依賴性注入器將不能正確的識別服務(wù)。
我們可以克服這個問題,通過用依賴性的名稱注釋這個函數(shù),作為字符串提供,它不會被壓縮。提供這種注入注釋有兩種方法:
在控制器函數(shù)中創(chuàng)建一個$inject
屬性,它可攜帶一個字符串?dāng)?shù)組。在數(shù)組中的每個字符串都是要注入到對應(yīng)的參數(shù)上的服務(wù)的名稱。我們可以在自己的示例中這樣寫:
function PhoneListCtrl($scope, $http) {...}
PhoneListCtrl.$inject = ['$scope', '$http'];
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
在那里使用一個內(nèi)聯(lián)注釋,并非是只提供這個函數(shù),你還提供了一個數(shù)組。這個數(shù)組包含了一系列服務(wù)名稱,后跟著函數(shù)本身。
function PhoneListCtrl($scope, $http) {...}
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
兩種方法都能與Angular注入的任何函數(shù)完美協(xié)作,因此要選用哪種方法完全取決于你的項目的編程風(fēng)格。
如果使用第二種方法,在注冊控制器時,通常以匿名函數(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
:
因為我們開始使用依賴性注入,而且我們的控制器包含了依賴性,在我們的測試中構(gòu)造控制器就變得有點(diǎn)復(fù)雜了。我們可以使用new
操作符,并提供帶有某種假的$http
實現(xiàn)的構(gòu)造器。然而,Angular提供了一個模擬$http
服務(wù),我們可以用在單元測試中。我們通過調(diào)用一個稱為$httpBackend
服務(wù)上的方法,為服務(wù)器請求配置了“假的”響應(yīng)。
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
// 在每次測試之前載入我們的應(yīng)用模塊定義
beforeEach(module('phonecatApp'));
// 注入器會忽略前面和后面的下劃線(例如_$httpBackend_)。
// 這允許我們注入一個服務(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});
}));
注意:因為我們在測試環(huán)境中載入了Jasmine以及angular-mocks.js
,我們得到了兩個輔助方法module和inject,用來訪問和配置注入器。
我們在測試環(huán)境中創(chuàng)建控制器,如下所示:
inject
輔助方法,向Jasmine的beforeEach
函數(shù)注入$rootScope、$controller和$httpBackend服務(wù)的實例,這些實例來自于一個注入器,在每一個測試內(nèi)部都會被重新創(chuàng)建這個注入器。這保證了每次測試都從一個眾所周知的起點(diǎn)開始,每次測試與其它測試相互獨(dú)立。$rootScope.$new()
來為我們的控制器創(chuàng)建一個新的作用域。$controller
函數(shù),以參數(shù)的形式傳入PhoneListCtrl
控制器的名稱和創(chuàng)建范圍。因為我們的代碼現(xiàn)在使用$http
服務(wù)以取回我們的控制器中的手機(jī)列表數(shù)據(jù),在我們創(chuàng)建PhoneListCtrl
子作用域之前,我們需要告訴測試套件等待一個后面的請求,來自控制器。我們可以這樣做:
請求把$httpBackend
服務(wù)注入到我們的beforeEach
函數(shù)中。這是一個在產(chǎn)品環(huán)境中的服務(wù)的模擬版本,可以響應(yīng)各種XHR和JSONP請求。該服務(wù)的模擬版本允許你編寫測試,不需要處理原生的API和與它相關(guān)的全局狀態(tài)——本來這兩者都會使測試變成一個噩夢。
$httpBackend.expectGET
方法規(guī)定$httpBackend
服務(wù)等待之后的HTTP請求,并告訴它如何響應(yīng)它。注意,直到我們調(diào)用$httpBackend.flush
方法,才會返回響應(yīng)。現(xiàn)在我們作了斷言以核實在響應(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()
,我們清空了瀏覽器中的請求隊列。這導(dǎo)致$http
服務(wù)返回的promise對象由規(guī)范的應(yīng)答來處理??梢栽?a rel="nofollow" rel="external nofollow" target="_blank" target="_blank">模擬$httpBackend文檔中了解為什么必須“清空HTTP請求”的完整解釋。
最后,我們核實已經(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
的底部,添加一個<pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>
綁定以查看以json格式顯示的手機(jī)列表。PhoneListCtrl
控制器中,通過限制手機(jī)的數(shù)量為列表的前五個來預(yù)處理http響應(yīng)。在$http
回調(diào)中使用以下的代碼:$scope.phones = data.splice(0, 5);
現(xiàn)在你已經(jīng)知道了使用Angular服務(wù)是多么容易(幸虧Angular的依賴性注入),前往第六步 模板連接和圖像,在那里你將添加一些手機(jī)的縮略圖以及一些鏈接。
更多建議: