在这一步,我们将修改我们应用读取数据
工作空间重置介绍 重置你的工作区间到第十一步
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的新服务,简化了我们的子控制器( PhoneListCtrl
和 PhoneDetaialCtrl
)。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客户端的自定义服务,在 第十二步我们准备学习怎样使用动画提升我们的应用。