我们已经习惯用AngularJS来创建前后端分离的应用,同时为了便于开发也会通过在前端代码库中使用mock数据来模拟服务端的接口。使用mock数据具有以下诸多的好处:
* 如果你并不是接口api的开发者,使用mock数据你就不用等待接口api设计完成就可以开始本地开发
* 如果你自己开发接口的api,使用mock数据不仅可以让你更专注于前端开发,还不比纠结于后端具体的细节实现
* 你可以快速的开发内容而不用管后台逻辑
* 建立mock服务还可以帮助你设计接口api的结构
* mock数据服务还可以给其他angular组件在对应服务上做单元测试用
本文我们介绍的是ngMockE2E模块提供的$httpBackend服务。需要注意的是它跟ngMock模块提供的同名服务是不同的哦~这两个$httpBackend服务连api都极为相似,但是ngMock版的$httpBackend主要用来做单元测试,因此它额外增加了很多测试用例和调用的功能在里面。我们今天要介绍的这个ngMockE2E则是针对angular应用开发中后端数据mock功能。
$httpBackend使用ngResource($resource)的resources给我们提供了一个mock终端。它可以应用到所有的http方法并返回特殊的句柄--例如:GET,POST,DELETE等等。它有效的取代了$http在Angular中的服务栈,因此它具有穿越的特性,无服务的请求可以直接穿越到服务端。它使得我们可以在静态服务器上创建包涵了所有服务的完美应用。
接下来我们先不急于开发一个完整的Angular应用,先从怎样开发简单的后段CRUD功能入手。这里我们首先开发如下功能:
* List - Resource.query()
* Create - Resource.save() or resource.$save()
* Get - Resource.get()
* Update - Resource.update() or resource.$update()
* Delete/remove - Resource.remove or resource.$remove()
例子中用到的所有代码都可以从这里点我啊得到。
多余的事情不说,我们现在就开始。从Angular的模版开始,先声明app module并加入项目需要依赖的模块。
var app = angular.module('app', [
'ngResource',
'ngMockE2E'
]);
然后在定义一个提供Angular组件和后端mock数据的接口服务的Contract resources。
app.factory('Contact', ['$resource', function($resource) {
return $resource(
'/contacts/:id',
{id: '@id'},
{
'update': {
method: 'PUT'
}
}
);
}]);
完成Contact resources的定义我们就可以开始定义mock backend了。$httpBackend其实是通过一个run模块来定义的,在我们的例子中我们把它添加到app的run模块中,然后定义一组contacts。
app.run(['$httpBackend', function($httpBackend) {
contacts = [
{
id: 1,
name: 'Ada Lovelace',
phone: '8445551815'
},
{
id: 2,
name: 'Grace Hopper',
phone: '8445551906'
},
{
id: 3,
name: 'Charles Babbage',
phone: '8445556433'
}
];
// $httpBackend interactions are defined here...
}]);
这个contacts数组是mock backend的所有操作都可以访问的,就像一个数据仓库,这是创建mock数据仓库的一种方式。另一种方式是创建一个以resources的id为下标的关联数组。另外为了简单和条理清晰我们还要定义一些数组查找和操作的基础方法,不想这么麻烦就直接用underscore或者lodash。
我们先模拟一个最简单的通讯录接口,用get方式请求/contacts就返回整个通讯录。
// Query; returns all contacts.
$httpBackend.whenGET('/contacts').respond(contacts);
再来实现Contact.save方法。ngResource的对应方法可以发送一个携带通讯录数据的post请求,这样后端就会增加一条新的纪录。
// Save; create a new contact.
$httpBackend.whenPOST('/contacts').respond(function(method, url, data) {
var newContact = angular.fromJson(data);
contacts.push(newContact);
return [200, newContact, {}];
});
因为angular组件会把数据当作json来处理且不会复查,所以一定要注意将序列化的通讯录数据转成json。创建一条新的纪录就像在contacts数组push一条数据一样简单。
获取一条通讯录数据相对就比较麻烦了,我们必须做两件事:从请求url中提取ID;从关联数组中查找到这个ID。代码如下:
// Get; return a single contact.
$httpBackend.whenGET(/\/contacts\/(\d+)/, undefined, ['id']).respond(function(method, url, data, headers, params) {
var contact = findContactById(params.id);
if (contact == null) {
return [404, undefined, {}];
}
return [200, contact, {}];
});
我们用一个正则匹配url,获取到id的集合。angular把匹配的到的结果保存到params的id属性上,也就是whenGET方法的第三个入参。再抓去这个值传到findContactById方法中,将查询的结果返回给我们。最后,我们返回查询结果,如果为空就返回404。
findContactById方法将ID的值转成数字,过滤数组找到匹配值并返回。
function findContactById(id) {
// Convert id to a number.
var contactId = Number(id);
var matches = contacts.filter(function(contact) {
return contact.id === contactId;
});
var contact = matches.shift();
return contact;
}
update方法于get方法很相似,区别只在于怎样更新通讯录数据。代码如下:
// Update; change details for an existing contact.
$httpBackend.whenPUT(/\/contacts\/(\d+)/, undefined, undefined, ['id']).respond(function(method, url, data, headers, params) {
var contact = findContactById(params.id),
parsedData = angular.fromJson(data);
if (contact == null) {
return [404, undefined, {}];
}
angular.extend(contact, parsedData);
return [200, contact, {}];
});
成功找到这条数据之后再使用angualr.extend方法将更新的数据拷贝到contact对象的属性上。这样我们不用破坏代码里相关的任何引用包括contacts数组。
最后是delete方法。仍然跟get和put类似,删除操作仍然依赖于findContactById方法来查找通讯录。一旦再contact对象中找到它的位置,就使用array#splice删除掉contact对象中的这条纪录:
// Delete; remove existing contact.
$httpBackend.whenDELETE(/\/contacts\/(\d+)/, undefined, ['id']).respond(function(method, url, data, headers, params) {
var contact = findContactById(params.id);
if (contact == null) {
return [404, undefined, {}];
}
// Replace contacts array with filtered results, removing deleted contact.
contacts.splice(contacts.indexOf(contact), 1);
return [200, undefined, {}];
});
完成这些后台服务和数据的定义,我们就可以把整个应用跑起来了。整个页面应用的和核心控制器是一个所有服务功能之行时调用的简单事务。随着通讯录的get操作,结果就会呈现在我们面前。然后使用更新功能给Grace Hopper所在列添加她的联系人姓名。创建操作是添加Gloria Gordan Bolotsky到通讯录。删除操作是将Charls Babbage从通讯录删除。增加,删除,更新的操作是放在一个promise队列中,结果会返回到成功的回调函数中。这就是整个控制器做的事情。
app.controller('MainCtrl', function($scope, Contact) {
//List
$scope.contacts = Contact.query();
// Get
$scope.ada = Contact.get({id: 1});
// Update
Contact.get({id: 2}).$promise
.then(function(contact2) {
contact2.name = 'Rear Admiral Grace Hopper';
return contact2.$update().$promise;
})
.then(updateContactsList);
// Create
var newContact = new Contact({
name: 'Gloria Gordon Bolotsky',
phone: '8445556433'
});
newContact.$save()
.then(updateContactsList);
// Delete
Contact.remove({id: 3}).$promise
.then(updateContactsList);
function updateContactsList() {
// Refresh contacts list
$scope.contacts = Contact.query();
}
});
最后一步创建这个简单应用的视图部分:
<!DOCTYPE html>
<html ng-app="app">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-resource.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-mocks.js"></script>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>All contacts:</p>
<ul>
<li ng-repeat="contact in contacts">{{ contact.name }}</li>
</ul>
<p>Contact 1: {{ ada.name }}</p>
</body>
</html>
代码给我们简单演示了从通讯录服务检索一条联系人记录。页面中需要引入:angular,ngResource和ngMockE2E几个文件。
终于完成了!虽然简单但是可以给我们使用mock模拟CRUD操作到现代web应用打下基础。
原文地址:http://madebymunsters.com/blog/posts/creating-a-mock-backend-in-angular/
祁幽小贵 译