1

We will soon be refactoring our code against the Angular Style Guide. The guide itself is great (and can be found slightly modified all over the interwebs), but no one mentions how $resource fits into a factory, or any reasons why it might have been left out. One guide says to use $resource over $http where you can, but then doesn't add it into their style for factories :/.

I remember reading in lots of places that $resource was better and that's why I started to use it, but now I'm forgetting why and wondering if that is still true - especially given the resource object at the bottom of this post. There are some opinions (Papas own, and again) about $resource (not?) being great, but that's another issue that I'm re-checking.

So, assuming we want to use $resource and given this sample code below, where does $resource fit in so that it adheres to the reasoning behind the styles in the guide? Also, if your answer is "It doesn't. The style [subtly] recommends $http because bla, bla and bla.", then that would be a useful as well.

(function() {
    'use strict';

    angular
        .module('myModule')
        .factory('oneService', oneService);

    predicateService.$inject = ['twoService', 'anotherService'];

    /* @ngInject */
    function oneService(twoService, anotherService) {
        var service = {
            doSomething: doSomething,
            etc: etc
        };

        // pos 1 (it really only works here but can be LONG)
        // var fancyResource = $resource('/path/to/thing', '...');

        // Ideally, this should be kept close to the top, right?
        return service;

        // pos 2 (here or below ////// is cleaner, but doesn't work)
        // var fancyResource = $resource('/path/to/thing', '...');

        ////////////////

        function doSomething() {}

        // rest of functions here etc...
    }
})();

Now, the only place that we use $resource (and maybe this is also incorrect) is within methods like doSomething(). At various points in the past, and even in various places in our code today, fancyResource is made public by the service and used directly from the controller: oneService.fancyResource.get(). I'm thinking this may be the intended use for $resource, but I'm not sure anymore.

Also, consider that one service might be quite large (never mind the fact that some of this should/could be broken into multiple resources; let's just pretend a resource object this size is likely and many verbs are needed):

var userResource = $resource(baseApiPath + 'users', {}, {
    get: {
        method: 'GET',
        headers: utilityService.getHeaders('sampling'),
        isArray: true,
        transformResponse: function(response){
            response = JSON.parse(response);
            if(response.result){
                return response.result.users;
            }
            return response;
        }
    },
    getUserDetails: {
        method: 'GET',
        url: baseApiPath+'users/:userId',
        params: {
            userId: '@userId'
        },
        headers: utilityService.getHeaders('sampling'),
        transformResponse: function(response){
            response = JSON.parse(response);
            if(response.result){
                return response.result.user;
            }
            return response;
        }
    },
    getUserByRole: {
        method: 'GET',
        url: baseApiPath+'users/roles/:roleId',
        params: {
            roleId: '@roleId'
        },
        headers: utilityService.getHeaders('sampling'),
    },
    getLoggedInUserData: {
        method: 'GET',
        url: baseApiPath + 'users/userData',
        headers: utilityService.getHeaders('sampling'),
    },
    getGrantedAuth: {
        method: 'GET',
        url: baseApiPath+'users/applicationPermissions/userId/:userId/:applicationId/',
        params: {
            applicationId: '@applicationId',
            userId: '@userId'
        },
        headers: utilityService.getHeaders('sampling'),
    }
});
coblr
  • 3,008
  • 3
  • 23
  • 31
  • This answer (http://stackoverflow.com/a/35212885/2800116) is helping me figure why $resource might just be plain overkill. Thinking on it, I don't even use the save/update/delete() methods it provides, and am constantly redoing the path. The last line is especially key. Regardless, I'd still like to know where a $resource object goes in the above style of factory. – coblr Feb 12 '16 at 23:37
  • seems you are overthinking things as well as creating a significant amount of code duplication in your current approach – charlietfl Feb 13 '16 at 01:43

1 Answers1

1

So, I think I've found my answer based on a few thoughts.

Firstly, I now realize that using a $resource like this is totally incorrect for two reasons. The first is that I was creating additional actions that required their own unique path. The whole point of a $resource is to make doing GET, PUT, POST, DELETE on a single REST resource easier. I was basically combining my resources because they appeared to be unified. For example, /users and /users/roles/:roleId should have been two different resources (and probably put into two different services to maintain the single responsibility style).

The second way I was using $resource wrong is actually because I wasn't really using the query, save, or delete methods that it supplies me with. I would just create another custom action for whatever I wanted to do. Sometimes this also included a unique URL like /users/:userId/delete, and that was because the API wasn't always a REST API. $resource is specifically designed for REST compliant APIs. Because it wraps $http and it can pass parameters to it, it's easy to fall into this trap. $resource is not intended to be a configuration for multiple $http uses.

So, now with that out of the way, here is how I would propose to include $resource into a factory, and still follow the style guide.

First, $resource should only be used with a true REST API. One where you only have/need one path, and only/mostly HTTP methods are used to interact with it. Also, because a factory is intended to represent and manage one kind of 'thing', interacting with the 'thing API', there should really only be one $resource per service. Extending the example, there would be a users service and a roles service; each with one $resource. There could then be another userRoleService that utilizes them both, and doesn't actually do any $resource stuff on its own. Something like that, anyway.

This being the case, the $resource config would actually be significantly shorter than what I was originally posting. Since it's smaller, we can treat it more like a variable declaration and put it above the service object that we create.

(function() {
    'use strict';

    angular
        .module('myModule')
        .factory('oneService', oneService);

    predicateService.$inject = ['anotherService', '$resource'];

    /* @ngInject */
    function oneService(anotherService, $resource) {

        // this resource is unlikely to get longer than this
        var userResource = $resource('http://api.com/users/:userId', {
            userId: '@userId'
        });

        // and we're still able to see all bindables at the top
        var service = {
            doSomething: doSomething,
            etc: etc
        };
        return service;

        ////////////////

        function doSomething() {
            // and in a service method, we can use the resource like this,
            userResource.query().$promise
            .then(function(response){...})
        }

        function doSomethingElse() {
            // or we could use the $resource in a way that would make
            // chainable with another .then() in the calling method.
            var load = userResource.query();

            // do some internal stuff when we get the response
            load.$promise
            .then(function(response){...});

            // then return the $resource object, or load.$promise
            // so that another .then can be chained afterwards.
            return load;
        }

        // rest of functions here etc...
    }
})();

Anyway, that's the answer that I came up with. I hope this helps some of you who came here looking for what I was looking for (and couldn't easily find).

coblr
  • 3,008
  • 3
  • 23
  • 31