11-REST和自定义服务

在这一步,我们将修改我们应用读取数据

  • 我们定义一个自定义服务代表一个RESTFULL客户端,使用客户端我们就能用更容易的方法去请求数据服务。不用低层次的$httpAPI,HTTP方法和URLs

工作空间重置介绍 重置你的工作区间到第十一步

git checkout -f step-11

刷新你的浏览器,或者在线上检出这一步:第十一步例子。 大多数重要的修改列在下面,你可以在 GitHub上看到全部不同。

依赖

RESTFULL功能由Angular的 ngResource模块提供,与Angular框架核心分离分布。 我们使用 Bower安装客户端依赖,这步更新 bower.json配置来包含新的依赖。

{
  "name": "angular-seed",
  "description": "A starter project for AngularJS",
  "version": "0.0.0",
  "homepage": "https://github.com/angular/angular-seed",
  "license": "MIT",
  "private": true,
  "dependencies": {
    "angular": "~1.3.0",
    "angular-mocks": "~1.3.0",
    "bootstrap": "~3.1.1",
    "angular-route": "~1.3.0",
    "angular-resource": "~1.3.0"
  }
}

新依赖 "angular-resource": "~1.3.0" 告诉bower安装 angular-resource组件兼容1.3.x的版本。我们必须寻问bower去下载并安装这个依赖,我们可以这样做:

npm install

警告:如果你最后运行npm install后,一个新的Angular版本发布,你用bower intall 可以有问题。你需要安装的Angular版本可能会有冲突。如果你遇到,在运行 npm install之前,你可以简单地删除你的app/bower_components 目录。 注意:如果你已经全局安装了bower,你可能运行 bower insatll,但是对于这个项目我们已经预配置npm install 给我们运行bower。

模板

我们自定义的资源服务将会定义在 app/js/service.js中,所有我们必须包含这个文件到布局模板,另外,我们也需要读取 angular-resource.js文件,他包含了 ngResource模块。 app/index.html.

...
  <script src="bower_components/angular-resource/angular-resource.js"></script>
  <script src="js/services.js"></script>
...

服务

我们创建了我们自己的服务提供对服务器上手机数据的访问。 app/js/services.js.

var phonecatServices = angular.module('phonecatServices', ['ngResource']);

phonecatServices.factory('Phone', ['$resource',
  function($resource){
    return $resource('phones/:phoneId.json', {}, {
      query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
    });
  }]);

通过工厂函数,我们使用了模块API去注册一个自定义的服务,我们通过了名字为Phone的服务和工厂函数,工厂函数类似于控制的构造器,他们都通过函数参数定义了要注入的依赖,Phone服务在 $resource服务上定义了依赖。 $resource服务只用几千代码就让创建 RESTFULL客户端变得容易,这个客户端可以用于我们的程序代替低层的 $http服务。 app/js/app.js.

...
angular.module('phonecatApp', ['ngRoute', 'phonecatControllers','phonecatFilters', 'phonecatServices']).
...

我们需要增加一个 phonecateServices模块依赖到 phonecatApp模块必须数组

控制器

我们通过分离出低层的$ http服务,替换名叫Phone的新服务,简化了我们的子控制器( PhoneListCtrlPhoneDetaialCtrl)。Angular的 $resource比 $http更容易交互暴露数据像RESTFULL客户端资源,现在也更容易懂得我们控制器中代码做了什么。 app/js/controllers.js.

var phonecatControllers = angular.module('phonecatControllers', []);

...

phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone', function($scope, Phone) {
  $scope.phones = Phone.query();
  $scope.orderProp = 'age';
}]);

phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone', function($scope, $routeParams, Phone) {
  $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
    $scope.mainImageUrl = phone.images[0];
  });

  $scope.setImage = function(imageUrl) {
    $scope.mainImageUrl = imageUrl;
  }
}]);

注意我们怎么替换PhoeListCtrl

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

通过

$scope.phones = Phone.query();

这是一个简单的语句,我们想查询所有的手机。 在上面的代码中,需要注意一个重要的事,当我们执行手机服务中的方法时,我们没有通过任何回调函数。虽然他很像结果同步返回。但那不是所有的情况。返回的future对象是什么?——一个对象,当XHR响应返回时就会填充。因为Angular的数据绑定,我们能使用future,并绑定到我们的模板,这样,当数据到达,视图将会被自动更新。 有些时候,单独依靠future对象和数据绑定不满足我们做我们所需的事,在这些案例中,我们可以增加一个回调处理服务器响应, PhoneDetaialCtrl通过在回调中设置一个 mainImageUrl阐明了这个。

测试

因为我们现在使用了 ngResource模块,所以必须更新我们的Karma配置,使用 angular-resource我们的测试才会通过。 test/karma.conf.js:

files : [
  'app/bower_components/angular/angular.js',
  'app/bower_components/angular-route/angular-route.js',
  'app/bower_components/angular-resource/angular-resource.js',
  'app/bower_components/angular-mocks/angular-mocks.js',
  'app/js/**/*.js',
  'test/unit/**/*.js'
],

我们修改了我们的测试验证我们新服务像期盼的一样发起了HTTP请求,并处理了他们。测试也检查了我们的控制器与服务是交互正确的。 $resource服务的参数响应对象,对象包含更新与删除资源的方法,如果我们使用标准的 toEqual匹配器,我们的测试将会失败,因为测试值将不会如期盼的一样响应。为了解决这个问题,我们使用新定义的 toEqualData Jasmine匹配器,当 toEqualData匹配两个对象,他只会比对帐号的属性,而忽略方法。 test/unit/controllersSpec.js:

describe('PhoneCat controllers', function() {

  beforeEach(function(){
    this.addMatchers({
      toEqualData: function(expected) {
        return angular.equals(this.actual, expected);
      }
    });
  });

  beforeEach(module('phonecatApp'));
  beforeEach(module('phonecatServices'));


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


    it('should create "phones" model with 2 phones fetched from xhr', function() {
      expect(scope.phones).toEqualData([]);
      $httpBackend.flush();

      expect(scope.phones).toEqualData(
          [{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
    });


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


  describe('PhoneDetailCtrl', function(){
    var scope, $httpBackend, ctrl,
        xyzPhoneData = function() {
          return {
            name: 'phone xyz',
            images: ['image/url1.png', 'image/url2.png']
          }
        };


    beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
      $httpBackend = _$httpBackend_;
      $httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData());

      $routeParams.phoneId = 'xyz';
      scope = $rootScope.$new();
      ctrl = $controller('PhoneDetailCtrl', {$scope: scope});
    }));


    it('should fetch phone detail', function() {
      expect(scope.phone).toEqualData({});
      $httpBackend.flush();

      expect(scope.phone).toEqualData(xyzPhoneData());
    });
  });
});

你会在Karma标签页下面的输出。

Chrome 22.0: Executed 5 of 5 SUCCESS (0.038 secs / 0.01 secs)

总结

现在我们已经学习怎样构建一个像RESTFULL客户端的自定义服务,在 第十二步我们准备学习怎样使用动画提升我们的应用。

你可能感兴趣的:(11-REST和自定义服务)