CHAPTER 5
Angular has built-in support for communication with remote HTTP servers. The $http service handles low-level AJAX requests via the browser's XMLHttpRequest object or via JSON with padding (JSONP). The $resource service lets you interact with RESTful data sources and provides high-level behaviors, which naturally map to RESTful resources.
You wish to fetch JSON data via an AJAX request and render it.
Implement a controller using the $http service to fetch the data and store it in the scope:
<body ng-app="MyApp"> |
You can find the complete example using the angular-seed project on GitHub.
The controller defines a dependency to the $scope and the $http module. An HTTP GET request to the data/posts.json endpoint is carried out with the get method. It returns a $promise object with a success and an error method. Once successful, the JSON data is assigned to $scope.posts to make it available in the template.
The $http service supports the HTTP verbs get, head, post, put, delete, and jsonp. We are going to look into more examples in the following chapters.
The $http service automatically adds certain HTTP headers, like for example, X-Requested-With: XMLHttpRequest. But you can also set custom HTTP headers by yourself using the $http.defaults function:
$http.defaults.headers.common["X-Custom-Header"] = "Angular.js" |
Until now, the $http service does not really look particularly special. But if you look into the documentation, you will find a lot of nice features including, for example, request/response transformations, automatically deserializing JSON for you, response caching, response interceptors to handle global error handling, authentication or other preprocessing tasks, and, of course, promise support. We will look into the $q service, Angular's promise/deferred service, in a later chapter.
You wish to consume a RESTful data source.
Use Angular's high-level $resource service. Note that the Angular ngResource module needs to be separately loaded since it is not included in the base angular.js file:
<script src="angular-resource.js"> |
Let us now start by defining the application module and our Post model as an Angular service:
var app = angular.module('myApp', ['ngResource']); |
Now we can use our service to retrieve a list of posts inside a controller (for example, HTTP GET /api/posts):
app.controller("PostIndexCtrl", function($scope, Post) { |
Or a specific post by id (for example, HTTP GET /api/posts/1):
app.controller("PostShowCtrl", function($scope, Post) { |
We can create a new post using save (for example, HTTP POST /api/posts):
Post.save(data); |
And we can delete a specific post by id (for example, DELETE /api/posts/1):
Post.delete({ id: id }); |
The complete example code is based on Brian Ford's angular-express-seed and uses the Express framework.
You can find the complete example on GitHub.
Following some conventions simplifies our code quite a bit. We define the $resource by passing the URL schema only. This gives us a handful of nice methods including query, get, save, remove, and delete to work with our resource. In the example above, we implemented several controllers to cover the typical use cases. The get and query methods expect three arguments, the request parameters, the success callback, and the error callback. The save method expects four arguments, the request parameters, the POST data, the success callback, and the error callback.
The $resource service currently does not support promises and therefore has a distinctly different interface to the $http service. But we don't have to wait very long for it, since work has already started in the 1.1 development branch to introduce promise support for the $resource service!
The returned object of a $resource query or get function is a $resource instance which provides $save, $remove, and $delete methods. This allows you to easily fetch a resource and update it as in the following example:
var post = Post.get({ id: 1 }, function() { |
It is important to note that the get call immediately returns an empty reference—in our case the post variable. Once the data is returned from the server, the existing reference is populated; we can then change our post title and use the $save method conveniently.
Note that having an empty reference means that our post will not be rendered in the template. Once the data is returned, though, the view automatically re-renders itself showing the new data.
What if your response of posts is not an array but a more complex JSON? This typically results in the following error:
TypeError: Object #<Resource> has no method 'push' |
Angular seems to expect your service to return a JSON array. Have a look at the following JSON example which wraps a posts array in a JSON object:
{ |
In this case, you have to change the $resource definition accordingly:
app.factory("Post", function($resource) { |
We only change the configuration of the query action to not expect an array by setting the isArray attribute to false. Then, in our controller, we can directly access data.posts.
It is generally good practice to encapsulate your model and $resource usage in an Angular service module and inject that in your controller. This way, you can easily reuse the same model in different controllers and test it more easily.
You wish to call a JSONP API.
Use the $resource service and configure it to use JSONP. As an example, we will take the Twitter Search API here.
The HTML template lets you enter a search term in an input field; itwill render the search result in a list:
<body ng-app="MyApp"> |
The $resource configuration can be done in a controller requesting the data:
var app = angular.module("MyApp", ["ngResource"]); |
You can find the complete example on GitHub.
The Twitter Search API supports a callback attribute for the JSON format as described in their documentation. The $resource definition sets the callback attribute to JSON_CALLBACK, which is a convention from Angular when using JSONP. It is a placeholder that is replaced with the real callback function generated by Angular. Additionally, we configure the get method to use JSONP. Now, when calling the API, we use the q URL parameter to pass the entered searchTerm.
You wish to synchronize multiple asynchronous functions and avoid JavaScript callback hell.
As an example, we want to call three services in sequence and combine the result of them. Let us start with a nested approach:
tmp = []; |
We call the get function three times to retrieve three JSON documents, each with an array of Strings. We haven't even started adding error handling but already, from using nested callbacks, the code becomes messy and can be simplified using the $q service:
var first = $http.get("/app/data/first.json"), |
You can find the complete example on GitHub.
The all function combines multiple promises into a single promise and solves our problem quite elegantly.
Let's have a closer look at the then method. It is rather contrived but should give you an idea of how to use then to sequentially call functions and pass data along. Since the all function returns a promise, again we can call then on it. By returning the tmp variable, it will be passed along to the then function as a tmpResult argument.
Before finishing this recipe, let us quickly discuss an example in which we have to create our own deferred object:
function deferredTimer(success) { |
Using the defer method, we create a deferred instance. As an example of an asynchronous operation, we will use the $timeout service which will either resolve or reject our operation depending on the Boolean success parameter. The function will immediately return the promise and therefore not render any result in our HTML template. After one second, the timer will execute and return our success or failure response.
This deferredTimer can be triggered in our HTML template by wrapping it into a function defined on the scope:
$scope.startDeferredTimer = function(success) { |
Our startDeferredTimer function will get a success parameter which it passes along to the deferredTimer. The then function expects a success and an error callback as arguments which we use to set a scope variable deferredTimerResult to show our result.
This is just one of many examples of how promises can simplify your code, but you can find many more examples by looking into Kris Kowal's Q implementation.
You wish to unit test your controller and service consuming a JSONP API.
Let's have a look again at our example we wish to test:
var app = angular.module("MyApp", ["ngResource"]); |
Note that it slightly changed from the previous recipe, as the TwitterAPI is pulled out of the controller and resides in its own service now.
Use the angular-seed project and the $http_backend mocking service.
describe('MyCtrl', function(){ |
You can find the complete example on GitHub.
Since we now have a clear separation between the service and the controller, we can simply inject the Twitter API into our beforeEach function.
Mocking with the $httpBackend is done as a last step in beforeEach. When a JSONP request happens, we respond with mockData. After the search() is triggered, we flush() the httpBackend in order to return our mockData.
Have a look at the ngMock.$httpBackend module for more details.