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 router
的 data
属性。但是,该页面不能添加书签。另一种方法是在 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);
}
});
您可以将上面的代码移动到一个服务中,使您的状态更轻量级。
希望对您有所帮助!
我有一个 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 router
的 data
属性。但是,该页面不能添加书签。另一种方法是在 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);
}
});
您可以将上面的代码移动到一个服务中,使您的状态更轻量级。
希望对您有所帮助!