ngResource resolving nested resources
Asked Answered
H

2

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.

Hepatitis answered 16/10, 2013 at 14:42 Comment(0)
V
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.

Vasoconstrictor answered 16/10, 2013 at 15:11 Comment(4)
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.Hepatitis
The Restangular way seems to be: pet.owner = Restangular.restangularizeElement(pet, pet.owner, 'owners').Hepatitis
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 possibleVasoconstrictor
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.Hepatitis
H
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
Hepatitis answered 22/10, 2013 at 15:12 Comment(2)
Where's the angular module? Did you delete it?Sublime
@Sublime Fixed the link; 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. For just the core of the reference resolution logic, check out this gistHepatitis

© 2022 - 2024 — McMap. All rights reserved.