2
\$\begingroup\$

Could you please review the below AngularJS service. What it does is centralize all (relevant) HTTP action calls.

Do you see any chances for improvement?:

"use strict";

angular.module("services.api")
    .factory("apiService", [
        "$http",
        "$q",
        "coreUtilityService",
        "constantsServiceNames",
        "constantsApiRequestFor",
        function (
            $http,
            $q,
            coreUtilityService,
            constantsServiceNames,
            constantsApiRequestFor
            ) {
            // === START SERVICE === //

            var self = {};

            self.Name = constantsServiceNames.ApiService;

            self.ExecuteRequest = function(verb, fullUri, data) {
                var defer = $q.defer();
                verb = verb.toLowerCase();

                //start with the uri
                var httpArgs = [fullUri];
                if (verb.match(/post|put/)) {
                    httpArgs.push(data);
                }

                $http[verb].apply(null, httpArgs)
                .success(function (response) {
                    defer.resolve(response);
                })
                .error(function (response, status) {
                    defer.reject("HTTP Error: " + status);
                });

                return defer.promise;
            }

            /*
            Do a request to any RESTful receiver. Meant for external use only, use DoViewRequest
            for internal requests.
            */
            self.DoRequest = function (verb, uri, data) {
                var fullUrl = coreUtilityService.GetBaseUrl(constantsApiRequestFor.WebAPI, uri);

                return self.ExecuteRequest(verb, fullUrl, data);
            };

            /*
            This will target an MVC View of the client application. The url will be different.
            */
            self.DoViewRequest = function (verb, uri) {
                var fullUrl = coreUtilityService.GetBaseUrl(constantsApiRequestFor.View, uri);

                return self.ExecuteRequest(verb, fullUrl, null);
            };

            // === PUBLIC === //

            return {
                Get: function (uri) {
                    return self.DoRequest("get", uri);
                },
                Post: function (uri, data) {
                    return self.DoRequest("post", uri, data);
                },
                Put: function (uri, data) {
                    return self.DoRequest("put", uri, data);
                },
                Delete: function (uri) {
                    return self.DoRequest("delete", uri);
                },
                View: function (uri) {
                    return self.DoViewRequest("get", uri);
                }
            }

            // === END SERVICE === //
        }]);
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

A few ideas - nothing major :

  1. Encapsulating the methods in self doesn't really add anything. A set of function statements or function expressions would do the job and self. could be purged all through.
  2. The explicit promise construction antipattern should be purged.
  3. "method" would be better understood than "verb".
  4. Does DoRequest() not throw when data is not passed? Do range checking in doRequest() instead of ExecuteRequest().
  5. Possibly allow for omitted uri, defaulting to empty string (browser will use current page's url). Could be useful in some use cases.
  6. By convention Initial-caps are reserved for constructors. Change to lower-case all through.
  7. Possibly use Function.prototype.bind() in the exposer. (Check that browser compatibility is acceptable).
angular.module("services.api").factory("apiService", [
"$http",
"$q",
"coreUtilityService",
"constantsServiceNames",
"constantsApiRequestFor",
function (
    $http,
    $q,
    coreUtilityService,
    constantsServiceNames,
    constantsApiRequestFor
) {
    // === START SERVICE === //
    // var name = constantsServiceNames.ApiService; // necessary?
    var executeRequest = function(method, fullUri, data) {
        method = method.toLowerCase();
        var httpArgs = [fullUri];
        if (data) {
            httpArgs.push(data);
        }
        return $http[method].apply(null, httpArgs).then(null, function (response) {
            throw new Error("HTTP Error: " + response.status);
        });
    };
    /*
    Do a request to any RESTful receiver. Meant for external use only, use DoViewRequest
    for internal requests.
    */
    var doRequest = function (method, uri, data) {
        uri = uri || '';
        data = (!data || !method.match(/post|put/)) ? null : data; // or similar
        return ExecuteRequest(method, coreUtilityService.GetBaseUrl(constantsApiRequestFor.WebAPI, uri), data);
    };
    /*
    This will target an MVC View of the client application. The url will be different.
    */
    var doViewRequest = function (method, uri) {
        uri = uri || '';
        var fullUrl = coreUtilityService.GetBaseUrl(constantsApiRequestFor.View, uri);
        return ExecuteRequest(method, fullUrl, null);
    };
    // === PUBLIC === //
    return {
        'get':    doRequest.bind(null, 'get'),
        'post':   doRequest.bind(null, 'post'),
        'put':    doRequest.bind(null, 'put'),
        'delete': doRequest.bind(null, 'delete'),
        'view':   doViewRequest.bind(null, 'get')
    };
    // === END SERVICE === //
}]);
\$\endgroup\$
4
  • \$\begingroup\$ All very good points, thanks! A question with point 7, shouldn't there be a data parameter in the bind() for post/put? \$\endgroup\$
    – kbd
    Commented Nov 30, 2015 at 7:08
  • \$\begingroup\$ Bind statements often look wrong. I've used them quite a bit and still have to concentrate. The thing to remember is that .bind() is not a call but something that returns a function. Here, a null context is provided (doRequest() and doViewRequest() don't use this therefore don't need a context), and "get" etc are "bound-into" the returned function as its first param. Further param(s) are passed when the returned function is eventually called for real, and don't need to appear in these .bind() statements. \$\endgroup\$ Commented Nov 30, 2015 at 12:07
  • \$\begingroup\$ I'll look into it when able, thanks for the information! \$\endgroup\$
    – kbd
    Commented Nov 30, 2015 at 12:26
  • \$\begingroup\$ Minor side note regarding 6: I'm not a fan of lowerCamelCase for functions/methods, because that makes callbacks confusing. Hard to see if you're passing a variable or function. I prefer the .net naming rules. \$\endgroup\$
    – kbd
    Commented Nov 30, 2015 at 12:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.