12

I am using AngularJS $resource model to REST API. I have got something like this:

angular.module('libraryapp')
    .factory('Book', function($resource){
        return $resource('books/:id');
    });

I am using in these way:

Book.get({ id: 42 }, function(book) {
    console.log(book);
});

But I also want an endpoint to a subresource, let's say:

GET /books/:id/comments

How should I define it in module? May I extend Book in some way, to use it like this

Book.get({ id: 42 }).Comment.query(function(comments) {
    console.log(comments);
});
Dariusz Mydlarz
  • 2,940
  • 6
  • 31
  • 59

4 Answers4

8

You can easily reach nested RESTful resources with AngularJS $resource definitions.

The clue is to understand how the params parameter of each action definition (in the list of actions) in the $resource definition works. As the documentation says, it's an

Optional set of pre-bound parameters for this action. […]

angular.module('libraryApp').factory('Book', [
  '$resource', function($resource) {
    return $resource('books/:id/:subResource', {}, {
      comments: {  // The `comments` action definition:
        params: {subResource: 'comments'},
        method: 'GET'
      }
    });
  }
]);

Given the above definition, you should still be able to use Book as before. For example Book.get({ id: 42 }) translates to a GET books/42/ request.

However, given the new :subResource part of the $resource URL ('books/:id/:subResource'), you now can generate a

GET books/42/comments

request by calling either Book.get({ id: 42, subResource: 'comments' }) or the much more short and elegant interface Book.comments({ id: 42 }) defined as your comments action.

Martin Thorsen Ranang
  • 2,394
  • 1
  • 28
  • 43
  • I know I answered this late, but if the answer works for you, it would be great if you accept it as the answer – so that other users can benefit from knowing that the answer works. If you had any issues, please let me know. – Martin Thorsen Ranang Dec 08 '15 at 20:53
  • 2
    The problem with this solution is that returned comments will be instances of Book resource. So if we call `Book.comments({ id: 42 }).$promise.then(function (comments) { comments[0].$get(); });` then this `$get` will try to `GET /books/` and this is of course is not right. – Ruslan Stelmachenko Jan 20 '16 at 18:43
  • djxak, Do you have an alternative to this problem? – Jens Alenius Mar 30 '16 at 07:01
2

As far as I know, you can't nest resources, but it's pretty simple to do what you're looking for:

You can define optional parameters which you can override in each resource (like category here) or even override the url (look at the otherUrl resource)

angular.module('libraryApp').factory('Book', [
  '$resource', function($resource) {
    return $resource('books/:id/:category', {}, {
      comments: {
        method: 'GET',
        action: 'category'
      },
      otherUrls: {
        method: 'GET',
        url: 'books/:id/admin/:option'
      }
    });
  }
]);
kumarharsh
  • 18,961
  • 8
  • 72
  • 100
2

You may want to use Restangular instead as it handles nested resources and a clean and easy way.

map7
  • 5,096
  • 6
  • 65
  • 128
0

As djxak pointed out, adding actions to the resource means that the returned value is the containing resource type, not the sub-resource type.

I solved a similar problem by creating a new resource with the sub-resource URL and modifying the prototype of the containing resource to add a function:

angular.module('libraryapp')
    .factory('Book', function($resource){
        var bookUrl = 'books/:id',
            Book = $resource(bookUrl),
            BookComment = $resource(bookUrl + /comments");

        Book.prototype.getComments = function () {
            return BookComment.query({id: this.id});
        };

        return $resource('books/:id');
    });

The usage then becomes:

Book.get({ id: 42 }).getComments(function(comments) {
    console.log(comments);
});

The only downside I see with this approach is that if you have a separate "Comment" resource that is accessed via a different URL, you have to duplicate the $resource initialisation code for the alternative endpoint. This seems a minor inconvenience though.

tvStatic
  • 921
  • 1
  • 9
  • 26