Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add methods to a collection returned from an angular resource query

I have a resource that returns an array from a query, like so:

.factory('Books', function($resource){
    var Books = $resource('/authors/:authorId/books');
    return Books;
})

Is it possible to add prototype methods to the array returned from this query? (Note, not to array.prototype).

For example, I'd like to add methods such as hasBookWithTitle(title) to the collection.

like image 212
Marty Pitt Avatar asked Mar 08 '13 00:03

Marty Pitt


3 Answers

The suggestion from ricick is a good one, but if you want to actually have a method on the array that returns, you will have a harder time doing that. Basically what you need to do is create a bit of a wrapper around $resource and its instances. The problem you run into is this line of code from angular-resource.js:

var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));

This is where the return value from $resource is set up. What happens is "value" is populated and returned while the ajax request is being executed. When the ajax request is completed, the value is returned into "value" above, but by reference (using the angular.copy() method). Each element of the array (for a method like query()) will be an instance of the resource you are operating on.

So a way you could extend this functionality would be something like this (non-tested code, so will probably not work without some adjustments):

var myModule = angular.module('myModule', ['ngResource']);
myModule.factory('Book', function($resource) {
    var service = $resource('/authors/:authorId/books'),
        origQuery = service.prototype.$query;

    service.prototype.$query = function (a1, a2, a3) {
        var returnData = origQuery.call(this, a1, a2, a3);
        returnData.myCustomMethod = function () {
            // Create your custom method here...
        return returnData;
        }
    }

    return service;
});

Again, you will have to mess with it a bit, but that's the basic idea.

like image 120
Jason Aden Avatar answered Nov 04 '22 16:11

Jason Aden


This is probably a good case for creating a custom service extending resource, and adding utility methods to it, rather than adding methods to the returned values from the default resource service.

var myModule = angular.module('myModule', []);
myModule.factory('Book', function() {
    var service = $resource('/authors/:authorId/books');
    service.hasBookWithTitle = function(books, title){
        //blah blah return true false etc.
    }
    return service;
});

then

books = Book.list(function(){
    //check in the on complete method
    var hasBook = Book.hasBookWithTitle(books, 'someTitle');
})
like image 26
ricick Avatar answered Nov 04 '22 17:11

ricick


Looking at the code in angular-resource.js (at least for the 1.0.x series) it doesn't appear that you can add in a callback for any sort of default behavior (and this seems like the correct design to me).

If you're just using the value in a single controller, you can pass in a callback whenever you invoke query on the resource:

var books = Book.query(function(data) {
    data.hasBookWithTitle = function (title) { ... };
]);

Alternatively, you can create a service which decorates the Books resource, forwards all of the calls to get/query/save/etc., and decorates the array with your method. Example plunk here: http://plnkr.co/edit/NJkPcsuraxesyhxlJ8lg

app.factory("Books",
  function ($resource) {
    var self = this;
    var resource = $resource("sample.json");

    return {
      get: function(id) { return resource.get(id); },
      // implement whatever else you need, save, delete etc.
      query: function() {
        return resource.query(
          function(data) { // success callback
            data.hasBookWithTitle = function(title) {
              for (var i = 0; i < data.length; i++) { 
                if (title === data[i].title) {
                  return true;
                }
              }
              return false;
            };
          },
          function(data, response) { /* optional error callback */}
        );
      }
    };
  }
);

Thirdly, and I think this is better but it depends on your requirements, you can just take the functional approach and put the hasBookWithTitle function on your controller, or if the logic needs to be shared, in a utilities service.

like image 30
Brett Avatar answered Nov 04 '22 16:11

Brett