rest - operativos - que es un sistema multiprogramado
¿Cómo manejar la paginación y contar con recursos angulares? (4)
Tengo que construir un cliente angularjs para una API que genere JSON de esta manera:
{
"count": 10,
"next": null,
"previous": "http://site.tld/api/items/?start=4"
"results": [
{
"url": "http://site.tld/api/items/1.json",
"title": "test",
"description": "",
"user": "http://site.tld/api/users/4.json",
"creation_datetime": "2013-05-08T14:31:43.428"
},
{
"url": "http://site.tld/api/items/2.json",
"title": "test2",
"description": "",
"user": "http://site.tld/api/users/1.json",
"creation_datetime": "2013-05-08T14:31:43.428"
},
{
"url": "http://site.tld/api/items/3.json",
"title": "test3",
"description": "",
"user": "http://site.tld/api/users/2.json",
"creation_datetime": "2013-05-08T14:31:43.428"
}
]
}
¿Cómo puedo hacer un $resource
que se asigna a esto? Si uso isArray=false
, obtendré el blob completo como un objeto, utilizable para lectura, pero no puedo llamar a .put()
. Si uso isArray
, simplemente no funciona.
¿Hay alguna manera limpia de hacer esto? ¿O debería volver a usar $http
?
Me metí en este problema también, y esto es lo que funcionó para mí. Agregue un transformador de respuesta que tome el resultado de la matriz y cree un objeto de recursos manualmente, lo que supongo que ngResource haría internamente de todos modos.
var Plan = $resource(apiPath + ''plans/:planId/'', {planId:''@id''}, {
query: {
isArray: false,
transformResponse: function(response){
response = angular.fromJson(response);
response.results = response.results.map(function(plan) {
return new Plan(plan);
});
return response;
}
},
});
Por ahora esto es aún más antiguo, pero logré resolverlo en una única fábrica de recursos:
.factory(''BeerResourceFactory'', function($resource, API_URI) {
var resource = $resource(API_URI + ''/beer/:id'',
{''id'': ''@id''},
{
''update'': {method: ''PUT''},
''query'' : {method: ''GET'', isArray:true, transformResponse : function(data) {
var jsonData = angular.fromJson(data);
jsonData.beers = jsonData.beers.map(function (beer) {
return new resource(beer)
});
return jsonData.content;
}}
});
return resource;
})
Sé que esta pregunta es un poco vieja. Pero creo que la respuesta no cubre el problema principal: cómo obtener información de paginación y cómo mantener las características de Recursos para la lista de objetos.
Básicamente, tiene dos soluciones, pase los datos del paginador a los encabezados en el mensaje de transformación o use $ http y luego cree una instancia de los elementos manualmente.
1. mensaje de transformación
Aquí redefino la consulta para poner los datos de paginación en los encabezados.
Los encabezados no son una matriz, es "headersGetter" que devuelve el encabezado llamando a los encabezados (''Header-Name'') y devuelve el objeto interno al llamar a los encabezados (). Tengo que configurar el encabezado en minúsculas.
var PAGINATION_TOTAL = ''pagination-total-elements'';
var PAGINATION_SIZE = ''pagination-size'';
...
.factory(''BeerResourceFactory'', function($resource, API_URI) {
return $resource(API_URI + ''/beer/:id'',
{''id'': ''@id''},
{
''update'': {method: ''PUT''},
''query'' : {method: ''GET'', isArray:true, transformResponse : function(data, headers) {
var jsonData = JSON.parse(data);
headers()[PAGINATION_TOTAL] = jsonData.totalElements;
headers()[PAGINATION_SIZE] = jsonData.size;
return jsonData.content;
}}
});
})
Después de eso, defino el servicio que encapsula esto y tomo la paginación de los encabezados. De repente, no podemos usar $ promise.then () y devolver el resultado porque la promesa solo se obtiene como argumento y no los encabezados, así que tenemos que usar la devolución de llamada ordinaria y crear una promesa propia.
.service(''beerService'', function(BeerResourceFactory, $q) {
this.query = function(filter) {
var defer = $q.defer();
BeerResourceFactory.query(filter, function(result, headers) {
var promiseResult = {
beers: result,
paging: {
totalItems: headers(PAGINATION_TOTAL),
perPage: headers(PAGINATION_SIZE)
}
};
defer.resolve(promiseResult);
});
return defer.promise;
}
2.Utilizando $ http y instanciar Recurso
Cuando usa $ http en lugar de recurso, hay un problema que aún desea usar elementos de la matriz como instancias de recursos y poder llamar a $ save / $ delete, por lo que es posible crear una instancia de ellos manualmente. Aquí también puedes usar la promesa ordinaria como ussual.
.service(''beerService'', function($http, BeerResourceFactory, API_URI) {
this.query = function(filter) {
return $http.get(API_URI + ''/beer'', {params: filter})
.then(function(response) {
var transformedList = response.data.content.map(function(element) {
return new BeerResourceFactory(element);
});
return {
beers: transformedList,
paging: {
totalItems: response.data.totalElements,
perPage: response.data.size
}
};
});
};
Preferiría la segunda solución, porque es más simple.
Tienes unas cuantas opciones. Si puede cambiar la salida del servidor, podría agregar la meta información (conteo, siguiente, anterior) como valores de encabezado en lugar de agregarlos en el cuerpo de la respuesta.
Su segunda opción es transformar la respuesta con transformResponse. Esto está disponible como una configuración $ response en angular v1.1.2 y posterior (la rama inestable):
var Data = $resource(''./data.json'',{},{
list:{isArray:true,method:''get'',
transformResponse: function (data, headers) {
return JSON.parse(data).results;
}}
});
Si no desea utilizar la rama inestable, también es posible cambiar el $ http que usa $ resource:
$http.defaults.transformResponse.push(function(data){
if(data && data.results){
return data.results;
}
});
He creado un plunker con ambos ejemplos: http://plnkr.co/edit/PmYotP0eo5k41Z6ZCxl4?p=preview
No estoy seguro de cuál es el mejor método para transmitir los metadatos al resto de su aplicación (si lo necesita). Puede adjuntarlo al primer resultado, o agregarlo como un objeto separado, tal vez no tan elegante, pero hará el trabajo.