Using Module.decorator() In AngularJS 1.4

In the past, we've been able to decorate AngularJS services by using the $provide service in the configuration phase of the application bootstrap. As of AngularJS 1.4, however, the concept of a decorator has been promoted to the Module API, finally living alongside .service(), .factory(), .run(), and the rest of the module methods. And, while this isn't truly new functionality, it does come with a caveat.

Run this demo in my JavaScript Demos project on GitHub.

I really like the idea of promoting the decorator functionality to the module-level because it allows us to isolate the act of decorating. Previously, decoration was just mixed into the general configuration phase and it was, therefore, left up to the developer to decide whether or not it should get its own configuration block; or, if it should be right along side the configuration for, say, $http interceptors. By adding a .decorator() method to the Module, it points the developer in the direction of isolating decorators within their own blocks, the same way we do most everything else in AngularJS.

With this change, however, there is a small caveat that didn't exist before: the service that you are decorating has to be defined before you try to decorate it. Previously, decorators were hidden behind the "configuration phase," which was queued up internally to execute after all of your AngularJS services had already been defined. As such, the order of module method invocation didn't matter - .config() before .service(), .service() before .config() - it was all good.

Now that .decorator() is part of the module API, however, this is no longer the case. Internally, the .decorator() method depends on the $get() method of the target service, as it always did. But, now that decoration is no longer forced to be part of the configuration phase, it means that you have to be explicit in the order of operations. You have to define your target service before your .decorator() call so that the underlying $get() method is available.

I assume that this can be fixed by queuing the .decorator() calls and deferring them until the configuration phase. But, I don't have a strong enough grasp of the AngularJS bootstrap internals to try and make that happen. That said, I'd be somewhat shocked if the AngularJS team doesn't make this change in one of the upcoming dot-releases.

With that said, I put together a quick demo to showcase the Module.decorator() method. I also took this an opportunity to demonstrate that you could decorate the same service more than once. In the following code, we're decorating a simple greeting service to append more text to the return value.

<!doctype html> <html ng-app="Demo"> <head> <meta charset="utf-8" /> <title> Using Module.decorator() In AngularJS 1.4 </title> </head> <body> <h1> Using Module.decorator() In AngularJS 1.4 </h1> <p> <em>See the console</em>. </p> <!-- Load scripts. --> <script type="text/javascript" src="../../vendor/angularjs/angular-1.4.2.min.js"></script> <script type="text/javascript"> // Create an application module for our demo. angular.module( "Demo", [] ); // --------------------------------------------------------------------------- // // --------------------------------------------------------------------------- // // I run when the AngularJS application is bootstrapped. angular.module( "Demo" ).run( function runBlock( greeting ) { console.log( greeting( "Joanna" ) ); } ); // --------------------------------------------------------------------------- // // --------------------------------------------------------------------------- // // I generate a greeting for the given. angular.module( "Demo" ).factory( "greeting", function greetingFactory() { return( greeting ); // I return a greeting for the given name. function greeting( name ) { return( "Hello " + name + "." ); } } ); // --------------------------------------------------------------------------- // // --------------------------------------------------------------------------- // // I decorate the "greeting" service, altering the return value. // -- // CAUTION: Unlike all other module methods, this decorator() has to be defined // AFTER the service that it is decorating as it relies on the existence of the // $get() method on the target service. angular.module( "Demo" ).decorator( "greeting", function greetingDecorator( $delegate ) { // Return the decorated service. return( decoratedGreeting ); // I append a new message to the existing greeting. function decoratedGreeting( name ) { return( $delegate( name ) + " How are you doing?" ); } } ); // I decorate the "greeting" service, altering the return value. // -- // NOTE: I am purposefully not combining this with the .decorator() above in // order to demonstrate that you can decorate the same service more than once. // -- // CAUTION: Unlike all other module methods, this decorator() has to be defined // AFTER the service that it is decorating as it relies on the existence of the // $get() method on the target service. angular.module( "Demo" ).decorator( "greeting", function greetingDecorator( $delegate ) { // Return the decorated service. return( decoratedGreeting ); // I append a new message to the existing greeting. function decoratedGreeting( name ) { return( $delegate( name ) + " Is there anything I can get for you?" ); } } ); </script> </body> </html>

As you can see, we are taking the greeting() service and decorating it twice, adding new text with each proxy. And, when we run the above code, we get the following console output:

Hello Joanna. How are you doing? Is there anything I can get for you?

This is a welcome change in the AngularJS API and helps us developers continue to move in the direction of small, cohesive blocks of code.

Tweet This Deep thoughts by @BenNadel - Using Module.decorator() In AngularJS 1.4 Woot woot — you rock the party that rocks the body!







