{"id":14,"date":"2015-02-25T23:24:00","date_gmt":"2015-02-25T23:24:00","guid":{"rendered":""},"modified":"2016-01-30T23:14:43","modified_gmt":"2016-01-30T23:14:43","slug":"angularjs-extending-existing-services-using-decorator","status":"publish","type":"post","link":"https:\/\/tpodolak.com\/blog\/2015\/02\/25\/angularjs-extending-existing-services-using-decorator\/","title":{"rendered":"AngularJS &#8211; extending existing services using decorator"},"content":{"rendered":"<p>One of the features I miss the most in <i>AngularJS<\/i> is ability to easy unsubscribe event handlers. There is no convenience function opposed to <i>$on<\/i>, so in order to unsubscribe event, we have to call method returned by <i>$on<\/i> function<\/p>\n<pre lang=\"javascript\">\r\n(function () {\r\n    angular.module('app.download', [])\r\n        .controller('downloadCtrl', downloadController);\r\n\r\n    function downloadController($scope) {\r\n        \/\/ keep the unsubscribe function in local variable\r\n        var afterRenderUnsubscribe = $scope.$on('afterRender', onAfterRender);\r\n\r\n        function onAfterRender() {\r\n            console.log('CallingonAfterRender');\r\n            \/\/ unsubscribe form event\r\n            afterRenderUnsubscribe();\r\n        }\r\n    }\r\n}());\r\n<\/pre>\n<p>However in my opinion this approach fails in real life scenarios, because usually we are subscribed to more than one event &#8211; thus we have to have multiple local variables for detaching listeners. Fortunately <i>AngularJS <\/i> provides an easy way to extend existing services with some new functionalities &#8211; we can achieve that by using <i>$provide<\/i> service. This service exposes <a href=\"https:\/\/docs.angularjs.org\/api\/auto\/service\/$provide#decorator\"><i>decorator<\/i><\/a> method, which is able to intercept creation of any service, so we are able to augment it with some new functions. The best moment to call <i>$provide.decorator<\/i> method is during module configuration, thanks to this approach we are sure that our custom logic is invoked before module is started.<\/p>\n<pre lang=\"javascript\">\r\nangular.module('app.common', [])\r\n       .config(extendServices);\r\n\r\n\/*ngInject*\/\r\nfunction extendServices($provide) {\r\n    $provide.decorator('serviceName', function ($delegate) {\r\n     \/*some interception logic*\/\r\n    });\r\n}\r\n<\/pre>\n<p>In my case I wanted to extend <i>$rootScope<\/i> with new method <i>$un<\/i> for unsubscribing events, so I called method <i>decorator<\/i> with parameter <i>&#8216;$rootScope&#8217;<\/i> (note single quotation marks) &#8211; this tells <i>Angular<\/i> that I will be modifying <i>$rootScope<\/i> service<\/p>\n<pre lang=\"javascript\">\r\nangular.module('app.common', [])\r\n    .config(extendServices);\r\n\r\n\/*ngInject*\/\r\nfunction extendServices($provide) {\r\n    $provide.decorator('$rootScope', extendRootScope);\r\n}\r\n\r\n\/*ngInject*\/\r\nfunction extendRootScope($delegate) {\r\n}\r\n<\/pre>\n<p>Because I passed <i>&#8216;$rootScope&#8217;<\/i> as a first argument of <i>$provide.decorator<\/i> method, the <i>$delegate<\/i> parameter in <i>extendRootScope<\/i> function is actually a <i>$rootScope<\/i> service. Now having access to the service I can augment it with my custom logic for detaching event handlers.<\/p>\n<pre lang=\"javascript\">\r\n\/*ngInject*\/\r\nfunction extendRootScope($delegate){\r\n    $delegate.$un=function(name,listener){\r\n        var namedListeners=this.$$listeners[name],\r\n            idx;\r\n        if(namedListeners&&(idx=namedListeners.indexOf(listener))>-1){\r\n            namedListeners.splice(idx,1);\r\n        }\r\n    };\r\n    return $delegate;\r\n}\r\n<\/pre>\n<p>Entire code listing looks then as follows<\/p>\n<pre lang=\"javascript\">\r\n(function () {\r\n    angular.module('app.common', [])\r\n           .config(extendServices);\r\n\r\n    \/*ngInject*\/\r\n    function extendServices($provide)\r\n    {\r\n        $provide.decorator('$rootScope', extendRootScope);\r\n    }\r\n\r\n    \/*ngInject*\/\r\n    function extendRootScope($delegate)\r\n    {\r\n        $delegate.$un = function (name, listener) {\r\n            var namedListeners = this.$$listeners[name],\r\n                idx;\r\n            if (namedListeners && (idx = namedListeners.indexOf(listener)) > -1) {\r\n                namedListeners.splice(idx, 1);\r\n            }\r\n        };\r\n        return $delegate;\r\n    }\r\n}());\r\n<\/pre>\n<p>From now on, you can unsubscribe events handlers simply by calling $un method<\/p>\n<pre lang=\"javascript\">\r\n(function () {\r\n    angular.module('app.download', ['app.common'])\r\n        .controller('downloadCtrl', downloadController);\r\n\r\n    function downloadController($scope) {\r\n        $scope.$on('afterRender', onAfterRender);\r\n\r\n        function onAfterRender() {\r\n            console.log('Calling afterRender handler');\r\n            $scope.$un('afterRender', onAfterRender);\r\n        }\r\n    }\r\n}());\r\n<\/pre>\n<p>and our listener will react only once<\/p>\n<pre lang=\"javascript\">\r\nconsole.log('Broadcasting afterRender event for the first time');\r\n$rootScope.$broadcast('afterRender');\r\nconsole.log('Broadcasting afterRender event for the second time');\r\n$rootScope.$broadcast('afterRender');\r\n<\/pre>\n<p><a href=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2015\/02\/angularjs-extending-existing-services-using-decorator\/InvokeEvents.png\" rel=\"attachment wp-att-277\"><img decoding=\"async\" src=\"http:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2015\/02\/angularjs-extending-existing-services-using-decorator\/InvokeEvents.png\" alt=\"AngularJS\" width=\"800\" class=\"aligncenter size-full wp-image-277\" srcset=\"https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2015\/02\/angularjs-extending-existing-services-using-decorator\/InvokeEvents.png 1024w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2015\/02\/angularjs-extending-existing-services-using-decorator\/InvokeEvents-150x22.png 150w, https:\/\/tpodolak.com\/blog\/wp-content\/uploads\/2015\/02\/angularjs-extending-existing-services-using-decorator\/InvokeEvents-300x45.png 300w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><br \/>\nSource code for this post can be found <a href=\"https:\/\/github.com\/tpodolak\/Blog\/tree\/master\/AngularJSDecorator\">here<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the features I miss the most in AngularJS is ability to easy unsubscribe event handlers. There is no convenience function opposed to $on, so in order to unsubscribe event, we have to call method returned by $on function (function () { angular.module(&#8216;app.download&#8217;, []) .controller(&#8216;downloadCtrl&#8217;, downloadController); function downloadController($scope) { \/\/ keep the unsubscribe function [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28,26,18],"tags":[178,179,174],"class_list":["post-14","post","type-post","status-publish","format-standard","hentry","category-angularjs","category-decorator","category-javascript","tag-angularjs","tag-decorator","tag-javascript"],"_links":{"self":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/14","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/comments?post=14"}],"version-history":[{"count":5,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/14\/revisions"}],"predecessor-version":[{"id":527,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/14\/revisions\/527"}],"wp:attachment":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/media?parent=14"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/categories?post=14"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/tags?post=14"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}