单页面应用程序和RESTful API

时间:2015-03-08 12:39:38

标签: angularjs rest single-page-application hateoas

真正的RESTful API利用超媒体,以便客户端仅依靠服务器提供的动态超媒体来浏览应用程序(称为HATEOAS的概念)

这个概念很容易适用于Web应用程序,但是如何将它应用于单页应用程序,因为SPA通常在内部管理其状态(在导航方面不依赖于服务器)?

我的感觉是SPA无法充分利用RESTful API,或者我错过了什么?

由于

Riana

2 个答案:

答案 0 :(得分:5)

单页面应用程序(SPA)可以充分利用启用了HATEOAS的RESTful API,例如SPA(带有ui-rauter的angularJS用于状态转换)

  

对于计算机到计算机的交互,我们宣传协议   通过在表示中嵌入链接来实现信息   人类网络。

在消费者服务互动中: -

  1. 消费者向该网站的入口点提交初始请求        服务。
  2. 该服务处理请求并使用填充了链接的资源表示进行响应。
  3. 消费者选择其中一个链接转换到下一步 在互动中。
  4. 在几个这样的互动的原因上,消费者的进步        朝着它的目标。
  5. 带示例代码的插图 服务入口点

    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
                          }
                  });
            }]);
    
      

    超媒体是关于松散耦合,在开发服务时我们   通过减少耦合来抽象消费者的细节,但是   无论消费者必须拥有足够松散耦合的程度   可用信息以便与我们的服务进行交互

    假设api/users是服务的入口点,也是SPA知道的唯一网址,它将使用填充了进一步交互链接的资源表示进行响应。

    {
        "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
        }
    }
    

    您的SPA从有关资源的部分信息开始,它将按需提供其他资源信息

    • 具有部分用户信息的用户列表(在启动时,知道网址 事前)
    • 用户资源信息的第1部分(按需查找,从1开始查找网址) 以上)
    • 用户资源信息的第2部分(按需查找,从1开始查找网址) 以上)
    • 用户资源信息的第n部分(按需查找,从1开始查找网址) 以上)

    使用ui-router导航

    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;
            }
            ]);
    

    angularJS SPA的UserController

    (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);
    
            };
    
        } ]);
    
    })();
    
      

    超媒体的美妙之处在于它允许我们传达协议   信息以陈述性和恰到好处的方式作为一部分   应用程序的资源表示

    使用$ scope.users嵌入式链接进行进一步的互动。了解somelinksection1()section2()函数中section(3)的解析方式。

    您的Angular SPA导航( users-index.html )可能是

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

    现在,您的SPA状态转换依赖于服务器提供的动态链接来浏览应用程序,这表明SPA可以充分利用启用了HATEOAS的RESTful API。 my apologies for the very long explanation ,hope it helps.

答案 1 :(得分:2)

SPA的特殊之处在于它为客户端上构建的UI提供了更流畅的用户体验,而不是在服务器上构建并仅在客户端上呈现。

SPA不一定需要保持自己的状态,服务器仍然可以驱动与HATEOAS的交互。对于SPA是超媒体驱动或以其他方式维护自己的状态并访问预定义资源,完全取决于应用程序。

组合也可以起作用,例如SPA为某些部分维护自己的状态,而其他部分由服务器驱动。从这个意义上讲,考虑例如分页结果。 SPA可能会转到特定的URL(具有该资源的先验知识)以获取结果列表,并将这些结果分页。服务器在结果中嵌入链接,以便客户端可以将它们导航到下一页和上一页(您通过服务器提供的超媒体与应用程序交互)。

  

我的感觉是SPA无法充分利用RESTful API,或者我错过了什么?

正如我上面所说,这取决于你的申请。如果应用程序是超媒体驱动的,那么就可以像这样构建它。另一方面,如果让SPA“驱动自身”更有意义,那么强制使用HATEOS可能不是一个好主意。