Uncategorized
Visual Studio Online is now Visual Studio Team Services Jesse Houwing 23 Nov, 2015
"A service decorator intercepts the creation of a service, allowing it to override or modify the behaviour of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service."Nothing to add there. You can use decorate() to change, add to, or completely replace the behaviour of services without having to edit its code. This can be done on any code not your own - core AngularJS services, but also third-party libraries. It's the equivalent of overriding methods in OO languages or monkey-patching in the more dynamic languages. “Isn’t that evil?”, I hear you ask. As with every programming-related question, the only correct answer is: it depends. I’m going to give a few practical examples of when I believe using decorate() is appropriate. In a future blog post, I'll expand on this example, showing how relatively simple code can positively influence your entire application architecture. Here’s a practical example, a neat follow-up on my previous blog about angular promises: decorating $q to add methods to the promise object. The promise API itself defines only one method: then(). $q adds a few simple methods to that like catch() and finally(), but for your own application you can add a few more. If you’ve been working with promises for a little while in your AngularJS application, you’ve probably noticed some operations are pretty common; assigning the promise result to the scope (or any object), logging the result in the console, or calling some other method. Using decorate(), we can add methods to the promise object to simplify those. Here's a bit of code from my previous post; we'll add a method to $q to remove the need for a callback: [javascript] CustomerService.getCustomer(currentCustomer) .then(CartService.getCart) .then(function(cart) { $scope.cart = cart; }) .catch($log.error); [/javascript] First, we’ll need to do some boilerplate: we create a function that adds our methods to the promise object, and then we replace all the default promise methods. Note that the decorating function will also apply itself to the given promise.then method again, so that our customisations aren’t lost further down a promise chain: [javascript] angular.module('ngPromiseDsl', []) .config(function ($provide) { $provide.decorator('$q', function ($delegate, $location) { // decorating method function decoratePromise(promise) { var then = promise.then; // Overwrite promise.then. Note that $q's custom methods (.catch and .finally) are implemented by using .then themselves, so they're covered too. promise.then = function (thenFn, errFn, notifyFn) { return decoratePromise(then(thenFn, errFn, notifyFn)); }; return promise; } // wrap and overwrite $q's deferred object methods var defer = $delegate.defer, when = $delegate.when, reject = $delegate.reject, all = $delegate.all; $delegate.defer = function () { var deferred = defer(); decoratePromise(deferred.promise); return deferred; }; $delegate.when = function () { return decoratePromise(when.apply(this, arguments)); }; $delegate.reject = function () { return decoratePromise(reject.apply(this, arguments)); }; $delegate.all = function () { return decoratePromise(all.apply(this, arguments)); }; return $delegate; }); }); [/javascript] With that boilerplate in place, we can now start adding methods. As I mentioned earlier, one of the most common uses of a then() function is to set the result onto the scope (or some other object). This is a fairly trivial operation, and it’s pretty straightforward to add it to the promise object using our decorator, too: [javascript]function decoratePromise(promise) { var then = promise.then; promise.then = function (thenFn, errFn, notifyFn) { return decoratePromise(then(thenFn, errFn, notifyFn)); }; // assigns the value given to .then on promise resolution to the given object under the given varName promise.thenSet = function (obj, varName) { return promise.then(function (value) { obj[varName] = value; return value; }); }; return promise; } [/javascript] That’s all. Put this .config block in your application's module definition, or create a new module and add a dependency to it, and you can use it throughout your application. Here's the same piece of code, now with our new thenSet method: [javascript] CustomerService.getCustomer(currentCustomer) .then(CartService.getCart) .thenSet($scope, 'cart') .catch($log.error); [/javascript] This particular example can be extended in a multitude ways to add useful utilities to promises. In my current project we’ve added a number of methods to the promise object, which allows us to reduce the number of callback definitions in our controllers and thus create cleaner, more legible code. Replacing custom callbacks with named methods allows for a more functional programming style, and allows readers to read and write code as a list of “whats”, instead of “hows” - and it's also fully asynchronous. Extending $q is just the start though: Every angular service can be extended for various purposes - add performance monitoring and logging to $http, set common prefixes or fixed properties on $resource urls or template paths, you name it. Leave a remark in the comments about how you've used decorate() to create a better application. Stay tuned for an upcoming post where I release a small open source project that extends angular’s promise objects with a number of helpful methods to perform common tasks. Further reading, resources, helpful links: