Single page applications(SPA) can fully leverage RESTful APIs that are HATEOAS enabled, example SPA (angularJS with ui-rauter for state transition)
For computer to computer interaction we advertise protocols
information by embedding links in representation much as we do in
human web.
In a consumer-service interaction :-
- The consumer submits initial request to the entry point of the
service.
- The service handles the request and respond with a resource representation populated with links.
- The consumer chooses one of the links to transition to the next step
in the interaction.
- Over the cause of several such interaction the consumer progress
towards it`s goal.
Illustration with sample code
Service Entry Point
var applicationServices = angular.module('applicationServices', ['ngResource']);
userDetailServices.factory('DetailService', ['$resource',function($resource){
return $resource('api/users', {},{
query : {
method : 'GET',
headers : {'Accept': 'application/json'},
isArray: true
}
});
}]);
Hypermedia is all about loose coupling, when developing service we
abstract away details from consumers there by decreasing coupling, but
not matter the degree of of loose coupling consumers must have enough
information available in order to interact with our services
Assuming that api/users
is the entry-point to the service and the only url your SPA knows about,it will respond with a resource representation populated with links for further interactions.
{
"links": [
{
"rel": "self",
"href": "http://localhost:8080/persons{?page,size,sort}"
}
],
"users": [
{
"id": "3415NE11",
"firstName": "somefirstname",
"lastName": "lastname",
"emailAddress": "someemail",
"links": [
{
"rel": "section1",
"href": "http://localhost:8080/persons/3415NE11/section1
},
{
"rel": "section2",
"href": "http://localhost:8080/persons/3415NE11/section2
},
{
"rel": "gallery,
"href": "http://localhost:8080/filesRepo/profile/3415NE11/images
},
]
}
],
"page": {
"size": 20,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
Your SPA starts with partial information about the the resource, and it will serve additional resource information on-demand
- List of user with partial user information (on start-up, knows url
before hand)
- Section1 of user resource information (on-demand, look up url from 1
above)
- Section2 of user resource information (on-demand, look up url from 1
above)
- Section-n of user resource information (on-demand, look up url from 1
above)
with ui-router navigation
angular.module('userApp', [
'ui.bootstrap',
'ui.router',
'userControllers',
'userServices'
])
.config(
[ '$stateProvider', '$urlRouterProvider', '$httpProvider', function($stateProvider,$urlRouterProvider, $httpProvider) {
$stateProvider
.state('users', {
url: '/users-index',
templateUrl: 'partials/users-index.html',
resolve:{ // Your SPA needs this on start-up
DetailService:function(DetailService){
return DetailService.query();
}
},
controller:'UserController',
})
.state('users.section1', {
url: '/user-section1',
templateUrl: 'partials/user-section1.html',
})
.state('users.section2', {
url: '/users-section2',
templateUrl: 'partials/users.section2.html'
})
.state('users.gallery', {
url: '/users-gallery,
templateUrl: 'partials/users-gallery.html'
});
$urlRouterProvider.otherwise('/');
}])
.run([
'$rootScope',
'$location',
'$state',
'$stateParams'
function($rootScope, $location,$state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}
]);
UserController for your angularJS SPA
(function() {
var userControllersApp = angular.module('userControllers', ['ngGeolocation']);
userControllersApp.controller('UserController',
['$scope',
'$rootScope',
'$http',
'$state',
'$filter',
'DetailService',
function($scope,$rootScope,$http,$state,$filter,DetailService) {
DetailService.$promise.then(function(result){
$scope.users = result.users;
});
$scope.userSection1= function(index){
var somelink = $filter('filter')($scope.users[index].links, { rel: "section1" })[0].href;
$http.get(somelink).success(function(data){
$state.go("users.section1");
});
// $http.post(somelink, data).success(successCallback);
};
$scope.userSection2= function(index){
var somelink = $filter('filter')($scope.users[index].links, { rel: "section2" })[0].href;
$http.get(somelink).success(function(data){
$state.go("users.section2");
});
// $http.post(somelink, data).success(successCallback);
};
$scope.userSection3= function(index){
var somelink = $filter('filter')($scope.users[index].links, { rel: "gallery" })[0].href;
$http.get(somelink).success(function(data){
$state.go("users.gallery");
});
// $http.post(somelink, data).success(successCallback);
};
} ]);
})();
The beauty of of hypermedia is that it allows us to convey protocol
information in a declarative and just in-tame fashion as part of the
application's resource representation
Use $scope.users embedded links for further interactions.look how somelink
is being resolved in section1()
, section2()
and section(3)
functions.
Your Angular SPA navigation(users-index.html) could be
<div ng-repeat="user in users">
<label>{{user.firstname}}</label>
<button type="button" class="btn btn-xs " ng-click="section1($index)">Section1</button>
<button type="button" class="btn btn-xs " ng-click="section2($index)">Section2</button>
<button type="button" class="btn btn-xs " ng-click="section3($index)">Section3</button>
</div>
Now your SPA's state translation in-turn relies on dynamic links provided by server to navigate through the application,this shows SPAs can fully leverage HATEOAS enabled RESTful APIs. my apologies for the very long explanation ,hope it helps.