AngularJS XHR 和依賴注入

2018-09-28 17:32 更新

XHR 和依賴注入

到現(xiàn)在為止,我們使用是硬編碼的三條手機(jī)記錄數(shù)據(jù)集?,F(xiàn)在我們使用AngularJS一個(gè)內(nèi)置服務(wù)$http來(lái)獲取一個(gè)更大的手機(jī)記錄數(shù)據(jù)集。我們將使用AngularJS的依賴注入(dependency injection (DI))功能來(lái)為PhoneListCtrl控制器提供這個(gè)AngularJS服務(wù)。

請(qǐng)重置工作目錄:

    git checkout -f step-5

刷新瀏覽器,你現(xiàn)在應(yīng)該能看到一個(gè)20部手機(jī)的列表。

步驟4和步驟5之間最重要的不同在下面列出。你可以在GitHub里看到完整的差別。

數(shù)據(jù)

你項(xiàng)目當(dāng)中的app/phones/phones.json文件是一個(gè)數(shù)據(jù)集,它以JSON格式存儲(chǔ)了一張更大的手機(jī)列表。

下面是這個(gè)文件的一個(gè)樣例:

    [
     {
      "age": 13,
      "id": "motorola-defy-with-motoblur",
      "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
      "snippet": "Are you ready for everything life throws your way?"
      ...
     },
    ...
    ]

控制器

我們?cè)诳刂破髦惺褂肁ngularJS服務(wù)$http向你的Web服務(wù)器發(fā)起一個(gè)HTTP請(qǐng)求,以此從app/phones/phones.json文件中獲取數(shù)據(jù)。$http僅僅是AngularJS眾多內(nèi)建服務(wù)中之一,這些服務(wù)可以處理一些Web應(yīng)用的通用操作。AngularJS能將這些服務(wù)注入到任何你需要它們的地方。

服務(wù)是通過(guò)AngularJS的依賴注入DI子系統(tǒng)來(lái)管理的。依賴注入服務(wù)可以使你的Web應(yīng)用良好構(gòu)建(比如分離表現(xiàn)層、數(shù)據(jù)和控制三者的部件)并且松耦合(一個(gè)部件自己不需要解決部件之間的依賴問(wèn)題,它們都被DI子系統(tǒng)所處理)。

app/js/controllers.js

    function PhoneListCtrl($scope, $http) {
      $http.get('phones/phones.json').success(function(data) {
        $scope.phones = data;
      });

      $scope.orderProp = 'age';
    }

    //PhoneListCtrl.$inject = ['$scope', '$http'];

$http向Web服務(wù)器發(fā)起一個(gè)HTTP GET請(qǐng)求,索取phone/phones.json(注意,url是相對(duì)于我們的index.html文件的)。服務(wù)器用json文件中的數(shù)據(jù)作為響應(yīng)。(這個(gè)響應(yīng)或許是實(shí)時(shí)從后端服務(wù)器動(dòng)態(tài)產(chǎn)生的。但是對(duì)于瀏覽器來(lái)說(shuō),它們看起來(lái)都是一樣的。為了簡(jiǎn)單起見(jiàn),我們?cè)诮坛汤锩婧?jiǎn)單地使用了一個(gè)json文件。)

$http服務(wù)用success返回[對(duì)象應(yīng)答][ng.$q]。當(dāng)異步響應(yīng)到達(dá)時(shí),用這個(gè)對(duì)象應(yīng)答函數(shù)來(lái)處理服務(wù)器響應(yīng)的數(shù)據(jù),并且把數(shù)據(jù)賦值給作用域的phones數(shù)據(jù)模型。注意到AngularJS會(huì)自動(dòng)檢測(cè)到這個(gè)json應(yīng)答,并且已經(jīng)為我們解析出來(lái)了!

為了使用AngularJS的服務(wù),你只需要在控制器的構(gòu)造函數(shù)里面作為參數(shù)聲明出所需服務(wù)的名字,就像這樣:

function PhoneListCtrl($scope, $http) {...}

當(dāng)控制器構(gòu)造的時(shí)候,AngularJS的依賴注入器會(huì)將這些服務(wù)注入到你的控制器中。當(dāng)然,依賴注入器也會(huì)處理所需服務(wù)可能存在的任何傳遞性依賴(一個(gè)服務(wù)通常會(huì)依賴于其他的服務(wù))。

注意到參數(shù)名字非常重要,因?yàn)樽⑷肫鲿?huì)用他們?nèi)ふ蚁鄳?yīng)的依賴。

Image of xhrs-&-dependency-injection-1.png

'$'前綴命名習(xí)慣

你可以創(chuàng)建自己的服務(wù),實(shí)際上我們?cè)?a href="rets-and-custom-services.html">步驟11就會(huì)學(xué)習(xí)到它。作為一個(gè)命名習(xí)慣,AngularJS內(nèi)建服務(wù),作用域方法,以及一些其他的AngularJS API都在名字前面使用一個(gè)‘$’前綴。不要使用‘$’前綴來(lái)命名你自己的服務(wù)和模型,否則可能會(huì)產(chǎn)生名字沖突。

關(guān)于 JS 壓縮

由于AngularJS是通過(guò)控制器構(gòu)造函數(shù)的參數(shù)名字來(lái)推斷依賴服務(wù)名稱的。所以如果你要壓縮PhoneListCtrl控制器的JS代碼,它所有的參數(shù)也同時(shí)會(huì)被壓縮,這時(shí)候依賴注入系統(tǒng)就不能正確的識(shí)別出服務(wù)了。

為了克服壓縮引起的問(wèn)題,只要在控制器函數(shù)里面給$inject屬性賦值一個(gè)依賴服務(wù)標(biāo)識(shí)符的數(shù)組,就像被注釋掉那段最后一行那樣:

    PhoneListCtrl.$inject = ['$scope', '$http'];

另一種方法也可以用來(lái)指定依賴列表并且避免壓縮問(wèn)題——使用Javascript數(shù)組方式構(gòu)造控制器:把要注入的服務(wù)放到一個(gè)字符串?dāng)?shù)組(代表依賴的名字)里,數(shù)組最后一個(gè)元素是控制器的方法函數(shù):

    var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];

上面提到的兩種方法都能和AngularJS可注入的任何函數(shù)完美協(xié)作,要選哪一種方式完全取決于你們項(xiàng)目的編程風(fēng)格,建議使用數(shù)組方式。

測(cè)試

test/unit/controllerSpec.js:

由于我們現(xiàn)在開(kāi)始使用依賴注入,并且我們的控制器也含有了許多依賴服務(wù),所以為我們的控制器構(gòu)造測(cè)試就有一點(diǎn)小小的復(fù)雜了。我們需要使用new操作并且提供給構(gòu)造器包括$http的一些偽實(shí)現(xiàn)。然而,我們推薦的方法(而且更加簡(jiǎn)單噢)是在測(cè)試環(huán)境下創(chuàng)建一個(gè)控制器,使用的方法和AngularJS在產(chǎn)品代碼于下面的場(chǎng)景下做的一樣:

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

注意:因?yàn)槲覀冊(cè)跍y(cè)試環(huán)境中加載了Jasmineangular-mock.js,我們有了兩個(gè)輔助方法,moduleinject,來(lái)幫助我們獲得和配置注入器。

用如下方法,我們?cè)跍y(cè)試環(huán)境中創(chuàng)建一個(gè)控制器:

  • 我們使用inject方法將$rootScope,$controller$httpBackend服務(wù)實(shí)例注入到Jasmine的beforeEach函數(shù)里。這些實(shí)例都來(lái)自一個(gè)注入器,但是這個(gè)注入器在每一個(gè)測(cè)試內(nèi)部都會(huì)被重新創(chuàng)建。這樣保證了每一個(gè)測(cè)試都從一個(gè)周知的起始點(diǎn)開(kāi)始,并且每一個(gè)測(cè)試都和其他測(cè)試相互獨(dú)立。

  • 調(diào)用$rootScope.$new()來(lái)為我們的控制器創(chuàng)建一個(gè)新的作用域。

  • PhoneListCtrl函數(shù)和剛創(chuàng)建的作用域作為參數(shù),傳遞給已注入的$controller函數(shù)。

由于我們現(xiàn)在的代碼在創(chuàng)建PhoneListCtrl子作用域之前,于控制器中使用$http服務(wù)獲取了手機(jī)列表數(shù)據(jù),我們需要告訴測(cè)試套件等待一個(gè)從控制器來(lái)的請(qǐng)求。我們可以這樣做:

  • 將請(qǐng)求服務(wù)$httpBackend注入到我們的beforeEach函數(shù)中。這是這個(gè)服務(wù)的一個(gè)偽版本,這樣做在產(chǎn)品環(huán)境中有助于處理所有的XHR和JSONP請(qǐng)求。服務(wù)的偽版本允許你不用考慮原生API和全局狀態(tài)——隨便一個(gè)都能構(gòu)成測(cè)試的噩夢(mèng)——就可以寫測(cè)試。

  • 使用$httpBackend.expectGET方法來(lái)告訴$httpBackend服務(wù)來(lái)等待一個(gè)HTTP請(qǐng)求,并且告訴它如何對(duì)其進(jìn)行響應(yīng)。注意到,當(dāng)我們調(diào)用$httpBackend.flush方法之前,響應(yīng)是不會(huì)被發(fā)出的。

現(xiàn)在,

    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()來(lái)清空(flush)請(qǐng)求隊(duì)列。這樣會(huì)使得$http服務(wù)返回的promise什么是promise請(qǐng)參見(jiàn)這里能夠被解釋成規(guī)范的應(yīng)答。

  • 我們?cè)O(shè)置一些斷言,來(lái)驗(yàn)證手機(jī)數(shù)據(jù)模型已經(jīng)在作用域里了。

最終,我們驗(yàn)證orderProp的默認(rèn)值被正確設(shè)置:

    it('should set the default value of orderProp model', function() {
      expect(scope.orderProp).toBe('age');
    });
    ;

執(zhí)行`./scripts/test.sh`腳本來(lái)運(yùn)行測(cè)試,你應(yīng)該會(huì)看到如下輸出:

    Chrome: Runner reset.
    ..
    Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms)
      Chrome 19.0.1084.36 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)

練習(xí)

index.html末尾添加一個(gè){{phones | json}}綁定,觀察json格式的手機(jī)列表。

PhoneListCtrl控制器中,把HTTP應(yīng)答預(yù)處理一下,使得只顯示手機(jī)列表的前五個(gè)。在$http回調(diào)函數(shù)里面使用如下代碼:

    $scope.phones = data.splice(0, 5);

總結(jié)

現(xiàn)在你應(yīng)該感覺(jué)得到使用AngularJS的服務(wù)是多么的容易(這都要?dú)w功于AngularJS服務(wù)的依賴注入機(jī)制),轉(zhuǎn)到連接與圖片模版,你會(huì)為手機(jī)添加縮略圖和鏈接。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)