How to create (singleton) AngularJS services in 4 different ways

Emil

Next to creating controllers and directives, AngularJS also supports “singleton” services. Services, like on the server-side, offer a great way for separating logic from your controllers. In AngularJS anything that’s either a primitive type, function or object can be a service. Although the concept of service is quite straight forward, the declaration of them in AngularJS isn’t:

  • There are 4 different ways to declare a service.
    • Registering a existing value as a service
    • Registering a factory function to create the singleton service instance
    • Registering a constructor function to create the singleton service instance
    • Registering a service factory which can be configured
  • Only 1 of them is extensively documented

The other 3 are only barely documentedThis post describes each option and when to use it in more detail.

Registering a existing value as a service

You can register an existing value as service, using the following methods of the Module type:

  • constant(name, value)“. intended for registering configuration data and should therefore only be used with primitives or object containing just data.
  • value(name, value)“: registers a primitive, existing object instance or function. The big difference between the “constant” and “value” methods is that:
    • “constant” should only be used for… constants.
    • services registered with “value” as well as the 3 other ways can even be proxied.

Consider the following example using both styles:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var app = angular.module('myApp', []);

app.constant('magicNumber', 42);
app.constant('bookTitle', "Hitchhiker's Guide");

function UsingConstantServiceCtrl(magicNumber, bookTitle) {
$scope.magicNumber = magicNumber;
$scope.bookTitle = bookTitle;
}

(function({
  var existingServiceInstance = {
    getMagicNumber: function({
      return 42// Note that we are using an "hard-coded" magic number
    }
  };

  app.value('magicNumberService', existingServiceInstance);
}());

function UsingValueServiceCtrl(magicNumberService{
  $scope.magicNumberFromService = magicNumberService.getMagicNumber();
}

Registering a factory function to create the singleton service instance

Instead of supplying an existing value, you could also register a factory function for the service. However since services in AngularJS are “singletons” the factory function is invoked once. A factory function can optionally take parameters which, just like controllers and directives, which will be injected by AngularJS. Using the earlier registered magicNumber ‘service’, you can now declare a service using the “factory(name, providerFunction)” (extensively documented) method:

1
2
3
4
5
6
7
8
9
10
(function({
// registers a service factory with "magicNumber" injected
  app.factory('magicNumberService'function(magicNumber{
    return {
getMagicNumber: function({
       return magicNumber;
    }
};
});
}());

Registering a constructor function to create the singleton service instance

Instead of registering a factory function that returns an instance of a service, you could also register a constructor function for your service object using the “service(name, constructor)” method:

1
2
3
4
5
6
7
8
9
(function({
  var MyService = function(magicNumber// "magicNumber" is injected
    this.getMagicNumber = function({
      return magicNumber;
    };
  };

  app.service('magicNumberService', MyService);
}());

Registering a service factory which can be configured

Last but not least… there is way more advanced way to register a service factory using “provider(name, providerType)” which can be configured used the “Module#config(configFn)” . Using the “provider(name, providerType)” you can register a so-called “providerType”. This “providerType” can be either an existing object or a constructor function containing:

  • any number of configuration methods (i.e. “setMagicNumber”)
  • a “$get” factory function just like the one used with “factory(name, providerFunction)

Using “provider(name, providerType)” we can make our service configurable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
app.provider('magicNumberService', {
  // internal configuration data; configured through setter function
  magicNumber: null,

  // configuration method for setting the magic number
  setMagicNumber: function(magicNumber{
    this.magicNumber = magicNumber;
  },

  $get: function(magicNumber) {
// use the magic number explicitly provided through "setMagicNumber" or
// otherwise default to the injected "magicNumber" constant
    var toBeReturnedMagicNumber = this.magicNumber || magicNumber;

    // return the service instance
    return {
      getMagicNumber: function({
        return toBeReturnedMagicNumber;
      }
    };
  }
});

To allow configuration each service registered with ”provider(name, providerType)” automatically gets a “special” additional service with the “Provider” postfix. This special “…Provider” service (i.e. “magicNumberServiceProvider”) can be used solely in combination with “Module#config(configFn)” to configure the service prior to its construction:

1
2
3
app.config('magicNumberServiceProvider'function({
  magicNumberServiceProvider.setMagicNumber(99);
});

When should you use which way?

There is no right or wrong when it comes the various way in which you can register a service. Some might argue that “factory(name, providerFunction)” would be preferable since it’s extensively documented. Others would prefer registering existing service instances using “value(name, value)“. To ease choosing between the 4 different ways I will shortly recap all of them:

  • Use either “value(name, value)” or “constant(name, value)” to register an existing value:
    • you typically would use “value” to register a service object or a function
    • whereas “constant” should only be used for configuration data
  • factory(name, providerFunction)“: registers a factory function responsible for creating the “singleton” service instance. Just like “value(name, value)” a factory function could return anything from primitive type, function or object instance.
  • service(name, constructor)“: registers the constructor function which will be constructed using a “new” keyword.
  • provider(name, providerType)“: the most advanced way to register a service. Most often there is no need for your service to be configurable through a “…Provider” service, unless you are writing a reusable JavaScript library containing AngularJS services.

References

JavaScript: console logging (with IE safety) Easy installation of Karma (Testacular) test runner on Windows