AngularJS XHR和依赖性注入 AngularJS 双路数据绑定 AngularJS 模板链接和图像 XHR和依赖性注入 本文主要为你介绍了 AngularJS XHR 和依赖注入,这里整理了详细资料和示例代码 在硬编码的数据集中有三款手机的数据,建立一个应用程序足够了!让我们使用Angular内建的服务之一,$http从服务器上取得更大的数据集我们将使用Angular的依赖性注入(DI)来为PhoneListCtrl控制器提供服务。 - 现在有一个20个电话的列表,从服务器载入。 把工作空间重置到第五步 git checkout -f step-5 刷新你的浏览器或在线检查这一步:Step 5 Live Demo 下面列出了第四步和第五步之间的最重要的区别。你可以在GitHub里看到完整的差异。 数据 在你的项目中,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服务向你的Web服务器发出HTTP请求,取回app/phones/phones.json文件中的数据。$http是几个用Web应用中来处理常见的操作的内建Angular服务之一。Angular在你需要的地方为你注入了这些服务。 Angular的DI子系统负责管理这些服务。依赖性注入有用助于你的web应用既结构完好(例如,分离表现层、数据和控制三者)以及松弛的耦合(不能由组件自身解决的组件之间的依赖性问题,由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向你的Web服务器发出一个HTTP GET请求,要求phones/phones.json(该url相对于我们的index.html文件)。服务器在json文件中提供该数据,以响应该请求。(响应可能是由后端服务器动态生成的。但是在浏览器和我们的应用看来,它们没什么不同。为了简单起见,我们在本教程中使用了一个json文件。) 该$http服务返回了一个promise对象?,带有success方法。我们调用这个方法以处理异步响应,并假定该作用域的手机数据由该控制器控制,作为一个模块,称为phones。注意Angular侦测了该json响应,并为我们解析了它。 要想在Angular中使用一个服务,你只要声明你所需要的依赖性的名字,作为控制器的构造函数的参数,如下所示: phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...} 在构造控制器时,Angular的依赖性注入器会把这些服务注入到你的控制器中。这些依赖性控制器还负责创建该服务可能需要的任何传递依赖性(一个服务通常会依赖于其它服务)。 注意,参数的名称非常重要,因为注入器会用这些名称去查阅依赖性。 $前缀名称约定 你可以创建你自己的服务,而且实际上我们将在第十一步 AngularJS REST和自定义服务做这个。作为一个命名约定,Angular的内建服务,作用域方法以及一些别的Angular API在命名前面使用一个$前缀。 Angular提供的服务的命名空间有$前缀。要想避免冲突,最好避免把你的服务和模块命名成带有$前缀。 如果你检查一个作用域,你可能还会注意到一些属性以$$开头。这些属性被视为是私有属性,不能访问或者修改。 在极简化上的一个注记 因为Angular从参数的名称调用控制器的依赖性到控制器构造器的函数,如果你打算为PhoneListCtrl控制器缩小JavaScript代码,所有的函数参数都会被压缩,而且依赖性注入器将不能正确的识别服务。 我们可以克服这个问题,通过用依赖性的名称注释这个函数,作为字符串提供,它不会被压缩。提供这种注入注释有两种方法: 在控制器函数中创建一个$inject属性,它可携带一个字符串数组。在数组中的每个字符串都是要注入到对应的参数上的服务的名称。我们可以在自己的示例中这样写: function PhoneListCtrl($scope, $http) {...} PhoneListCtrl.$inject = ['$scope', '$http']; phonecatApp.controller('PhoneListCtrl', PhoneListCtrl); 在那里使用一个内联注释,并非是只提供这个函数,你还提供了一个数组。这个数组包含了一系列服务名称,后跟着函数本身。 function PhoneListCtrl($scope, $http) {...} phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]); 两种方法都能与Angular注入的任何函数完美协作,因此要选用哪种方法完全取决于你的项目的编程风格。 如果使用第二种方法,在注册控制器时,通常以匿名函数的形式提供内联的构造器函数。 phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {...}]); 从此刻开始,我们将在本教程中使用内联方法。考虑到这一点,让我们把注释加到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: 因为我们开始使用依赖性注入,而且我们的控制器包含了依赖性,在我们的测试中构造控制器就变得有点复杂了。我们可以使用new操作符,并提供带有某种假的$http实现的构造器。然而,Angular提供了一个模拟$http服务,我们可以用在单元测试中。我们通过调用一个称为$httpBackend服务上的方法,为服务器请求配置了“假的”响应。 describe('PhoneCat controllers', function() { describe('PhoneListCtrl', function(){ var scope, ctrl, $httpBackend; // 在每次测试之前载入我们的应用模块定义 beforeEach(module('phonecatApp')); // 注入器会忽略前面和后面的下划线(例如_$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}); })); 注意:因为我们在测试环境中载入了Jasmine以及angular-mocks.js,我们得到了两个辅助方法module和inject,用来访问和配置注入器。 我们在测试环境中创建控制器,如下所示: 我们使用inject辅助方法,向Jasmine的beforeEach函数注入$rootScope、$controller和$httpBackend服务的实例,这些实例来自于一个注入器,在每一个测试内部都会被重新创建这个注入器。这保证了每次测试都从一个众所周知的起点开始,每次测试与其它测试相互独立。 通过调用$rootScope.$new()来为我们的控制器创建一个新的作用域。 调用了已注入的$controller函数,以参数的形式传入PhoneListCtrl控制器的名称和创建范围。 因为我们的代码现在使用$http服务以取回我们的控制器中的手机列表数据,在我们创建PhoneListCtrl子作用域之前,我们需要告诉测试套件等待一个后面的请求,来自控制器。我们可以这样做: 请求把$httpBackend服务注入到我们的beforeEach函数中。这是一个在产品环境中的服务的模拟版本,可以响应各种XHR和JSONP请求。该服务的模拟版本允许你编写测试,不需要处理原生的API和与它相关的全局状态——本来这两者都会使测试变成一个噩梦。 使用$httpBackend.expectGET方法规定$httpBackend服务等待之后的HTTP请求,并告诉它如何响应它。注意,直到我们调用$httpBackend.flush方法,才会返回响应。 现在我们作了断言以核实在响应到达之前,作用域上不存在手机模块: 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服务返回的promise对象由规范的应答来处理。可以在模拟$httpBackend文档中了解为什么必须“清空HTTP请求”的完整解释。 我们制作了断言,核实作用域上已经有手机模块了。 最后,我们核实已经正确设置了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响应。在$http回调中使用以下的代码: $scope.phones = data.splice(0, 5); 总结 现在你已经知道了使用Angular服务是多么容易(幸亏Angular的依赖性注入),前往第六步 模板连接和图像,在那里你将添加一些手机的缩略图以及一些链接。 AngularJS 双路数据绑定 AngularJS 模板链接和图像