Decorators are a design pattern that is used to separate modification or decoration of a class without modifying the original source code. In AngularJS, decorators are functions that allow a service, directive or filter to be modified prior to its usage.
There are two ways to register decorators
- $provide.decorator, and
- decorator
Each provide access to a $delegate, which is the instantiated service/directive/filter, prior to being passed to the service that required it.
$provide.decorator
The decorator function allows access to a $delegate of the service once it has been instantiated. For example:
angular.module(‘myApp’, [])
.config([ ‘$provide’, function($provide) {
$provide.decorator(‘$log’, [
‘$delegate’,
function $logDecorator($delegate) {
var originalWarn = $delegate.warn;
$delegate.warn = function decoratedWarn(msg) {
msg = ‘Decorated Warn: ‘ + msg;
originalWarn.apply($delegate, arguments);
};
return $delegate;
}
]);
}]);
After the $log service has been instantiated the decorator is fired. The decorator function has a $delegate object injected to provide access to the service that matches the selector in the decorator. This $delegate will be the service you are decorating. The return value of the function provided to the decorator will take place of the service, directive, or filter being decorated.
The $delegate may be either modified or completely replaced. Given a service myService with a method someFn, the following could all be viable solutions:
Completely Replace the $delegate
angular.module(‘myApp’, [])
.config([ ‘$provide’, function($provide) {
$provide.decorator(‘myService’, [
‘$delegate’,
function myServiceDecorator($delegate) {
var myDecoratedService = {
// new service object to replace myService
};
return myDecoratedService;
}
]);
}]);
Patch the $delegate
angular.module(‘myApp’, [])
.config([ ‘$provide’, function($provide) {
$provide.decorator(‘myService’, [
‘$delegate’,
function myServiceDecorator($delegate) {
var someFn = $delegate.someFn;
function aNewFn() {
// new service function
someFn.apply($delegate, arguments);
}
$delegate.someFn = aNewFn;
return $delegate;
}
]);
}]);
Augment the $delegate
angular.module(‘myApp’, [])
.config([ ‘$provide’, function($provide) {
$provide.decorator(‘myService’, [
‘$delegate’,
function myServiceDecorator($delegate) {
function helperFn() {
// an additional fn to add to the service
}
$delegate.aHelpfulAddition = helperFn;
return $delegate;
}
]);
}]);
Note that whatever is returned by the decorator function will replace that which is being decorated. For example, a missing return statement will wipe out the entire object being decorated.
Decorators have different rules for different services. This is because services are registered in different ways. Services are selected by name, however filters and directives are selected by appending “Filter” or “Directive” to the end of the name. The $delegate provided is dictated by the type of service.
Service Type | Selector | $delegate |
Service | serviceName | The object or function returned by the service |
Directive | directiveName + ‘Directive’ | An Array.<DirectiveObject>* |
Filter | filterName + ‘Filter’ | The function returned by the filter |
* Multiple directives may be registered to the same selector/name
module.decorator
This function is the same as the $provide.decorator function except it is exposed through the module API. This allows you to separate your decorator patterns from your module config blocks.
Like with $provide.decorator, the module.decorator function runs during the config phase of the app. That means you can define a module.decorator before the decorated service is defined.
Since you can apply multiple decorators, it is noteworthy that decorator application always follows order of declaration:
- If a service is decorated by both $provide.decorator and module.decorator, the decorators are applied in order:
angular
.module(‘theApp’, [])
.factory(‘theFactory’, theFactoryFn)
.config(function($provide) {
$provide.decorator(‘theFactory’, provideDecoratorFn); // runs first
})
.decorator(‘theFactory’, moduleDecoratorFn); // runs seconds
- If the service has been declared multiple times, a decorator will decorate the service that has been declared last:
angular
.module(‘theApp’, [])
.factory(‘theFactory’, theFactoryFn)
.decorator(‘theFactory’, moduleDecoratorFn)
.factory(‘theFactory’, theOtherFactoryFn);
// `theOtherFactoryFn` is selected as ‘theFactory’ provider and it is decorated via `moduleDecoratorFn`.