Interception using Decorator and Lazy Loading with AngularJS

Angular provides it’s own dependency injection that supports everything from annotations to decorators. Interception is a feature that allows you to extend, intercept, or otherwise manipulate existing services. It makes it easy to monkey-patch existing APIs to suite the specific needs of your application. You can build an app that relies on the built-in services for common functionality such as logging and still apply your own custom behavior as needed.

To illustrate this, consider a simple Angular app. The markup simply displays a title:

<div data-ng-app="myApp">
    <div data-ng-controller='MyController'>{{title}}</div>     
</div>

The controller is a bit more interesting because it configures the title and then logs a few messages to the console. By default, Angular’s $log service provides a safe way to log information to the browser’s console. I say “safe” because it will check if the console is present before attempting to use it so you won’t throw exceptions on older browsers. Let’s look at  a simple controller that is injected its $scope to set up the title and the $log service to log a warning and an error. Notice how the service uses the $injector property (array) to annotate its dependencies – you can verify this by changing the name of the constructor parameters to see they will still be injected correctly.

var MyController = (function () {
    function MyController($scope, $log) {
        this.$scope = $scope;
        this.$log = $log;
        $scope.title = 'Decorator example';
        $log.warn('This is a warning.');
        $log.error('This is an error.');
    }
    MyController.$injector = ['$scope', '$log'];
    return MyController;
})();

Wiring up the app is then simple:

var app = angular.module('myApp', []);
app.controller('MyController', MyController);

When you run the app in a browser with the console open, you’ll see the warning and error written to the console:

console

Of course, some systems may wish to capture errors and warnings in a different way. You may want to present a debug console to the user regardless of the browser they are in, or even wire up a service that can record exceptions on the server. Either way, instead of writing your own logging service, you can use Angular’s decorator to monkey-patch the $log service and extend it with your own functionality. This supports the open/closed principle, to keep your components open to extension but closed to direct modification.

First I’ll extend the markup to provide a console area. I wouldn’t normally do this using the $rootScope but it will keep the example simple. It also shows how we can set up a global area outside of any of the controllers:

<div data-ng-app="myApp">
    <div data-ng-controller='MyController'>{{title}}</div>
    <hr />
    <div data-ng-repeat="line in console">
        <pre>{{line}}</pre>
    </div>
</div>

Now let’s create a service that simply picks up anything generated as a warning or an error and keeps track of it. (If you wanted to, you could take what I wrote about providers in my last post and use that to configure how much history the service keeps).

var MyConsole = (function () {
    function MyConsole() {
        this.lines = [];
        this.writeLn = this.pushFn;
             }
    MyConsole.prototype.pushFn = function (message) {
        this.lines.push(message);
    };
    return MyConsole;
})();

Notice this simply tracks an array internally and exposes a method to write to it. By itself, it won’t do much good because there is no way to see what’s actually being written. In the HTML we’re expecting something called console that contains a collection. Normally you’d wire this in your app’s run section or similar, but for fun let’s have the service itself set up the root scope. The problem is that in using the service to monkey-patch the logger, we’ll have to instantiate it before the $rootScope is created by Angular. If we try to inject it as a dependency, we’ll get an exception. To fix this, we’ll keep track of whether we have the root scope or not, and ask the $injector if it is a available. Here is the updated service:

var MyConsole = (function () {
    function MyConsole($injector) {
        this.lines = [];
        this.rootScope = false;
        this.writeLn = this.lazyRootCheckFn;
        this.injector = $injector;
    }
    MyConsole.prototype.pushFn = function (message) {
        this.lines.push(message);
    };
 
    MyConsole.prototype.lazyRootCheckFn = function (message) {
        this.pushFn(message);
        if (!this.rootScope && this.injector.has('$rootScope')) {
            this.rootScope = true;
            this.injector.get('$rootScope').console = this.lines;
            this.writeLn = this.pushFn;
        }
    };
    MyConsole.$inject = ['$injector'];
    return MyConsole;
})();

Notice that there are two internal functions that can be exposed as the outer writeLn function. At first, a function called lazyRootCheckFn that checks for the $rootScope is wired in. It asks the $injector if it has the $rootScope yet, and when it does, wires it up. It then swaps the external function with the simpler function called pushFn that simply adds the message to the list. This prevents it from checking again every time it is called because the $rootScope wire-up is a one-time event. This is also how you can lazy-load dependencies when they are not available to your app, because the $injector is the first thing Angular wires up as it handles everything else and allows you to query whether something has been configured yet using the has function. Now the console on the root scope is wired to the collection of messages and ready for data-binding. Of course, you won’t see anything yet because the logger needs to be intercepted.

To intercept a service, request the $provide service during your app’s configuration:

app.config([
    '$provide', function ($provide) {
}]);

This service exposes a function named decorator that allows you to intercept a service. You pass the decorator the service you wish to intercept, then an annotated function that you use for decoration. That function should request a dependency named $delegate. The $delegate dependency passed in is the service you wish to intercept (in this case, the $log service). At the end of the function, you return the service to take it’s place. You could return an entirely new service that mimics the API of the original, or in the case of our example simply monkey-patch the existing service and return it “as is.” Here I just return the original service:

$provide.decorator('$log', [
'$delegate',
'myConsole',
function ($delegate, myConsole) {
    return $delegate; // this is the $log service
}]);

I want to intercept calls to warn and error, so I created a function to reuse for patching:

var swap = function (originalFn) {
    return function () {
        var args = [].slice.call(arguments);
        angular.forEach(args, function (value, index) {
            myConsole.writeLn(value);
        });
        originalFn.apply(null, args);

    }
};

The function returns a new function that effectively parses out the arguments into an array, sends them to my own version of the console service, then calls the original function with the arguments list. Now I can monkey-patch the existing methods to call my own:

$delegate.warn = swap($delegate.warn);
$delegate.error = swap($delegate.error);

That’s it! My controller has no clue anything changed and is still faithfully calling the $log service. However, as a result of intercepting that service and adding a call to myConsole, and the fact that myConsole lazy-loads the $rootScope and wires up, it now will display errors and warnings on the page itself:

Decorator example

This is a warning.
This is an error.

In this post I’ve attempted to further demonstrate just how powerful and flexible Angular is for client-side development. I’ve created a fiddle with full source code for you to experiment with on your own. Enjoy!

Atmosera is an Oregon-based company who architects, deploys, and operates public, private, and hybrid Microsoft Cloud Platform and Azure environments for customers across the globe and diverse industries. Since 2011, Atmosera has been a trusted cloud partner to the State of Oregon. State agencies including the Oregon State Hospital (OSH), Oregon Health Authority (OHA), the Department of Justice, the Department of Human Services, and the Department of Treasury host their applications with Atmosera. This includes a partnership with Enterprise Technology Services (ETS) and the State of Oregon Data Center.

The Right Solution for Your Needs.

We deliver a hybrid spectrum of Microsoft Cloud Platform and Azure solutions to government agencies and application developers who demand a modern, open and flexible cloud service platform. We offer trusted, transparent, and secure Infrastructure as a Service (IaaS) and Platform as a Service (PaaS) solutions for production business applications, Business Intelligence (BI), continuous data protection, application availability, test/development, and Software as a Service (SaaS)

Architected to meet your needs.

Deployed flawlessly.

Operated reliably 24x7x365.

We build solutions to address your individual business objectives with an eye to sustained technology innovation.

We manage the entire lifecycle from 
start to finish to eliminate surprises 
and allow you to focus on your services.

We deploy environments which 
can be managed and maintained for you by our team of experts 24x7x365.

20+ Years of Experience Makes Us a Trusted Partner You Can Count On.

Atmosera backs technology with real humans who deliver experience and unparalleled dedication, resulting in smarter cloud computing investments. We developed a core methodology to ensure we accurately capture your needs and translate them into the best solution possible. This process gives you the peace of mind that your cloud investment will be aligned with the return you seek. We can be counted on to bring our industry experience and real-world best practices to operate Microsoft Cloud Platform and Azure environments.

Assess:

Migrate:

Re-platform:

Operate:

Rely on our team to map your existing environment to a corresponding 
Azure cloud.

Easily move from your existing environment to a public or private Microsoft cloud.

Understand how to transform your applications to better take advantage 
of cloud capabilities.

Our team actively manages all maintenance & optimization to keep your environment running at its best.

Information Security: Protect Your IT with Industry Best Practices.

We provide managed Information Security (InfoSec) and compliance options across the entire computing stack, from connectivity to applications, with stringent physical and logical security controls.
We take on the security and infrastructure concerns by becoming an extension to our customer’s team or their application development vendor. In that partnership, Atmosera shares the responsibility and liability associated with maintaining a secure environment, and stands by that commitment with a guarantee based on the defined lines of responsibility. All Atmosera customers benefit from more than technology by also getting processes, policies, training, and 24x7x365 technical resources. Customers have the peace of mind of knowing an industry expert has performed a thorough risk assessment, identified a remediation plan and provided ongoing audit support to ensure customers stay secure. Best of all, Atmosera understands this level of service is, and will continue to be, required year after year.

Why Microsoft Cloud Platform and Azure?

Microsoft has made major strides in the public cloud space over the past few years. They are gaining market momentum with companies and also the analysts community who recognize the more than USD15B investment made. At this point Azure has more data centers than AWS and Google Cloud combined. It spans an unparalleled global reach with 36 regions available and more being added. Azure offers companies wanting to put workload in the a public cloud with a secure, scalable and competitive option.

The Power of Microsoft’s Hybrid Cloud Platform.

Microsoft is the only hyperscaler to offer a true hybrid cloud solution with the flexibility to run applications using private or public clouds — and the ability to move between them. Atmosera has the skills and expertise necessary to help architect, deploy, and operate integrated hybrid cloud solutions based on the Microsoft Cloud Platform and microservices. This offers our customers a unique set of capabilities and flexibility when planning out their cloud journey.

Azure Certified for Hybrid Cloud.

Atmosera has developed core competencies and intellectual property to take full advantage of all Azure has to offer. All Atmosera Azure services receive the “Azure Certified for Hybrid Cloud” designation which confirms it passed Microsoft’s rigorous validation process under the Cloud OS Network (COSN) program. Customers benefit from knowing that their solution was carefully planned and executed using best practices and proven methods.

Microsoft Azure Government.

Starting in June of 2017, Microsoft opened up their Microsoft Azure government regions to partners. Atmosera was one of the first Microsoft Cloud Solution Provider (CSP) to be approved to deploy qualified government customers in these dedicated regions.
Azure Government offers world-class security and compliance for US federal, state, local, and tribal government agencies and their partners. It provides an environment for government-only workloads which is operated by screened US citizens. Customers can choose from six data center regions with DoD Impact Level 5 Provisional Authority (PA) including two dedicated regions for US Department of Defense workloads. Environments can leverage hybrid flexibility by maintaining some data and functionality on-premises. Azure Government also offers the most certifications of any cloud provider to simplify critical government compliance requirements.

Azure Government regions where Atmosera can deploy customers include the following six locations:

We deliver solutions that accelerate the value of Azure.

Ready to experience the full power of Microsoft Azure?

Start Today

Blog Home

Stay Connected

Upcoming Events

All Events