5

The back-end team I am working with implemented a REST API which returns collections as object (instead of array). For example, calling GET /some_resources would return:

{
  id_1: {
          p1: "lorem",
          p2: 4 
        }, 
  id_2: {
          p1: "ipsum", 
          p2: 2
        }, 
  id_n: {
          p1: "sic", 
          p2: 7
        }
}

This structure is limiting to work with in Angular as you can't leverage the ng-repeat directive for instance (e.g orderBy and filter do not work with object).

For the previous example, ideally, I would rather expect an array of objects:

[ 
  {id: "id_1", p1: "lorem", p2: 4},
  {id: "id_2", p1: "ipsum", p2: 2},
  {id: "id_n", p1: "sic", p2: 7}
]

This would allow to directly work with the object returned by the ngResource service and sent it back without touching its structure. It would also allow to fully use the ng-repeat directive.

When I asked the back-end team to change the structure of their response of collections from object to array, they argued that the API consumer should not lead the design of the structure for the response.

I partially agree with their argument, so for now, the only solution I found consist in transforming the object into an array, work with this object into my scope and retransform the array back to the original object structure, before sending it back to the API.

This leads to a lot of code boilerplate and I am wondering if there is an "angular way" to deal with this situation.

Eturcim
  • 798
  • 8
  • 26
  • 1
    If you're using ngResource, you can set request and response transformers to take care of the data manipulation transparently – Phil Jun 06 '16 at 02:34
  • Ok, I see. But I would have to implements the transformers for each case right ? I mean, there is no generic way to do ? Transformers are interesting though, as they reduce the amount of code in my controllers, which are currently performing the transformations. – Eturcim Jun 06 '16 at 04:28
  • You can re-use the transformer if all you're doing is converting to an array and adding the object key as `id` – Phil Jun 06 '16 at 04:40
  • Yes, except that in some case, there are several nesting levels, like { id_1: { nested_collection: { key_1: "...", key_2: "..." }, id_2: {key_1: "...", key_2: "..."}}. In other case, it's just as simple as the example in my question. I my reuse some common code though. – Eturcim Jun 06 '16 at 13:59

4 Answers4

7

"they argued that the API consumer should not lead the design of the structure for the response"

Argue back - If the only consumer of the API is the front end currently, then they have time to put it in the proper format before other consumers are added. That data is perfectly suited to be an array instead of an object. I don't know how big your data set could potentially be but one thing you don't want to be doing in the future is manipulating objects - large or small - on the front end. Browsers have limited resources, servers can always have resources added.

Brant
  • 1,764
  • 11
  • 18
  • "_If the only consumer of the API is the front end currently_". That's unfortunatly (for me) not the case. – Eturcim Jun 06 '16 at 04:02
3

Angular JS's ng-repeat don't work well with objects, and collections should be arrays.

Your API Team is wrong, they are not building a RESTful API if they do not implement it the way the community use and create it. APIs should be built for the benefit of the consumer, it is an Interface just as like UI is built for the users.

And you are right to argue that it should be an array instead of an object. Here's one way of designing an API that is built by a community of experts: ebook

There are so many libraries that already work with RESTful APIs very well, if your backend will not implement the API correctly, you won't be able to use those great libraries created by the community. Plus, your future consumers will have a hard time using the API and will avoid using your product as a third party app

Louie Almeda
  • 5,366
  • 30
  • 38
  • 1
    "_Your API Team is wrong, they are not building a RESTful API_". According to who ? To be honest, it's the kind of argument I opposed to them. But their response is that they did not find anything mentioning that one should not return object instead of array for collection. And their are true in way, that it's Angular who'is not playing well in that situation, but you'll not find something (on the web or in the literature), saying that you should prefer an array instead of an object. – Eturcim Jun 06 '16 at 04:18
  • 1
    @Mic what they're returning is a serialized `Map` which is **not** a `Collection`. They should be returning an array – Phil Jun 06 '16 at 04:53
  • @Phil again, that just an opinion. They should return an array, because angular and other framework work this way. But maybe, with ES6 bringing the Map object, future framework will use Map over Array. – Eturcim Jun 06 '16 at 14:37
  • 1
    @Mic, I agree, most of popular APIs return an array for a getAll, (github, facebook, twitter) though some of them returns an object but the list of items itself inside it is still an array, Plus, ASP.net's web API defaults to have a list of objects returned for get all, I'm not sure with other frameworks but I bet It will be similar – Louie Almeda Jun 07 '16 at 03:11
  • I would agree that in @phil comment "They should be returning an array" is an opinion, but the point about a Map VS a Collection is on point. – Native Coder Sep 17 '20 at 16:09
1

Update

let resource = {
  id_1: {
          p1: "lorem",
          p2: 4 
        }, 
  id_2: {
          p1: "ipsum", 
          p2: 2
        }, 
  id_n: {
          p1: "sic", 
          p2: 7
        }
}
let model = [];

Object.keys(resource).forEach(
    key=>{
        let o = resource[key];
        o['id'] = key;
        model.push(o)
    }
);

After applying this to your resource, the output will be an array, with keys id , p1, p2 in compliance with your preferred format. Once you're done working with the model, you can get it back in its original format:

let original={};
model.forEach(o => original[o.id] = {p1:o.p1, p2:o.p2});

Original Answer

I don't know of an "Angular way". I use the code below to handle similar situations. This is typescript but could easily be adapted to javascript. It was inspired by David Sherret's code

getObjValues(e: any) {
    return Object.keys(e).map(k => e[k]);
}

let resource = {
  id_1: {
          p1: "lorem",
          p2: 4 
        }, 
  id_2: {
          p1: "ipsum", 
          p2: 2
        }, 
  id_n: {
          p1: "sic", 
          p2: 7
        }
}

let model = getObjValues(resource)

model will be an array of elements with keys p1 and p2. If you need to recover the original object you'll need more code though both to save the initial id_1, id_2 and to reconstruct the original object.

Community
  • 1
  • 1
BeetleJuice
  • 39,516
  • 19
  • 105
  • 165
  • OP needs the `id` in there too – Phil Jun 06 '16 at 02:35
  • @Patrick, to clarify, I am not asking how to implement the transformation obj <-> array. I am asking, how a framework like Angular (or the community using it) suggests to deal with this kind of situation (having objects instead array for collection). – Eturcim Jun 06 '16 at 04:13
0

If your use case is really simple (if the order of objects within the array doesn't matter), you can use ng-repeat to iterate over object keys according to the docs

<div ng-repeat="(id, value) in myObj"> 
    {{id}} - {{value.p1}} - {{value.p2}} 
</div>
BeetleJuice
  • 39,516
  • 19
  • 105
  • 165
  • 1
    No, like I said, I would like to leverage all the power of ng-repeat, especially orderBy and filter filters. Having an object do not allow me to do so, hence the necessity to convert it into an array. – Eturcim Jun 06 '16 at 04:23