7

What options are there there for resolving nested resources in ngResource responses?

There have been some related questions about resolving endpoints for nested resource in ngResource, but this question is about when a REST response contains a second resource nested in the collection that is being queried, especially 1-to-1 mappings where you wouldn't have e.g. pets/<id>/owner as its own resource.

Say there are two resources, Pets and Owners:

GET /pets:

[{
  name: 'spark',
  type: 'dog',
  owner: '/owners/3/' # alternatively just '3' or the full object.
}]

As a developer, I sometimes want to query the Owner resource as a whole, sometimes I want to query the Pet resource and then I automatically want to resolve the owner attribute into a resource instance.

This is my current solution:

.factory('Pet', function ($resource, Owner) {
  var Pet = $resource('/pets/:id', {id: '@id'});

  Pet.prototype.getOwner = function () {
    return new Owner(this.owner); // or Owner.get({id: this.owner})
  }

  return Pet;
})

Problems here are many. There's integrity – for one. This implementation, I believe, allows for multiple instances of the same resource. Then there's practicality. You also have additional attributes to keep track of (owner and getOwner(), instead of just owner; possibly setOwner if you want to be able to save the model).

An alternative solution could be built on transformResponse, but it would feel like a hack to include that in every resource that has a nested mapping.

lyschoening
  • 18,170
  • 11
  • 44
  • 54

2 Answers2

3

I believe this is the exact reason why Martin Gontovnikas created Restangular. He didn't like having to deal with nested $resources in the main angular framework. I think his Restangular solution would fit nicely into your needs. His code is on GitHub here and he's got a nice intro video on youtube here.

Check it out. I think you'll find it does exactly what you want it to do.

tennisgent
  • 14,165
  • 9
  • 50
  • 48
  • 3
    So what you can do in Restangular is, call `Restangular.one('pets', 1).one('owner').get()` and that loads `/pets/1/owner`.I am looking for a paradigm where there is one unique endpoint for each resource, so each owner would be accessible only from `/owners/`, but at the same time it might be possible to embed an object in a response for a different resource. – lyschoening Oct 17 '13 at 08:00
  • The Restangular way seems to be: `pet.owner = Restangular.restangularizeElement(pet, pet.owner, 'owners')`. – lyschoening Oct 17 '13 at 11:00
  • That seems more like simply two dependent resources, rather than truly "nested" resources. "nested" would mean one is accessible from within (meaning some derived url) the other. Having two separate resources (`Pet` and `Owner`) that depend on one another is different. Do you have access to the backend service? It seems like the dependencies would be better if the backend were to pre-populate the other dependency... but that might not be possible – tennisgent Oct 17 '13 at 13:39
  • 1
    I mean nested as in literally nested into the response JSON object, such as when you have a foreign key to another resource (as shown in my example above). It does not have to be a parent-child relationship. – lyschoening Oct 18 '13 at 07:57
2

Update: I ended up working on this for a bit and have started a new angular module, available on GitHub. The answer below is about the Gist I wrote originally.

There doesn't seem to be anything around there like what I have been looking for. I have started an implementation of a solution that only supports get and getList (query) operations. The remaining methods should be trivial to add since I've pretty much kept with the layout of the ngResource module. The Gist for my implementation is below.

https://gist.github.com/lyschoening/7102262

Resources can be embedded in JSON either as full objects that simply get wrapped in the correct Resource model, or as URIs, which get resolved automatically. In addition to embedded resources, the module also supports typical nested resources, either as true parent-child collections (where the resource is only accessible after selecting the parent) or as cross-referenced collection.

Yard = Resource('/yard')     # resource model
Yard.$nested('trees')        # embedded item or list of items

Chair = Resource('/chair')

Yard.$nested('/chair')      # sub-collection without its own model
                             # (for many-to-many)

Tree = Resource('/tree')

# child-collection with its own model
TreeHouse = Tree.$childResource('/treehouse')


yard = Yard.get(1)
# GET /yard/1
# {
#    "uri": "/yard/1",
#    "trees": [
#       "/tree/15",   -- reference, looked-up automatically with GET
#       {"uri": "/tree/16", "name": "Apple tree"} 
#                     -- full object, resolved to Tree instance
#    ]
# }
# GET /tree/16
# {"uri": "/tree/15", "name": "Pine tree"}

yard.chair.getList()
# GET /yard/1/chair
# [{"uri": "/chair/1", ...}, ..]
# -- model inferred from URI

yard.trees[0].treehouse.getList()
# GET /tree/15/treehouse
# [{"uri": "/tree/15/treehouse/1", ...}, ..]
# -- automatically resolved to TreeHouse instance
lyschoening
  • 18,170
  • 11
  • 44
  • 54
  • Where's the angular module? Did you delete it? – bruno Nov 12 '15 at 02:35
  • @bruno Fixed the [link](https://github.com/biosustain/angular-potion); it's not exactly the same as in the original comment as it depends on json-refs. I am not currently maintaining this any further as I am planning to rewrite it in TypeScript for Angular 2. It's designed to be used with [Flask-Potion](https://github.com/biosustain/potion). For just the core of the reference resolution logic, check out [this gist](https://gist.github.com/lyschoening/a9e94a3fc77ae571df63) – lyschoening Nov 12 '15 at 09:14