美文网首页一起学AngularJS
【一起学AngularJS】第七章、XHRs和依赖注入

【一起学AngularJS】第七章、XHRs和依赖注入

作者: 40ab6525bf35 | 来源:发表于2016-03-11 13:47 被阅读87次

    之前几章中,我们使用的3个手机数据集都是硬编码的。下面让我们使用Angular自带的一个叫$httpservice来从远程服务器上获取一个较大的数据集。我们将使用Angular的依赖注入(DI)PhoneListCtrl控制器注入$http服务。
    下面我们把代码切换到step-5:

    git checkout -f step-5
    

    刷新浏览器查看效果。也可以点这里在线看效果

    数据

    项目文件中的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?"
      ...
     },
    ...
    ]
    

    控制器

    我们将使用Angular的$http服务(在控制器中)来发起一个HTTP请求,从而从我们的网站应用服务器获取app/phones/phones.json文件。 Angular自带了一些服务用于完成一些通用的功能,$http是其中之一。在你需要的时候,Angualr会为你注入它们。
    服务通过Angualr的DI 子系统来管理。依赖注入功能不仅可以帮你组织好网站应用架构(比如,把控件、表示层、数据和控制分开),还可以让应用变得松耦合(组件之间的依赖不会由组件自身来控制,而是由DI系统统一管理)。
    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发起了一个HTTP GET请求,从我们服务器上获取phones/phones.json文件(这个目录是相对于index.html文件的)。服务器将返回文件里的json内容。(其实服务器完全可以动态的产生数据,比如从数据库读取。对于浏览器来说,都一样,这个例子中我们为了教程简单易懂,所以用了一个json文件来做演示。)
    $http服务通过success方法返回一个约定的数据对象。我们通过调用这个函数,来异步的处理HTTP返回并且把手机数据加入控制器域中的phones数据模型。注意这个过程是由Angular在后台自动完成处理和解析的。
    在Angular中,使用一个服务很简单,只需把服务的名字作为入参传给控制器的构造器函数。

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

    通过上述代码,Angular在构造控制器时同时会为你的控制器提供以下这些服务($scope, $http)。Angular还会自动把你想注入的服务所依赖的服务递归的引用进来。
    注意参数的名字很重要,因为注入器将根据它们来寻找对应的服务。

    $前缀命名公约

    你可以创建自己定制的服务,我们再第十三章中也会教大家去做。Angular有一个服务命名公约,对于Angular自带的服务,我们一般使用 $作为前缀。当然你也可以自己创建以$开头的服务,为了防止冲突,我们希望大家最好不要这么做。
    另外,在有些Scope中,你会发现有些属性变量是以$$开头的,这代表这是一个私有属性,应该避免访问和修改它。

    最小化JS时的注意事项

    由于Angular是根据控制器构造函数的入参来加载控制器所依赖的对象的,所以你一旦对JS代码进行了最小化(注解:应该就是所谓的JS压缩),函数的参数也会被最小化(注解:JS压缩一般会把长变量使用简单字母来替换,从而缩小JS代码),这样一来,Angular就无法正确加载对应的依赖了。
    我们可以通过用依赖的名字字符串来注解函数(因为字符串是不会被压缩的),从而解决这个问题。我们有两种方法来实现注解注入:

    1. 在控制器定义函数上方创建一个$inject字符串数组属性,用来存放依赖服务的名字字符串。本例中代码如下:
    function PhoneListCtrl($scope, $http) {...}
    PhoneListCtrl.$inject = ['$scope', '$http'];
    phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
    
    1. 使用内联注解:为控制器构造函数传入一个数组,而不是一个简单的函数。这个数组包含了服务的名称以及放在最后的函数本身,如下:
    function PhoneListCtrl($scope, $http) {...}
    phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
    

    上述两种方案对于任何能被Angular注入的函数都适用,所以使用那种方法完全取决于你项目的风格。
    当大家使用第二种方案的时候,我们一般把函数的定义直接放在数组最后一个元素内。如下:

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

    对应本例中的代码:
    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
    因为我们开始使用依赖注入并且控制器也有了自己的依赖,所以在测试中构造控制器也变得复杂起来了。我们将使用new操作符来提供一个包含$http伪实现的构造器。然而,Angular已经提供了一个$httpmock服务,我们可以直接在单元测试中使用。我们可以通过调用$httpBackend服务中的函数来设置请求返回,从而达到mock数据的功能。代码如下:

    describe('PhoneCat controllers', function() {
    
    describe('PhoneListCtrl', function(){
      var scope, ctrl, $httpBackend;
    
      // Load our app module definition before each test.
      beforeEach(module('phonecatApp'));
    
      // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
      // This allows us to inject a service but then attach it to a variable
      // with the same name as the service in order to avoid a name conflict.
      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-mocks.js, 我们可以直接使用moduleinject两个帮助函数来访问和配置注入器。

    我们在测试中是这样创建控制器的:

    • 我们使用inject这个帮助函数来注入$rootScopecontroller$httpBackend这三个服务实例到Jasmine的beforeEach函数中。这些实例在每个单独的测试中都会重新创建。这保证了每个测试的初始条件是一致的,并且互相之间是独立开的。
    • 我们为我们的控制器新创建了一个,即 $rootScope.$new()
    • 我们通过控制器的名字PhoneListCtrl来调用被注入测试的$controller函数,并且以创建好的`域作为参数。

    因为我们的控制器代码使用了$http服务来获取手机信息列表数据,在我们创建控制器PhoneListCtrl域之前,我们需要告诉对应的测试模块接受来自控制器的请求:

    • 通过beforeEach函数中注入的$httpBackend服务,我们可以向它发送请求。httpBackend服务所模拟的是真实生产环境中的用来处理所有XHR(异步请求)和JSONP请求的服务。httpBackend服务可以让你轻松的编写请求测试,而不用调用一些本地API和负责的全局上下文参数——这两个东西都会让编写测试变成噩梦。
    • 我们使用$httpBackend.expectGET来“训练”$httpBackend服务,告诉它当它收到一个HTTP请求的时候,应该返回什么。注意,你必须调用$httpBackend.flush来完成HTTP应答。

    在该测试用例中,收到HTTP回复之前,我们断言scope域中不包含phones数据模型:

    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()来清空浏览器中的请求队列。这将促使$http服务返回对应的回复。点这里可以了解为什么这句代码很重要
    • 我们现在可以断言,scope域中已经包含手机数据模型了。

    最后我们再看看orderProp的默认值是否设置正确了:

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

    你将可以在Karma的终端输出中看到以下信息:

    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格式的手机信息列表。
    PhoneListCtrl控制器中,预处理HTTP回复,使得手机列表中只含有5条记录。把下面的代码加到$http回调函数中:

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

    总结

    现在你已经了解了使用Angular的服务是多么的简单(谢谢Angular的依赖注入)。下一章我们将添加为手机添加一些缩略图以及链接。

    相关文章

      网友评论

        本文标题:【一起学AngularJS】第七章、XHRs和依赖注入

        本文链接:https://www.haomeiwen.com/subject/wzatlttx.html