AngularJS 使用超媒体 (HATEOAS):如何跨状态使用超媒体 URL

AngularJS with Hypermedia (HATEOAS): how to use hypermedia urls accross states

我有一个 AngularJS 应用程序,带有 ui 路由器 ,它使用 REST API超媒体。一般的想法是让 API 为其各种调用生成 url,并防止客户端自己构建 url。

例如,当获取产品列表时,API returns:

[
  {
    "id": 1,
    "name": "Product A",
    "_links": {
      "self": {
        "href": "http://localhost:4444/api/products/1",
        "name": null,
        "templated": false
      },
      "actions": []
    }
  },
  {
    "id": 2,
    "name": "Product B",
    "_links": {
      "self": {
        "href": "http://localhost:4444/api/products/2",
        "name": null,
        "templated": false
      },
      "actions": []
    }
  }
]

所以,问题是:我想导航到产品的详细信息。我有 API url 可从 collection 超媒体获得。但是,在更改状态时,我需要以某种方式将此 url 传递给详细状态,以便详细状态的控制器可以获取产品。

UI url 与 API url 完全解耦,即客户端有自己的 url 方案。

实现此目的的最佳方法是什么,同时保持客户端无状态且每个页面都可收藏?

一种解决方案是将 url 传递给 ui routerdata 属性。但是,该页面不能添加书签。另一种方法是在 UI url 中传递 API url,这将使页面可收藏(只要 API url不会改变),但是 UI url 会很丑。

还有其他想法吗?

除非我对此非常错误,否则我不是在寻找模板化解决方案,即 API returns 需要 url 的模板的解决方案由客户端注入参数。重点是 url 已经填充了数据,因为一些 urls 是 quite 比上面提供的例子复杂一点。

我以前遇到过几次这个问题。我在下面详细说明了我的首选解决方案 step-by-step。最后两个步骤专门针对您在 post 中列出的问题,但同样的原则可以应用于您的整个应用程序。

1。根端点

首先在 API 级别定义根端点。相应的根实体是顶级 link 的集合,换句话说,客户端需要直接访问的 link。

想法是客户端只需要知道一个端点,即根端点。这样做的好处是您无需将路由逻辑复制到客户端 并且 API 的版本控制变得容易得多。

根据您的示例,此根端点可能如下所示:

{
    "_links": {
      "products": {
        "href": "http://localhost:4444/api/products",
      }
    }
}

2。抽象主状态

接下来定义一个位于状态层次结构顶部的抽象超级状态。我通常将此状态命名为 main 以避免与 root 端点混淆。这个超状态的任务就是解析根端点,比如:

$stateProvider.state('main', {
    resolve: {
        root: function($http) {
         return $http.get("http://localhost:4444/api/").then(function(resp){
              return resp.data;
         });
        }
    }
});

3。产品概览状态作为主要状态的子状态

然后创建一个 products 状态,它是主状态的后代。因为根端点已经解析,我们可以在此状态下使用它来将 link 获取到产品 API:

 $stateProvider.state('products', {
    parent: 'main',
    url: "/products",
    resolve: {
        products: function($http, root) {
         return $http.get(root._links.products.href).then(function(resp){
              return resp.data;
         });
        }
    }
});

4。作为产品状态子级的产品详细信息状态

最后,创建一个产品详细信息状态作为上述产品状态的子状态。通过 $stateParams 传递产品的 ID(因此,它是客户端 URI 的一部分)并使用它从父产品数组中检索正确的产品:

$stateProvider.state('products.product', {
    url: "/{productId}"
    resolve: {
        product: function($http, $timeout, $state, $stateParams, $q products) {
         var productId = parseInt($stateParams.productId);
         var product;

        for (var i = 0; i < products.length; i++) {
            product = products[i];
            if (product.id === productId) {
                return $http.get(product._links.self.href).then(function(response){
                    return response.data;
                });
                }
        }

        // go back to parent state if no child was found, do so in a $timeout to prevent infinite state reload
        $timeout(function(){
            $state.go('^', $stateParams);
        });

        // reject the resolve
        return $q.reject('No product with id ' + productId);
    }
});

您可以将上面的代码移动到一个服务中,使您的状态更轻量级。

希望对您有所帮助!