angular-hateoas
An AngularJS module for using $resource
with a HATEOAS-enabled REST API.
What is HATEOAS?
HATEOAS, which stands for Hypermedia as the Engine of Application State, is a type of REST architecture that enables enhanced decoupling of server and client. The basic idea is that with every response, the server provides a list of endpoints (or "links") to perform actions or retrieve information related to the data in the response.
Why HATEOAS?
The use of HATEOAS can allow the client to be completely indifferent to the REST structure of the API, and instead rely on knowledge of relationships between objects. In many cases this can result in a more logical client application flow, as well as the opportunity for the server to evolve independently from the client application without disrupting functionality.
What does this module do?
Angular does not have a built-in way to cleanly navigate a HATEOAS-style API — the $resource
object was created to consume traditional service-based endpoints, plugging in variables as needed to account for relationships between objects.
For instance, a traditional approach might look like this:
var personResource = $resource("/api/people/:personId");
var addressResource = $resource("/api/people/:personId/addresses");
var people = personResource.query(null, function () {
var firstPerson = people[0];
var firstPersonAddresses = addressResource.query({ personId: person[i].personId }, function () {
console.log(firstPersonAddresses);
});
});
This is a very simplified example, but notice the arbitrary information that the front-end developer needs to know about how to get an address for a person: you need to know where the addresses
endpoint is, and you need to know what to pass in to the call to get it (personId
).
Compare this to the HATEOAS approach, where the server delivers links to related endpoints:
var personResource = $resource("/api/people/:personId");
var people = personResource.query(null, function () {
var firstPerson = people[0];
var firstPersonAddresses = firstPerson.resource("addresses").query(null, function () {
console.log(firstPersonAddresses);
});
});
The angular-hateoas
module creates an application-wide interceptor to automatically add the resource
method to all HATEOAS responses. Alternatively, you could transform a response manually:
var personResource = $resource("/api/people/:personId");
var people = personResource.query(null, function () {
var firstPerson = people[0];
var firstPersonAddresses = new HateoasInterface(firstPerson).resource("addresses").query(null, function () {
console.log(firstPersonAddresses);
});
});
Usage
To start, make sure you are including angular-hateoas.js
in your JavaScript compiler (or the minified version on your page), and add hateoas
as a dependency in your application module declaration:
var app = angular.module("your-application", ["ngResource", "hateoas"]);
To enable the global interceptor, invoke HateoasInterceptorProvider.transformAllResponses()
in a config
block:
app.config(function (HateoasInterceptorProvider) {
HateoasInterceptorProvider.transformAllResponses();
});
And that's it! All HATEOAS-enabled responses will allow you to retrieve related resources as you need them:
var item = $resource("/api/path/to/item").get(null, function () {
console.log("Here's a related $resource object: ", item.resource("some-related-endpoint"));
});
// also works with array results from $resource(...).query()
var items = $resource("/api/path/to/items").query(null, function () {
angular.forEach(items, function (item) {
console.log("Here's a related $resource object: ", item.resource("some-related-endpoint"));
});
});
You can also instantiate HateoasInterface
directly if you prefer not to use an application-wide interceptor. Simply inject HateoasInterface
where you need it and instantiate a new instance, passing in the raw response data as seen in the previous section.
Options
The HateoasInterfaceProvider
allows for some basic configuration.
Setting the links
key
By default, HateoasInterface
parses the HATEOAS links from an object with the key "links" within the response data. This key can be changed using setLinksKey
:
app.config(function (HateoasInterfaceProvider) {
HateoasInterfaceProvider.setLinksKey("related");
// HateoasInterface will now search response data for links in a property called "related"
});
Setting $resource
options
Angular's $resource
object accepts two optional parameters, as seen in the documentation: paramDefaults
and actions
. These parameters can be passed directly in to the $resource
constructor from the HateoasInterface.resource
method:
var relatedResource = someHateoasItem.resource("some-related-endpoint", {
binding: "value"
}, {
update: {
method: "PUT"
}
});
For more information about these parameters, refer to Angular's $resource
documentation.
The custom HTTP actions can also be set to an application-wide default, which can be useful if you're using the same actions/methods throughout your app:
app.config(function (HateoasInterfaceProvider) {
HateoasInterfaceProvider.setHttpMethods({
update: {
method: "PUT"
}
});
});
About
The angular-hateoas
module was created for a personal need, and while it is working for my own projects, it is still in a very early stage of development and probably won't fit every HATEOAS developer's needs. Feel free to use and enjoy – while I can't guarantee support, I welcome any comments, forks, pull requests, bug reports, etc.
angular-hateoas
was built to work with Spring Hateoas, and has yet to be tested in other environments.
Related Projects
angular-spring-data-rest — based on angular-hateoas
but designed specifically to consume Spring Data REST responses rather than vanilla HATEOAS objects