a.k.a. “Modifying the behavior of AngularJS’ core services”

After using Batarang, I wondered how they are able to collect performance metrics at this level of detail. Thoughts full of horrible code rushed to my mind. I opened the source code in fear, always ready to close it as fast as possible. After some reading, I discovered these lines:

$provide.decorator('$rootScope', function ($delegate) {

    // ...

    $delegate.__proto__.$watch = function (watchExpression, applyFunction) {
    	// ...
    };

    // ...

    return $delegate;
});

Where once was fear there is now only awesome! I just discovered another well-architected feature of AngularJS, which – unfortunately – isn’t well documented.

Meet the decorator pattern

In software design, a decorator is an object that modifies the behaviour of another, already existing object without touching its code. This is achieved by wrapping either the whole object or just parts of it and then using the wrapped object instead of the original one.

Conceptual view of the decorator pattern

Decorators in Angular

Let’s create a basic AngularJS decorator to modify the behavior of the popular ngResource service:

var API_PREFIX = '/api/v1';

angular.module('ngResource+apiPrefix', [
    'ngResource'
]).config(function ($provide) {
	$provide.decorator('$resource', function ($delegate) {
	
		// Return a new constructor function that prepends an API
		// prefix to the passed resource URL (first parameter).
    	return function decoratedResource() {
        	arguments[0] = API_PREFIX + arguments[0];
	        return $delegate.apply(this, arguments);
    	};
    
	});
});

This code will prepend '/api/v1' to all your $resource REST urls. Therefore, when you define new models with $resource('/model'), they will use '/api/v1/model' instead.

To accomplish that, we create a new module named ngResource+apiPrefix that depends on ngResource. We call $provide.decorate() and pass in the name of the service we want to decorate, along with a function whose parameters are going to be injected.

The $delegate argument is resolved with an instance of the specified service. We now replace the original $resource function with a new one that does three things:

  1. Prepend a common API_PREFIX to the resource url.
  2. Invoke the original $resource function with the modified arguments
  3. Return the original object

In order to load this decorator, we would have to load the module:

var app = angular.module('app', ['ngResource+apiPrefix']);

From now on, all instances of $resource are decorated. There’s no way to access the original $resource service again.

Syntactic Sugar

The snippet above is a little verbose, because it has some boilerplate code in it. We can get rid of that by using the angular-decorate plugin.

var API_PREFIX = '/api/v1';

angular.module('ngResource+apiPrefix', [
    'ngResource'
]).decorate('$resource', function ($delegate) {
	
    return function decoratedResource() {
        arguments[0] = API_PREFIX + arguments[0];
        return $delegate.apply(this, arguments);
    };
    
});

Simple, isn’t it?

By the way, the plus sign in the module name is a convention that orginates in ObjectiveC and helps to identify the intention of a decorator.

Angular decorate from Damien Klinnert