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

本文讲解AngularJS中如何使用XHR服务从远程服务器获取数据,介绍依赖注入的概念和使用,包括控制器、$http服务、依赖注入的命名约定以及在最小化JS时的注意事项。还探讨了测试和模拟数据的方法。
摘要由CSDN通过智能技术生成

之前几章中,我们使用的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'}]);
});

最后我们再看看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的依赖注入)。下一章我们将添加为手机添加一些缩略图以及链接。

博客原文http://www.tbwood.cn/articles/2016/03/11/1457675255432.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值