AngularJS Abstractions: Services

At first glance, services in AngularJS are a catch-all destination for any code that doesn’t fall into one of the other primary abstractions in the framework. If it’s not a controller, filter, directive, or model,  it must be a service. One reason to think to think this way is because one can interpret the word “service” to mean anything in software. However, services in Angular have capabilities and behaviors that make them well suited for specific jobs.

Services As Application Helpers

Controllers, applications, and even other services can take a dependency on 0 or more services, meaning services can be a convenient location for code that is used in different places throughout an application. For example, if two controllers both require some algorithmic logic to make a decision, it would be better to place the logic inside a service that both controllers can use than have the code duplicated in two different controllers.

Services As Singletons

AngularJS will manage services to make sure there is only one instance of a service per application. This makes services a convenient storage location for data that needs to stick around (controllers and their models can come and go as views are loaded and unloaded in the DOM).

Services As Communication Hubs

AngularJS provides a framework to achieve a separation of concerns and a the various components have no knowledge of each other. Since services can be injected into almost anything (controllers, directives, filters, and even other services), a service can play the role of a mediator or event aggregator for other components to communicate in a loosely coupled manner.

Services As Injectable Dependencies

Perhaps the biggest reason to put code into a service is because services are injected into other components, meaning components maintain a loose coupling and any particular service can be replaced during a unit test. AngularJS itself provides many services that fall into this category. There is the $http service (for network communication),  the $window service (a wrapper for the global window object), the $log service, and others.

What Isn’t A Service?

There is plenty of vanilla JavaScript code you can write for an AngularJS application that doesn’t have to live inside an AngularJS abstraction. Model and view model definitions, for example, could live outside of any controller or service. Controllers could instantiate models, obviously, and services could hold references and vice versa. My candidates for services generally fall into one of the previous 4 categories, and as models are easy to test without test doubles, they don’t need to be injected and hence don’t need to be considered a service themselves, although it is certainly reasonable to make a service responsible for fetching a model from some unknown data source.

How To Create A Service?

One of the confusing areas of AngularJS revolves around the APIs for  creating a service, since there are no fewer than 6 ways to register a service and the obvious API choice, a method named service, probably shouldn’t be your first choice. The one that can adapt to just about any scenario is the factory method, which when used on a module object looks like something that would define a factory function for the module, but in fact is defining a factory function for a service (another unfortunate point of confusion).

As an example, here is a service to wrap the geolocation APIs of  the browser:

(function () {
 
    var geo = function ($q, $rootScope) {
 
        var apply = function () {
            $rootScope.$apply();
        };
 
        var locate = function () {
            var defer = $q.defer(); 
            navigator.geolocation.getCurrentPosition(
                function (position) {
                    defer.resolve(position);
                    apply();
                },
                function(error) {
                    defer.reject(error);
                    apply();
                });
            return defer.promise;
        };
 
        return {
            locate: locate
        };
    };
 
    geo.$inject = ["$q", "$rootScope"];
 
    angular.module("testApp")
           .factory("geo", geo);
 
}());

The factory method takes a function that AngularJS will invoke to create the service instance. This geo service makes use of the $q service provided by Angular to create promises for the asynchronous delivery of latitude and longitude. Somewhere else in the app, a controller can take a dependency on the geo service and use it to find the user’s position.

(function () {
 
    var TestController = function ($scope, geo) {
 
        geo.locate()
           .then(function (position) {
                $scope.position = position;
            });
    };
 
    TestController.$inject = ["$scope", "geo"];
 
    angular.module("testApp")
           .controller("TestController", TestController);
 
}());

Summary

Services are an important part of any AngularJS application. The built-in services in the framework make code more testable, and custom services can provide an abstraction to make code easier to maintain.