Ever wondered about intercepting an Angular service creation to add behavior to the service instance that is being returned or replace it with something else altogether? Well, you don't have to do anything fancy for that, this mechanism is built right into Angular :). The $provide service, along with other types of providers such as factory, service, value etc, supports decorators too. While you can find decorator usage in angular-mocks.js, examples are a bit difficult to find in general. Hence this post :).

Let's consider a contrived scenario. HTML5 brings FileSystem API to browser, but support for it in browsers varies. I'm on latest Chrome (Chrome 26) and it supports it but none of the other browsers do. Let's say we need a simple data storage functionality in our Angular application which stores data in file system if it's available or in sessionStorage if it's not.

Here is the full working plunk that demonstrates decorator usage.


You'll find two providers registered in above example -

  1. storageServiceFile - Stores data in File System
  2. storageService - Stores data in sessionStorage
Now comes the decorator part. Here is how it looks like -
app.config(function($provide) {
$provide.decorator('storageService',
['$delegate', '$injector', '$rootScope', 'fileSystem',
function ($delegate, $injector, $rootScope, fileSystem) {
if (fileSystem.requestFileSystem) {
$rootScope.isFileSystemSupported = true;
return $injector.get('storageServiceFile');
} else {
$rootScope.isFileSystemSupported = false;
return $delegate;
}
}]);
});
...
...
...
app.controller('MainCtrl', ['$scope', 'storageService', function($scope, storageService) {
...
...
Points worth noting are -
  • In the config callback function, $provide service has been injected and a decorator is registered.
  • A decorator is just like any other provider. You can inject whatever than can be injected into a provider including user-defined services. In the example above, fileSystem is a user-defined service that has been injected into the decorator.
  • The $delegate argument to the decorator function is the actual service instance which it can choose to return as is, monkey-patch or replace altogether.
  • In the example above, the decorator returns instance of storageServiceFile service if FileSystem is supported by the browser else returns the original storageService instance which stores data in sessionStorage.
  • At runtime, the decorator comes into picture when Angular instantiates MainCtrl, finds out it has dependency on storageService, instantiates it and finds there is a decorator for this service and invokes it before the service instance is handed to the controller.

If you play with the plunk in latest Chrome, you can see data being stored in file system and for Firefox and others, in sessionStorage.