Angular与服务端交互
在Angular中,封装了诸如$http
、$resource
等众多服务模块,供开发者与服务端交互时调用,同时,应用内部的缓存机制可加速交互时的数据通信,高效的处理客户端与服务端的数据交互
与服务端交互简介
Angular中的$http
服务就是将以往的Ajax请求封装成了一个内部的服务模块来供开发者使用。
传统的Ajax方式与服务端交互
下面是传统的Ajax请求示例代码:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>传统Ajax与服务端交互</title>
<style>
.frame {font-size: 12px; width: 320px; float: left}
ul {list-style-type: none; padding: 0; margin: 0}
ul li {background-color: #f3f1f1; padding: 8px; float: left; border-bottom: 1px solid #666}
ul li span {text-align: left; width: 86px; height: 18px; line-height: 18px; float: left}
.show {width: 260px; padding: 8px; background-color: #eee;}
.show .tip {font-size: 9px; color: #666; margin: 8px 3px}
</style>
</head>
<body>
<div class="frame">
<ul id="stuInfo">
<li>正在加载中......</li>
</ul>
</div>
<script>
(function () {
var xhr = null;
if (window.ActiveXObject) {
xhr = new ActiveXObject("Microsoft.XMLHTTP")
} else if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
}
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status === 200) {
var HTML = '';
var data = eval("(" + xhr.responseText + ")");
for (var i=0; i<data.length; i++) {
HTML += '<li><span>' + data[i].Code + '</span>';
HTML += '<span>' + data[i].Name + '</span>';
HTML += '<span>' + data[i].Score + '</span></li>';
}
document.getElementById('stuInfo').innerHTML = HTML;
}
}
}
xhr.open('GET', 'http://localhost/data/stu.php', true);
xhr.send();
})();
</script>
</body>
</html>
PHP服务端代码:
<?php
header('Content-type: text/json');
$stulist = array (
array('Code'=>'10101', 'Name'=>'刘真真', 'Score'=>'530'),
array('Code'=>'10102', 'Name'=>'张明吉', 'Score'=>'460'),
array('Code'=>'10103', 'Name'=>'舒虎', 'Score'=>'660'),
array('Code'=>'10104', 'Name'=>'周晓敏', 'Score'=>'500'),
array('Code'=>'10105', 'Name'=>'卢明明', 'Score'=>'300'),
array('Code'=>'10106', 'Name'=>'王小五', 'Score'=>'490')
);
echo json_encode($stulist);
?>
使用$http
服务与服务端交互
在Angular中,与后端交互调用$http
服务模块,它封装了Javascript中的XMLHttpRequest
对象,接收一个对象作为参数,用于收集生成HTTP请求的配置内容,同时返回一个promise
对象,该对象可以使用success
和error
两个回调方法
通用格式如下:
$http.请求类型(url, [data], [config])
.success (data, status, headers, config) {
// 成功后的操作
} .error (data, status, headers, config) {
// 错误时的操作
}
- url: 表示一个相对或绝对的服务端请求路径
- 请求类型: 包括POST、GET、JSONP、DELETE、PUT和HEAD
- 回调参数data: 表示返回的数据体
- status:表示返回的状态值
- headers: 表示返回的头函数
- config: 表示发送HTTP请求的完整配置信息,一个对象
示例:
html:
<div class="frame">
<div class="tip">POST返回的结果是:{{result}}</div>
<button ng-click="onclick()">发送</button>
</div>
javascript:
var myapp = angular.module('MyApp', []);
// 将客户端数据以POST方式通过$http服务发送到服务端
// 需要调用config方法,注入$httpProvider服务
// 并调用该服务对象重置发送数据时默认函数transformRequest和属性Content-Type的值
myapp.config(function ($httpProvider) {
// 通过$httpProvider服务改写transformRequest和headers
$httpProvider.defaults.transformRequest = function (obj) {
var arrStr = [];
for (var p in obj) {
arrStr.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
}
return arrStr.join('&');
}
$httpProvider.defaults.headers.post = {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
myapp.controller('MyController', ['$scope', '$http', function ($scope, $http) {
var postData = {name: 'Kaindy'};
$scope.onclick = function () {
$http.post('post.php', postData)
.success(function (data, status, headers, config) {
$scope.result = data;
})
.error(function (data, status, headers, config) {
$socpe.result = data;
})
}
}]);
使用$http
配置对象方式与服务端交互
上面的方式缺乏灵活性,而且代码量较多,我们可以将$http
服务模板当成一个函数来使用,将构造XHR对象的所有配置项作为一个对象,并将对象定义为函数的形参,在调用时修改形参对象中的各属性值即可,调用格式如下:
// $http中的形参是一个配置对象
$http({
method: // 请求方法,可以是PSOT、GET、JSONP、DELETE、PUT和HEAD
url: // 向服务器请求的地址
data: // 对象,作为消息体的一部分发送给服务端,常用于POST和PUT
params: // 字符串或对象,如果是对象将被自动按json格式序列化,并追加到URL后
transformRequest: // 用于将请求头信息和请求体进行序列化,并生成一个数组发送给服务端
transformResponse: // 用于将响应头信息和响应体进行反序列化,其实质就是解析服务器发送过来的被序列化后的数据
cache: // 如果为true,表示将缓存请求结果,反之则不缓存
timeout: // 表示延迟发送HTTP请求的时间,单位是毫秒
})
示例代码:
html:
<input type="text" ng-model="num">
<button ng-click="onclick()">验证奇偶</button>
<div class="tip">您输入的是: {{result}}</div>
javascript:
var myapp = angular.module('MyApp', []);
myapp.controller('MyController', ['$scope', '$http', function ($scope, $http) {
// $scope.num = 0;
// $scope.result = '偶数';
$scope.onclick = function () {
$http({
method: 'GET',
url: 'chk.php',
params: {
n: $scope.num
}
}).success(function (data, status, headers, config) {
$scope.result = data;
})
}
}]);
php:
<?php
function checkNum ($num) {
return ($num % 2) ? true : false;
}
if (checkNum($_GET['n']) === true) {
echo '奇数';
} else {
echo '偶数';
}
?>
需要注意的是,在Angular中,执行$http
函数后,它返回的内容其实一个是promise
对象,可以直接通过链式的写法调用then
方法获取成功和异常后的数据
$http({
// 配置对象
})
.success(fn1)
.error(fn2)
等价于:
$http({
// 配置对象
})
.then(fn1, fn2)
上面的fn1和fn2分别表示成功和错误时的返回函数,使用第二种方式获取的是服务端完整的响应对象,而使用success
和error
方法只是接收解析并处理后的响应对象
Angular中的缓存
缓存的功能就是加快获取内容的速度,减少重复请求。因此,在Angular中,提供了专门的服务 - $cacheFactory
来生成缓存对象,同时,$http
服务中还可以开启缓存、自定义默认缓存名称
$cacheFactory
服务创建缓存对象
使用$cacheFactoy
服务创建缓存对象,一般都以key/value形式存储,如下:
$cacheFactoy(key, [options]);
参数key表示缓存对象的名称,可选参数options是一个对象,用于指定缓存的特征。一般情况下,会在这个对象中添加一个capacity
属性,它是一个数字,用于说明缓存的最大容量,如该值为3,则只能缓存前3次请求。
创建或获取缓存对象后,就可以使用对象本身的方法进行缓存的操作
(1) info
方法
info
方法返回缓存对象的一些信息,包括大小、名称
var cache = $cacheFactory('test');
console.log(cache.info());
(2) put
方法
put
方法向缓存对象中以key/value的形式添加缓存内容,并返回添加后的键值
cache.put('c1', 'hello');
console.log(cache.put('c1', 'hello'));
(3) get
方法
get
方法获取键名对应的键值内容
console.log(cache.get('c1')); // hello
console.log(cache.get('c2')); // undefined
(4) remove
方法
remove
方法可以移除指定键名的缓存
cache.remove('c1');
(5) removeAll
和destory
方法
removeAll
方法用于移除全部的缓存内容,并重置缓存结构,destory
方法则是从$cacheFactory
缓存注册表中删除所有的缓存引用条目,并重置缓存对象
示例代码:
<div ng-controller="MyCtrl">
<input type="text" ng-model="cname" size="6">
<button ng-click="cset()">设置</button>
<button ng-click="cshow()">显示</button>
<button ng-click="cdel()">删除</button>
<div>缓存值是: {{cvalue}}</div>
</div>
var myapp = angular.module('MyApp', []);
myapp.service('cache', function ($cacheFactory) {
return $cacheFactory('test');
});
myapp.controller('MyCtrl', ['$scope', 'cache', function ($scope, cache) {
$scope.cset = function () {
if (cache.put('mytest', $scope.cname)) {
alert('set cache success!');
}
}
$scope.cshow = function () {
var tcache = cache.get('mytest');
$scope.cvalue = tcache ? tcache : '空值';
}
$scope.cdel = function () {
cache.remove('mytest');
}
}])
$http
服务中的缓存
在Angular中,当调用$http
方法与服务端进行数据交互时,也能使用缓存,方法是在配置对象中添加一个名为'cache'的属性,并将它的属性值设为true,表示开启请求缓存
示例代码:
<div ng-controller="MyCtrl">
<div>接收的内容是: {{result}}</div>
<div>缓存的内容是: {{cache}}</div>
</div>
var myapp = angular.module('MyApp', []);
myapp.controller('MyCtrl', ['$scope', '$http', '$cacheFactory', function ($scope, $http, $cacheFactory) {
var url = 'cache.php';
// 在调用$http方法时,Angular内部自动创建$http缓存对象
var cache = $cacheFactory.get('$http');
$http({
method: 'GET',
url: url,
cache: true
})
.then(function (data, status, headers, config) {
$scope.result = data.data;
var arrResp = cache.get(url);
$scope.cache = arrResp[0] + ' - ' + arrResp[1];
})
}])
自定义$http
服务中的缓存
在自定义缓存对象过程中,可以采用传递实例缓存的方法,将定义好的缓存对象添加到$http服务中
示例代码:
<div ng-controller="MyCtrl">
<div>接收内容是: {{result}}</div>
<button ng-click="refresh()">刷新</button>
</div>
var myapp =angular.module('MyApp', []);
// 创建名为cache的服务,并返回一个名为mycache的缓存实例
myapp.service('cache', ['$cacheFactory', function ($cacheFactory) {
return $cacheFactory('mycache', {capacity: 3});
}]);
myapp.controller('MyCtrl', ['$scope', '$http', 'cache', function ($scope, $http, cache) {
var url = 'cache.php';
$http({
method: 'GET',
url: url,
cache: cache // 这里设置为cache,实现缓存实例的传递
}).success(function (data, status, headers, config) {
$scope.result = data;
cache.put('c', data);
})
$scope.refresh = function () {
var _c = cache.get('c');
$scope.result = (_c) ? _c + '来源缓存' : '刷新失败!';
}
}])
$resource
服务
$resource
服务能支持与RESTful的服务器进行无缝隙的数据交互,在调用$resource
服务后,返回的$resource
对象包含了多种与服务端进行交互的API,像get
、save
和query
等
$resource
服务的使用和对象中的方法
$resource
服务是一个可选模块,所以它没有被包含在Angular中,所以如果需要使用它,就需要使用<script>
元素进行文件导入
<script src="js/angular-resource.min.js"></script>
导入模块后,在应用的模型中通过下面的方式进行注入
angular.module('myapp', ['ngResource'])
注入之后,就可以在控制器或其他自定义的服务中直接调用$resource
服务了
var obj = $resource(url, [, paramDefaults] [, actions]);
obj
表示请求服务器指定url地址后返回的$resource
对象,该对象就包含了与服务器进行数据交互的全部API
参数url表示请求服务器的地址,它允许使用占位符变量,该变量以:
为前缀
var obj = $resource('url?action=:act');
obj.$save(act : 'save');
// 在执行save后,实际发送地址就是:url?action=save
参数paramDefaults是一个对象,用于设置请求时的默认参数值
var obj = $resource('url?action=:act', {
act: 'save',
a: '1',
b: '2'
});
// 实际发送地址为:url?action=save&a=1&b=2
另一个可选参数actions也是一个对象,它的功能是扩展默认资源动作,比如可以在该对象中自定义新的方法
var obj = $resource('url?action=:act', {
// 定义请求默认值
}, {
a: {
method: 'get'
}
});
然后我们就可以直接调用可选参数actions中自定义的方法a,即obj.$a()
调用$resource
服务所返回的对象中包含5个与服务端交互的API,2个是GET类型,3个是非GET类型
(1) $resource
对象中的GET类型请求
$resource
对象中的两个GET类型请求分别是get
和query
方法
var obj = $resource('url');
// get()方法
obj.get(params, successFn, errorFn);
// query()方法
obj.query(params, successFn, errorFn);
参数params是一个对象,用于添加随请求一起发送的数据,发生请求时该对象中的键值会被自动序列化并添加到url的后面。
successFn和errorFn分别表示成功和失败后的回调函数
它们的区别是,get()
方法可以返回单个资源,而query()
方法必须返回一个数组或集合类的资源
(2) $resource
对象中的非GET类型请求
非GET类型请求有3个,分别是save
、delete
和remove
方法
var obj = $resource(url);
// save()方法
obj.save(params, postData, successFn, errorFn);
// delete()方法
obj.delete(params, postData, successFn, errorFn);
// remove()方法
obj.remove(params, postData, successFn, errorFn);
非GET类型请求与上面的GET请求类型相比,多了一个postData参数,它的功能是添加以非GET方式向服务端发送的数据体
save()
方法在服务端保存数据,它将以POST方式向服务端发送请求,postData参数中添加的数据体也将一起被发送
delte()
和remove()
方法都是在删除服务端数据时使用,它们将携带postData参数中添加的数据体,以delete方式向服务端发送请求,它们的区别是,remove
方法可以解决IE浏览器中delete
是JavaScript保留字而导致的错误
综合示例:
html:
<div ng-controller="MyCtrl">
<ul>
<li ng-repeat="item in items">
<span>{{item.Code}}</span>
<span>{{item.Name}}</span>
<span>{{item.Sex}}</span>
</li>
</ul>
<div>
key值: <input type="text" ng-model="key">
<button ng-click="save()">保存</button>
<div>{{result}}</div>
</div>
</div>
javascript:
var myapp = angular.module('MyApp', ['ngResource']);
myapp.config(function ($httpProvider) {
$httpProvider.defaults.transformRequest = function (obj) {
var arrStr = [];
for (var p in obj) {
arrStr.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
}
return arrStr.join('&');
}
$httpProvider.defaults.headers.post = {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
myapp.controller('MyCtrl', ['$scope', '$resource', function ($scope, $resource) {
var stus = $resource('info.php');
stus.query({action: 'search'}, function (resp) {
$scope.items = resp;
});
$scope.save = function () {
var data = {
key: $scope.key
}
stus.save({action: 'save'}, data, function (resp) {
$scope.result = (resp[0] == '1') ? '保存成功' : '保存失败';
})
}
}]);
PHP:
<?php
header('Content-Type: text/json');
if ($_GET['action'] == 'search') {
$stulist = array (
array('Code'=>'1001', 'Name'=>'刘振', 'Sex'=>'男'),
array('Code'=>'1002', 'Name'=>'李雨', 'Sex'=>'女')
);
echo json_encode($stulist);
} elseif ($_GET['action'] == 'save') {
if ($_POST['key'] == '1010') {
echo '1';
} else {
echo '0';
}
}
?>
在$resource
服务中自定义请求方法
在$resource
服务中自定义请求方法,只需在调用$resource
服务的方法中,添加第3个可选项参数actions,在参数对象中,通过key/value的方式自定义$resource
对象方法。
示例代码:
html:
<div ng-controller="MyCtrl">
<div>{{r0}}</div>
<div>{{r1}}</div>
<div>{{r2}}</div>
<button ng-click="click()">开始</button>
</div>
javascript:
'use strict';
var url = 'self.php?action=:act';
var myapp = angular.module('MyApp', ['ngResource']);
// 重置transformRequest和Content-Type,以便以POST方式通过$http发送数据
myapp.config(function ($httpProvider) {
$httpProvider.defaults.transformRequest = function (obj) {
var arrStr = [];
for (var p in obj) {
arrStr.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
}
return arrStr.join('&');
}
$httpProvider.defaults.headers.post = {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
// 通过factory工厂函数定义custom服务,返回$resource对象
// 并在actions参数中自定义方法update
myapp.factory('custom', ['$resource', function ($resource) {
return $resource(url,
{
act: 'search'
},
{
update: {
method: 'POST',
params: {
update: true
},
isArray: false
}
});
}]);
myapp.controller('MyCtrl', ['$scope', 'custom', function ($scope, custom) {
$scope.click = function () {
custom.get({id: '1010'}, function (resp0) {
$scope.r0 = (resp0[0] == '1') ? '查找成功' : '查找失败';
resp0.key = '1011';
resp0.$update({act: 'update'}, function (resp1) {
$scope.r1 = (resp1[0] == '1') ? '更新成功' : '更新失败';
resp1.key = '1012';
resp1.$save({act: 'save'}, function (resp2) {
$scope.r2 = (resp2[0] == '1') ? '保存成功' : '保存失败';
})
});
});
}
}]);
PHP:
<?php
if ($_GET['action'] == 'search') {
if ($_GET['id'] == '1010') {
echo '1';
} else {
echo '0';
}
} elseif ($_GET['action'] == 'update') {
if ($_POST['key'] == '1011' && $_GET['update'] == 'true') {
echo '1';
} else {
echo '0';
}
} elseif ($_GET['action'] == 'save') {
if ($_POST['key'] == '1012') {
echo '1';
} else {
echo '0';
}
}
?>
promise对象
在Ajax中,我们会添加回调函数来处理服务端返回的数据,但这种处理方法失去了控制流、异常处理,并会陷入层层的回调嵌套中。
promise是一种处理异步编程的模式,可以有效的解决回调的繁琐,并以一种同步的方式去处理业务流程
我们用一种拟物化的方式来说明pormise
比如有一名A客户,向B公司提出制作网页的需求,B公司答应3天内完成,这个承诺就是一个promise对象
它的本质就是A客户发起的一个延期业务,可以理解为通过$q
对象调用defer
方法创建了一个延期对象
在接下来的3天里,A客户与B公司交流开发进度,这可以理解为调用延期对象中的notify
方法发送消息的过程
如果3天内,B公司正常将网页交付给A客户了,则可以理解为调用延期对象中resolve
方法的过程
如果无法交付,则可以理解为调用延期对象中reject
方法的过程
如果B公司将以前做过的一个相同的页面交付给了A客户,A客户也很满意,则可以理解为通过$q
对象调用then
方法的过程
总结promise中常用的各种方法:
defer()
notify()
resolve()
reject()
then()
在Angular中创建一个promise对象,必须在模板中先注入$q
服务,然后调用defer()
方法创建一个延期对象
var myapp = angular.module('MyApp', []);
myapp.controller('MyCtrl', ['$scope', '$q', function($scope, $q) {
var defer = $q.defer(); // 创建延期对象
}])
defer
是一个延期对象,包括3个方法,分别是notify()
、resolve()
和reject()
,还有一个名为promise
的属性
一旦创建了promise对象,就可以通过调用then
方法来执行延期对象不同操作后的回调函数,then
方法包含与操作相对应的3个回调函数
promise.then(successCallback, errorCallback, notifyCallback);
successCallback
表示执行resolve方法时的回调函数
errorCallback
表示执行reject方法时的回调函数
notifyCallback
表示执行notify方法时的回调函数
示例代码:
html:
<div ng-controller="MyCtrl">
<div>{{t0}}</div>
<div>{{t1}}</div>
<button ng-click="action(true)">解决</button>
<button ng-click="action(false)">拒绝</button>
</div>
'use strict';
var myapp = angular.module('MyApp', []);
myapp.controller('MyCtrl', ['$scope', '$q', function ($scope, $q) {
var defer = $q.defer(); // 定义延期对象
$scope.action = function (type) {
defer.notify(0);
type ? defer.resolve(1) : defer.reject(1);
// 创建promise对象
var promise = defer.promise;
// 调用promise的then方法完成回调处理
promise.then(function (n) {
n++;
$scope.t1 = '已处理完成:' + n;
}, function (n) {
n++;
$scope.t1 = '未完成原因:' + n;
}, function (n) {
n++;
$scope.t0 = '正在处理中:' + n;
});
}
}]);
promise对象在$http中的应用
在$http
请求中使用promise对象,可以减少数据加载时的白框现象或等待加载的时间
示例代码:
html:
<div ng-controller="MyCtrl">
{{result}}
</div>
javascript:
<script>
'use strict';
var myapp = angular.module('MyApp', []);
myapp.factory('async', function ($q, $http) {
var defer = $q.defer();
$http.get('async.php')
.success(function (data) {
defer.resolve(data);
})
.error(function (reason) {
defer.reject(reason);
})
return defer.promise;
});
myapp.controller('MyCtrl', ['$scope', 'async', function ($scope, async) {
var promise = async;
promise.then(function (resp) {
$scope.result = '请求成功:' + resp;
}, function (n) {
$scope.result = '请求失败:' + resp;
})
}])
</script>