鍍金池/ 教程/ HTML/ XHR和依賴性注入
必要準(zhǔn)備工作
靜態(tài)模板
Angular模板
快速入門
XHR和依賴性注入
更多模板
雙路數(shù)據(jù)綁定
模板連接和圖像
篩選器
REST和自定義服務(wù)
路由與多視圖
篩選迭代器
應(yīng)用動(dòng)畫
事件處理函數(shù)
完結(jié)篇

XHR和依賴性注入

在硬編碼的數(shù)據(jù)集中有三款手機(jī)的數(shù)據(jù),建立一個(gè)應(yīng)用程序足夠了!讓我們使用Angular內(nèi)建的服務(wù)之一,$http從服務(wù)器上取得更大的數(shù)據(jù)集我們將使用Angular的依賴性注入(DI)來為PhoneListCtrl控制器提供服務(wù)。

  • 現(xiàn)在有一個(gè)20個(gè)電話的列表,從服務(wù)器載入。

把工作空間重置到第五步

git checkout -f step-5

刷新你的瀏覽器或在線檢查這一步:Step 5 Live Demo

下面列出了第四步和第五步之間的最重要的區(qū)別。你可以在GitHub里看到完整的差異。

數(shù)據(jù)

在你的項(xiàng)目中,app/phones/phones.json文件是一個(gè)數(shù)據(jù)集,包含了一個(gè)更大的手機(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是幾個(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請求,要求phones/phones.json(該url相對于我們的index.html文件)。服務(wù)器在json文件中提供該數(shù)據(jù),以響應(yīng)該請求。(響應(yīng)可能是由后端服務(wù)器動(dòng)態(tài)生成的。但是在瀏覽器和我們的應(yīng)用看來,它們沒什么不同。為了簡單起見,我們在本教程中使用了一個(gè)json文件。)

$http服務(wù)返回了一個(gè)promise對象?,帶有success方法。我們調(diào)用這個(gè)方法以處理異步響應(yīng),并假定該作用域的手機(jī)數(shù)據(jù)由該控制器控制,作為一個(gè)模塊,稱為phones。注意Angular偵測了該json響應(yīng),并為我們解析了它。

要想在Angular中使用一個(gè)服務(wù),你只要聲明你所需要的依賴性的名字,作為控制器的構(gòu)造函數(shù)的參數(shù),如下所示:

phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...}

在構(gòu)造控制器時(shí),Angular的依賴性注入器會把這些服務(wù)注入到你的控制器中。這些依賴性控制器還負(fù)責(zé)創(chuàng)建該服務(wù)可能需要的任何傳遞依賴性(一個(gè)服務(wù)通常會依賴于其它服務(wù))。

注意,參數(shù)的名稱非常重要,因?yàn)樽⑷肫鲿眠@些名稱去查閱依賴性。

$前綴名稱約定

你可以創(chuàng)建你自己的服務(wù),而且實(shí)際上我們將在第十一步做這個(gè)。作為一個(gè)命名約定,Angular的內(nèi)建服務(wù),作用域方法以及一些別的Angular API在命名前面使用一個(gè)$前綴。

Angular提供的服務(wù)的命名空間有$前綴。要想避免沖突,最好避免把你的服務(wù)和模塊命名成帶有$前綴。

如果你檢查一個(gè)作用域,你可能還會注意到一些屬性以$$開頭。這些屬性被視為是私有屬性,不能訪問或者修改。

在極簡化上的一個(gè)注記

因?yàn)锳ngular從參數(shù)的名稱調(diào)用控制器的依賴性到控制器構(gòu)造器的函數(shù),如果你打算為PhoneListCtrl控制器縮小JavaScript代碼,所有的函數(shù)參數(shù)都會被壓縮,而且依賴性注入器將不能正確的識別服務(wù)。

我們可以克服這個(gè)問題,通過用依賴性的名稱注釋這個(gè)函數(shù),作為字符串提供,它不會被壓縮。提供這種注入注釋有兩種方法:

  • 在控制器函數(shù)中創(chuàng)建一個(gè)$inject屬性,它可攜帶一個(gè)字符串?dāng)?shù)組。在數(shù)組中的每個(gè)字符串都是要注入到對應(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)格。

如果使用第二種方法,在注冊控制器時(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)槲覀冮_始使用依賴性注入,而且我們的控制器包含了依賴性,在我們的測試中構(gòu)造控制器就變得有點(diǎn)復(fù)雜了。我們可以使用new操作符,并提供帶有某種假的$http實(shí)現(xiàn)的構(gòu)造器。然而,Angular提供了一個(gè)模擬$http服務(wù),我們可以用在單元測試中。我們通過調(diào)用一個(gè)稱為$httpBackend服務(wù)上的方法,為服務(wù)器請求配置了“假的”響應(yīng)。

describe('PhoneCat controllers', function() {

  describe('PhoneListCtrl', function(){
    var scope, ctrl, $httpBackend;

    // 在每次測試之前載入我們的應(yīng)用模塊定義
    beforeEach(module('phonecatApp'));

    // 注入器會忽略前面和后面的下劃線(例如_$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)槲覀冊跍y試環(huán)境中載入了Jasmine以及angular-mocks.js,我們得到了兩個(gè)輔助方法moduleinject,用來訪問和配置注入器。

我們在測試環(huán)境中創(chuàng)建控制器,如下所示:

  • 我們使用inject輔助方法,向Jasmine的beforeEach函數(shù)注入$rootScope$controller$httpBackend服務(wù)的實(shí)例,這些實(shí)例來自于一個(gè)注入器,在每一個(gè)測試內(nèi)部都會被重新創(chuàng)建這個(gè)注入器。這保證了每次測試都從一個(gè)眾所周知的起點(diǎn)開始,每次測試與其它測試相互獨(dú)立。
  • 通過調(diào)用$rootScope.$new()來為我們的控制器創(chuàng)建一個(gè)新的作用域。
  • 調(diào)用了已注入的$controller函數(shù),以參數(shù)的形式傳入PhoneListCtrl控制器的名稱和創(chuàng)建范圍。

因?yàn)槲覀兊拇a現(xiàn)在使用$http服務(wù)以取回我們的控制器中的手機(jī)列表數(shù)據(jù),在我們創(chuàng)建PhoneListCtrl子作用域之前,我們需要告訴測試套件等待一個(gè)后面的請求,來自控制器。我們可以這樣做:

  • 請求把$httpBackend服務(wù)注入到我們的beforeEach函數(shù)中。這是一個(gè)在產(chǎn)品環(huán)境中的服務(wù)的模擬版本,可以響應(yīng)各種XHR和JSONP請求。該服務(wù)的模擬版本允許你編寫測試,不需要處理原生的API和與它相關(guān)的全局狀態(tài)——本來這兩者都會使測試變成一個(gè)噩夢。

  • 使用$httpBackend.expectGET方法規(guī)定$httpBackend服務(wù)等待之后的HTTP請求,并告訴它如何響應(yīng)它。注意,直到我們調(diào)用$httpBackend.flush方法,才會返回響應(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(),我們清空了瀏覽器中的請求隊(duì)列。這導(dǎo)致$http服務(wù)返回的promise對象由規(guī)范的應(yīng)答來處理??梢栽?a rel="nofollow" >模擬$httpBackend文檔中了解為什么必須“清空HTTP請求”的完整解釋。

  • 我們制作了斷言,核實(shí)作用域上已經(jīng)有手機(jī)模塊了。

最后,我們核實(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)

實(shí)驗(yàn)

  • 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);

總結(jié)

現(xiàn)在你已經(jīng)知道了使用Angular服務(wù)是多么容易(幸虧Angular的依賴性注入),前往第六步 模板連接和圖像,在那里你將添加一些手機(jī)的縮略圖以及一些鏈接。

上一篇:路由與多視圖下一篇:Angular模板