Single Page Application and RESTful API
Asked Answered
G

2

7

A real RESTful API leverages hypermedia so that clients rely only on dynamic hypermedia provided by server to navigate through the application (the concept known as HATEOAS)

This concept is easily applicable to web applications but how do you apply it to Single Page Application as SPAs normally manage their state internally (without relying on the server as far as navigation is concerned) ?

My feeling is that SPAs cannot fully leverage RESTful APIs or did I miss something ?

Thanks

Riana

Gally answered 8/3, 2015 at 12:39 Comment(3)
Well there can be navigation in a single page application.Edging
Sorry ? I did not understand your answer ..Gally
I think @Bogdan explained it well :)Edging
D
3

A SPA's particularity is it provides a more fluid user experience with the UI constructed on the client instead of being constructed on the server and just rendered on the client.

A SPA doesn't necessarily need to hold its own state, the server can still drive the interactions with HATEOAS. For the SPA to be hypermedia driven or otherwise maintain its own state and access predefined resources, depends entirely on the application.

Combinations can also work, like for example the SPA maintaining its own state for some parts with other being driven by the server. In this sense, consider for example paginated results. The SPA might go to a particular URL (having prior knowledge of that resource) to get a list of results, with those results being paginated. The server embeds links inside the results so that the client can navigate them to the next and previous pages (you interact with the application through hypermedia provided by the server).

My feeling is that SPAs cannot fully leverage RESTful APIs or did I miss something ?

As I said above, it depends on your application. If it makes sense for the application to be hypermedia driven, then it can be built like that. On the other hand, if it makes more sense to have the SPA "drive itself" then forcing HATEOS on it might not be a good idea.

Drain answered 8/3, 2015 at 13:13 Comment(0)
N
7

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 :-

  1. The consumer submits initial request to the entry point of the service.
  2. The service handles the request and respond with a resource representation populated with links.
  3. The consumer chooses one of the links to transition to the next step in the interaction.
  4. 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.

Needful answered 8/3, 2015 at 17:15 Comment(5)
Very nice post ! Thank you very much, it is very helpful ! :)Gally
May I ask how you would handle deep linking? What would happen if the user directly requests the URL/user-section1. Or, as that refers to a specific user, it could easily also be /users/123123/section1. My question is less about how you do this with angular, but rather with any kind of SPA at all.Tessi
a simple solution is to check the $state or url in your controller. based on that, your controller should go fetch the appropriate resources and render the ui.Carnot
example state definition url: .state('taskmgr.tasklist', { url: '/tasks?offset&limit&orderBy&asc', templateUrl: '/tasklist.html', ...etc }); The controller, when loading will check the state and hit the task list rest api with those same parameters (if they exist).Carnot
Does anybody have a solution for @Tessi 's question?Septuagesima
D
3

A SPA's particularity is it provides a more fluid user experience with the UI constructed on the client instead of being constructed on the server and just rendered on the client.

A SPA doesn't necessarily need to hold its own state, the server can still drive the interactions with HATEOAS. For the SPA to be hypermedia driven or otherwise maintain its own state and access predefined resources, depends entirely on the application.

Combinations can also work, like for example the SPA maintaining its own state for some parts with other being driven by the server. In this sense, consider for example paginated results. The SPA might go to a particular URL (having prior knowledge of that resource) to get a list of results, with those results being paginated. The server embeds links inside the results so that the client can navigate them to the next and previous pages (you interact with the application through hypermedia provided by the server).

My feeling is that SPAs cannot fully leverage RESTful APIs or did I miss something ?

As I said above, it depends on your application. If it makes sense for the application to be hypermedia driven, then it can be built like that. On the other hand, if it makes more sense to have the SPA "drive itself" then forcing HATEOS on it might not be a good idea.

Drain answered 8/3, 2015 at 13:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.