Decorators are a design pattern that is used to separate modification or decoration of a class without modifying the original source code. In Angular, 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
, andmodule.decorator
Each provide access to a $delegate
, which is the instantiated service/directive/filter, prior to being passed to the service that required it.
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:
angular.module('myApp', []) .config([ '$provide', function($provide) { $provide.decorator('myService', [ '$delegate', function myServiceDecorator($delegate) { var myDecoratedService = { // new service object to replace myService }; return myDecoratedService; } ]); }]);
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; } ]); }]);
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; } ]); }]);
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> 1
|
Filter | filterName + 'Filter' | The function returned by the filter |
1. Multiple directives may be registered to the same selector/name
$delegate
for the service. Not only should expectations for the consumer be kept, but some functionality (such as directive registration) does not take place after decoration, but during creation/registration of the original service. This means, for example, that an action such as pushing a directive object to a directive $delegate
will likely result in unexpected behavior. Furthermore, great care should be taken when decorating core services, directives, or filters as this may unexpectedly or adversely affect the functionality of the framework. 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. The main caveat here is that you will need to take note the order in which you create your decorators.
Unlike in the module config block (which allows configuration of services prior to their creation), the service must be registered prior to the decorator (see Provider Recipe). For example, the following would not work because you are attempting to decorate outside of the configuration phase and the service hasn't been created yet:
// will cause an error since 'someService' hasn't been registered angular.module('myApp').decorator('someService', ...); angular.module('myApp').factory('someService', ...);
The following sections provide examples each of a service decorator, a directive decorator, and a filter decorator.
This example shows how we can replace the $log service with our own to display log messages.
Failed interpolated expressions in ng-href
attributes can easily go unnoticed. We can decorate ngHref
to warn us of those conditions.
Let's say we have created an app that uses the default format for many of our Date
filters. Suddenly requirements have changed (that never happens) and we need all of our default dates to be 'shortDate'
instead of 'mediumDate'
.
© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://code.angularjs.org/1.5.11/docs/guide/decorators