到現在為止,我們使用是硬式編碼三條手機記錄資料集。現在我們使用AngularJS一個內建服務$http來擷取一個更大的手機記錄資料集。我們將使用AngularJS的依賴注入(dependency injection (DI))功能來為PhoneListCtrl控制器提供這個AngularJS服務。
請重設工作目錄:
git checkout -f step-5
重新整理瀏覽器,你現在應該能看到一個20部手機的列表。
步驟4和步驟5之間最重要的不同在下面列出。你可以在GitHub裡看到完整的差別。
資料
你項目當中的app/phones/phones.json檔案是一個資料集,它以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?" ... },...]
控制器
我們在控制器中使用AngularJS服務$http向你的Web伺服器發起一個HTTP請求,以此從app/phones/phones.json檔案中擷取資料。$http僅僅是AngularJS眾多內建服務中之一,這些服務可以處理一些Web應用的通用操作。AngularJS能將這些服務注入到任何你需要它們的地方。
服務是通過AngularJS的依賴注入DI子系統來管理的。依賴注入服務可以使你的Web應用良好構建(比如分離表現層、資料和控制三者的組件)並且松耦合(一個組件自己不需要解決組件之間的依賴問題,它們都被DI子系統所處理)。
app/js/controllers.js
function PhoneListCtrl($scope, $http) { $http.get('phones/phones.json').success(function(data) { $scope.phones = data; }); $scope.orderProp = 'age';}
$http向Web伺服器發起一個HTTP GET請求,索取phone/phones.json(注意,url是相對於我們的index.html檔案的)。伺服器用json檔案中的資料作為響應。(這個響應或許是即時從後端伺服器動態產生的。但是對於瀏覽器來說,它們看起來都是一樣的。為了簡單起見,我們在教程裡面簡單地使用了一個json檔案。)
$http服務用success返回[對象應答][ng.$q]。當非同步響應到達時,用這個對象應答函數來處理伺服器響應的資料,並且把資料賦值給範圍的phones資料模型。注意到AngularJS會自動檢測到這個json應答,並且已經為我們解析出來了!
為了使用AngularJS的服務,你只需要在控制器的建構函式裡面作為參數聲明出所需服務的名字,就像這樣:
function PhoneListCtrl($scope, $http) {...}
當控制器構造的時候,AngularJS的依賴注入器會將這些服務注入到你的控制器中。當然,依賴注入器也會處理所需服務可能存在的任何傳遞性依賴(一個服務通常會依賴於其他的服務)。
注意到參數名字非常重要,因為注入器會用他們去尋找相應的依賴。
'$'首碼命名習慣
你可以建立自己的服務,實際上我們在步驟11就會學習到它。作為一個命名習慣,AngularJS內建服務,範圍方法,以及一些其他的AngularJS API都在名字前面使用一個‘$'首碼。不要使用‘$'首碼來命名你自己的服務和模型,否則可能會產生名字衝突。
關於JS壓縮
由於AngularJS是通過控制器建構函式的參數名字來推斷依賴服務名稱的。所以如果你要壓縮PhoneListCtrl控制器的JS代碼,它所有的參數也同時會被壓縮,這時候依賴注入系統就不能正確的識別出服務了。
為了克服壓縮引起的問題,只要在控制器函數裡面給$inject屬性賦值一個依賴服務識別符的數組,就像被注釋掉那段最後一行那樣:
PhoneListCtrl.$inject = ['$scope', '$http'];
另一種方法也可以用來指定依賴列表並且避免壓縮問題——使用Javascript數組方式構造控制器:把要注入的服務放到一個字串數組(代表依賴的名字)裡,數組最後一個元素是控制器的方法函數:
var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];
上面提到的兩種方法都能和AngularJS可注入的任何函數完美協作,要選哪一種方式完全取決於你們項目的編程風格,建議使用數組方式。
測試
test/unit/controllerSpec.js:
由於我們現在開始使用依賴注入,並且我們的控制器也含有了許多依賴服務,所以為我們的控制器構造測試就有一點小小的複雜了。我們需要使用new操作並且提供給構造器包括$http的一些偽實現。然而,我們推薦的方法(而且更加簡單噢)是在測試環境下建立一個控制器,使用的方法和AngularJS在產品代碼於下面的情境下做的一樣:
describe('PhoneCat controllers', function() { describe('PhoneListCtrl', function(){ var scope, ctrl, $httpBackend; 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}); }));
注意:因為我們在測試環境中載入了Jasmine和angular-mock.js,我們有了兩個輔助方法,module和inject,來協助我們獲得和配置注入器。
用如下方法,我們在測試環境中建立一個控制器:
我們使用inject方法將$rootScope,$controller和$httpBackend服務執行個體注入到Jasmine的beforeEach函數裡。這些執行個體都來自一個注入器,但是這個注入器在每一個測試內部都會被重新建立。這樣保證了每一個測試都從一個周知的起始點開始,並且每一個測試都和其他測試相互獨立。
調用$rootScope.$new()來為我們的控制器建立一個新的範圍。
PhoneListCtrl函數和剛建立的範圍作為參數,傳遞給已注入的$controller函數。
由於我們現在的代碼在建立PhoneListCtrl子範圍之前,於控制器中使用$http服務擷取了手機列表資料,我們需要告訴測試套件等待一個從控制器來的請求。我們可以這樣做:
將請求服務$httpBackend注入到我們的beforeEach函數中。這是這個服務的一個偽版本,這樣做在產品環境中有助於處理所有的XHR和JSONP請求。服務的偽版本允許你不用考慮原生API和全域狀態——隨便一個都能構成測試的噩夢——就可以寫測試。
使用$httpBackend.expectGET方法來告訴$httpBackend服務來等待一個HTTP請求,並且告訴它如何對其進行響應。注意到,當我們調用$httpBackend.flush方法之前,響應是不會被發出的。
現在,
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'}]);});
在瀏覽器裡,我們調用$httpBackend.flush()來清空(flush)請求隊列。這樣會使得$http服務返回的promise(什麼是promise請參見這裡)能夠被解釋成規範的應答。
我們設定一些斷言,來驗證行動數據模型已經在範圍裡了。
最終,我們驗證orderProp的預設值被正確設定:
it('should set the default value of orderProp model', function() { expect(scope.orderProp).toBe('age');});;
練習
在index.html末尾添加一個{{phones | json}}綁定,觀察json格式的手機列表。
在PhoneListCtrl控制器中,把HTTP應答預先處理一下,使得只顯示手機列表的前五個。在$http回呼函數裡面使用如下代碼:
$scope.phones = data.splice(0, 5);
總結
現在你應該感覺得到使用AngularJS的服務是多麼的容易(這都要歸功於AngularJS服務的依賴注入機制),轉到步驟6,你會為手機添加縮圖和連結。
謝謝大家對本站的支援,後續繼續更新相關文章!