【问题标题】:Single Page Application and RESTful API单页应用程序和 RESTful API
【发布时间】:2015-05-09 16:37:51
【问题描述】:

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

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

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

谢谢

丽安娜

【问题讨论】:

  • 在单页应用程序中可以有导航。
  • 对不起?我不明白你的回答..
  • 我认为@Bogdan 解释得很好:)

标签: angularjs rest single-page-application hateoas


【解决方案1】:

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

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

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

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

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

【讨论】:

    【解决方案2】:

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

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

    在消费者-服务交互中:-

    1. 消费者向入口点提交初始请求 服务。
    2. 服务处理请求并使用填充了链接的资源表示进行响应。
    3. 消费者选择其中一个链接过渡到下一步 在互动中。
    4. 由于多次这样的交互消费者的进步 朝着它的目标前进。

    示例代码说明 服务入口点

    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 知道的唯一 url,它将以填充有链接的资源表示进行响应,以进行进一步的交互。

    {
        "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 从资源的部分信息开始,它将按需提供其他资源信息

    • 具有部分用户信息的用户列表(在启动时,知道 url 之前)
    • 用户资源信息Section1(按需,从1开始查找url 以上)
    • Section2用户资源信息(点播,从1开始查找url 以上)
    • 用户资源信息的Section-n(按需,从1开始查找url 以上)

    带有 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 的用户控制器

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

    【讨论】:

    • 非常好的帖子!非常感谢,非常有帮助! :)
    • 请问您将如何处理深度链接?如果用户直接请求 URL/user-section1 会发生什么。或者,当它指的是特定用户时,它也很容易成为/users/123123/section1。我的问题不是关于你如何使用 Angular 来做到这一点,而是关于任何类型的 SPA。
    • 一个简单的解决方案是检查控制器中的 $state 或 url。基于此,你的控制器应该去获取适当的资源并渲染用户界面。
    • 示例状态定义 url: .state('taskmgr.tasklist', { url: '/tasks?offset&limit&orderBy&asc', templateUrl: '/tasklist.html', ...etc });控制器在加载时将检查状态并使用相同的参数(如果存在)点击任务列表 rest api。
    • 有人对@amann 的问题有解决方案吗?
    猜你喜欢
    • 2018-03-04
    • 2012-10-16
    • 2010-10-15
    • 2019-06-14
    • 1970-01-01
    • 2020-06-10
    • 2018-06-27
    • 1970-01-01
    • 2020-10-24
    相关资源
    最近更新 更多