Angular 内置了与远程 HTTP 服务器通信的支持。 $http 服务通过浏览器的 XMLHttpRequest 对象或通过带有填充符的JSON(JSONP)处理低级 AJAX 请求。 $resource 服务允许您与 RESTful 数据源交互,并提供高级行为,这些行为自然映射到 RESTful 资源。
您希望通过 AJAX 请求获取 JSON 数据并呈现它。
使用$http
服务实现一个控制器,以获取数据并将其存储在范围内:
<body ng-app="MyApp">
<div ng-controller="PostsCtrl">
<ul ng-repeat="post in posts">
<li>{{post.title}}</li>
</ul>
</div>
</body>
var app = angular.module("MyApp", []);
app.controller("PostsCtrl", function($scope, $http) {
$http.get('data/posts.json').
success(function(data, status, headers, config) {
$scope.posts = data;
}).
error(function(data, status, headers, config) {
// log error
});
});
您可以在 GitHub 上使用角度种子项目找到完整的示例。
控制器定义了对$scope
和$http
模块的依赖。使用get
方法执行对data/posts.json
端点的 HTTP GET 请求。它用success
和error
方法返回一个 $promise 对象。一旦成功,JSON 数据被分配到$scope.posts
以使其在模板中可用。
$http
服务支持 HTTP 动词get
、head
、post
、put
、delete,
和jsonp
。我们将在接下来的章节中研究更多的例子。
$http
服务自动添加某些 HTTP 头,例如X-Requested-With: XMLHttpRequest
。但是您也可以使用$http.defaults
功能自行设置自定义的 HTTP 头:
$http.defaults.headers.common["X-Custom-Header"] = "Angular.js"
直到现在,$http
服务看起来并不是特别特别。但是如果你查看文档,你会发现很多不错的特性,包括,例如,请求/响应转换,自动为你反序列化 JSON,响应缓存,处理全局错误处理的响应拦截器,身份验证或其他预处理任务,当然还有承诺支持。我们将在后面的章节中研究$q
服务,Angular 的承诺/延期服务。
您希望使用 RESTful 数据源。
使用 Angular 的高级$resource
服务。请注意,Angular ngResource
模块需要单独加载,因为它不包含在基本 angular.js 文件中:
<script src="angular-resource.js">
现在让我们从将应用程序模块和我们的Post
模型定义为 Angular 服务开始:
var app = angular.module('myApp', ['ngResource']);
app.factory("Post", function($resource) {
return $resource("/api/posts/:id");
});
现在,我们可以使用我们的服务来检索控制器内部的帖子列表(例如,HTTP GET/API/post):
app.controller("PostIndexCtrl", function($scope, Post) {
Post.query(function(data) {
$scope.posts = data;
});
});
或由id
指定的具体帖子(如 HTTP GET/API/post/1):
app.controller("PostShowCtrl", function($scope, Post) {
Post.get({ id: 1 }, function(data) {
$scope.post = data;
});
});
我们可以使用 save 创建一个新帖子(例如,HTTP POST/API/POST):
Post.save(data);
并且我们可以通过id
删除特定的帖子(例如,DELETE/API/post/1):
Post.delete({ id: id });
完整的示例代码基于布莱恩·福特的角度表达种子并使用表达框架。
你可以在 GitHub 上找到完整的例子。
遵循一些约定会大大简化我们的代码。我们只通过传递 URL 模式来定义$resource
。这给了我们一些很好的方法,包括query
、get
、save
、remove,
和delete
来使用我们的资源。在上面的例子中,我们实现了几个控制器来覆盖典型的用例。get
和query
方法需要三个参数,请求参数、成功回调和错误回调。save
方法需要四个参数,请求参数、开机自检数据、成功回调和错误回调。
$resource
服务目前不支持承诺,因此与$http
服务有明显不同的接口。但是我们不用等太久,因为 1.1 开发分公司已经开始引入$resource
服务承诺支持!
$resource
查询或获取函数返回的对象是提供$save
、$remove,
和$delete
方法的$resource
实例。这允许您轻松获取资源并更新它,如下例所示:
var post = Post.get({ id: 1 }, function() {
post.title = "My new title";
post.$save();
});
需要注意的是get
调用会立即返回一个空引用——在我们的例子中是post
变量。一旦从服务器返回数据,就填充现有的引用;然后,我们可以方便地更改我们的文章标题和使用$save
方法。
请注意,有一个空引用意味着我们的帖子不会在模板中呈现。但是,一旦数据被返回,视图会自动重新呈现,显示新数据。
如果你对帖子的回应不是一个数组,而是一个更复杂的 JSON 呢?这通常会导致以下错误:
TypeError: Object #<Resource> has no method 'push'
Angular 似乎希望您的服务返回一个 JSON 数组。看看下面的 JSON 例子,它在 JSON 对象中包装了一个posts
数组:
{
"posts": [
{
"id" : 1,
"title" : "title 1"
},
{
"id": 2,
"title" : "title 2"
}
]
}
在这种情况下,您必须相应地更改$resource
的定义:
app.factory("Post", function($resource) {
return $resource("/api/posts/:id", {}, {
query: { method: "GET", isArray: false }
});
});
app.controller("PostIndexCtrl", function($scope, Post) {
Post.query(function(data) {
$scope.posts = data.posts;
});
});
我们仅通过将isArray
属性设置为false
来将query
动作的配置更改为不期待数组。然后,在我们的控制器中,我们可以直接访问data.posts
。
将您的模型和$resource
用法封装在 Angular 服务模块中,并将其注入到您的控制器中,这通常是一个很好的做法。这样,您可以轻松地在不同的控制器中重用同一个模型,并更轻松地测试它。
您希望调用一个 JSONP 应用编程接口。
使用$resource
服务,并将其配置为使用 JSONP。作为一个例子,我们将在这里使用推特搜索应用编程接口。
HTML 模板允许您在输入字段中输入搜索词;它将在列表中呈现搜索结果:
<body ng-app="MyApp">
<div ng-controller="MyCtrl">
<input type="text" ng-model="searchTerm" placeholder="Search term">
<button ng-click="search()">Search</button>
<ul ng-repeat="tweet in searchResult.results">
<li>{{tweet.text}}</li>
</ul>
</div>
</body>
$resource
配置可以在请求数据的控制器中完成:
var app = angular.module("MyApp", ["ngResource"]);
function MyCtrl($scope, $resource) {
var TwitterAPI = $resource("http://search.twitter.com/search.json",
{ callback: "JSON_CALLBACK" },
{ get: { method: "JSONP" }});
$scope.search = function() {
$scope.searchResult = TwitterAPI.get({ q: $scope.searchTerm });
};
}
你可以在 GitHub 上找到完整的例子。
推特搜索应用编程接口支持 JSON 格式的callback
属性,如他们的文档中所述。$resource
定义将callback
属性设置为JSON_CALLBACK
,这是使用 JSONP 时来自 Angular 的约定。它是一个占位符,由 Angular 生成的真实回调函数替换。另外,我们将get
方法配置为使用JSONP
。现在,在调用 API 时,我们使用q
URL 参数来传递输入的searchTerm
。
您希望同步多个异步函数,避免 JavaScript 回调地狱。
举个例子,我们想按顺序调用三个服务,并组合它们的结果。让我们从嵌套方法开始:
tmp = [];
$http.get("/app/data/first.json").success(function(data) {
tmp.push(data);
$http.get("/app/data/second.json").success(function(data) {
tmp.push(data);
$http.get("/app/data/third.json").success(function(data) {
tmp.push(data);
$scope.combinedNestedResult = tmp.join(", ");
});
});
});
我们调用get
函数三次来检索三个 JSON 文档,每个文档都有一个字符串数组。我们甚至还没有开始添加错误处理,但是已经通过使用嵌套回调,代码变得混乱,并且可以使用$q
服务进行简化:
var first = $http.get("/app/data/first.json"),
second = $http.get("/app/data/second.json"),
third = $http.get("/app/data/third.json");
$q.all([first, second, third]).then(function(result) {
var tmp = [];
angular.forEach(result, function(response) {
tmp.push(response.data);
});
return tmp;
}).then(function(tmpResult) {
$scope.combinedResult = tmpResult.join(", ");
});
你可以在 GitHub 上找到完整的例子。
all
函数将多个承诺组合成一个承诺,非常优雅地解决了我们的问题。
让我们仔细看看then
法。这是相当做作的,但应该会让你知道如何使用then
来顺序调用函数和传递数据。既然all
函数返回了一个承诺,我们可以在上面再次调用then
。通过返回tmp
变量,它将作为一个tmpResult
参数传递给当时的函数。
在完成这个配方之前,让我们快速讨论一个例子,在这个例子中,我们必须创建自己的延迟对象:
function deferredTimer(success) {
var deferred = $q.defer();
$timeout(function() {
if (success) {
deferred.resolve({ message: "This is great!" });
} else {
deferred.reject({ message: "Really bad" });
}
}, 1000);
return deferred.promise;
}
使用defer
方法,我们创建一个延迟实例。作为异步操作的一个例子,我们将使用$timeout
服务,该服务将根据布尔成功参数来解决或拒绝我们的操作。该函数将立即返回promise
,因此不会在我们的 HTML 模板中呈现任何结果。一秒钟后,计时器将执行并返回我们的成功或失败响应。
这个deferredTimer
可以在我们的 HTML 模板中通过将其包装到范围上定义的函数中来触发:
$scope.startDeferredTimer = function(success) {
deferredTimer(success).then(
function(data) {
$scope.deferredTimerResult = "Successfully finished: " +
data.message;
},
function(data) {
$scope.deferredTimerResult = "Failed: " + data.message;
}
);
};
我们的startDeferredTimer
函数将获得一个success
参数,并将其传递给deferredTimer
。then
函数期望一个成功和一个错误回调作为参数,我们用它来设置一个范围变量deferredTimerResult
来显示我们的结果。
这只是承诺如何简化代码的众多例子之一,但是你可以通过查看 Kris Kowal 的 Q 实现找到更多的例子。
您希望使用 JSONP 应用编程接口对您的控制器和服务进行单元测试。
让我们再次看看我们希望测试的示例:
var app = angular.module("MyApp", ["ngResource"]);
app.factory("TwitterAPI", function($resource) {
return $resource("http://search.twitter.com/search.json",
{ callback: "JSON_CALLBACK" },
{ get: { method: "JSONP" }});
});
app.controller("MyCtrl", function($scope, TwitterAPI) {
$scope.search = function() {
$scope.searchResult = TwitterAPI.get({ q: $scope.searchTerm });
};
});
请注意,它与之前的配方略有不同,因为TwitterAPI
被拉出控制器,现在驻留在自己的服务中。
使用 angular-seed 项目和$ http _ 后端嘲讽服务。
describe('MyCtrl', function(){
var scope, ctrl, httpBackend;
beforeEach(module("MyApp"));
beforeEach(
inject(
function($controller, $rootScope, TwitterAPI, $httpBackend) {
httpBackend = $httpBackend;
scope = $rootScope.$new();
ctrl = $controller("MyCtrl", {
$scope: scope, TwitterAPI: TwitterAPI });
var mockData = { key: "test" };
var url = "http://search.twitter.com/search.json?" +
"callback=JSON_CALLBACK&q=angularjs";
httpBackend.whenJSONP(url).respond(mockData);
}
)
);
it('should set searchResult on successful search', function() {
scope.searchTerm = "angularjs";
scope.search();
httpBackend.flush();
expect(scope.searchResult.key).toBe("test");
});
});
你可以在 GitHub 上找到完整的例子。
由于现在服务和控制器之间有了明确的分离,我们可以简单地将Twitter API
注入到我们的beforeEach
功能中。
用$httpBackend
嘲讽是beforeEach
的最后一步。当 JSONP 请求发生时,我们用mockData
来响应。search()
触发后,我们flush()
返回httpBackend
以便返回我们的mockData
。
看一看 ngMock。$ httpbacknd模块了解更多详情。