AngularJS Dependency Injection: Introduction

Speaking of injection, AngularJS encourages a lot of dependency injection (DI), a programming paradigm made popular by the Java Spring Framework several years ago.

Any application, module, or function in JavaScript can resolve dependencies as variables from within that function’s scope, a parent scope or the global scope. Direct dependencies outside a function’s immediate scope are bad, and global dependencies are considered evil among experienced JavaScript developers and especially the AngularJS team- not just due to global namespace pollution and the potential for naming collisions and overwrites, but because a tight coupling between a function or module dependency and a global variable make its quite hard to write valid unit tests.

Dependency Injection or Inversion of Control (IOC) became popular with the Spring framework for Java. In Java applications prior to the advent of Spring, the norm was to run the application in a very “heavy weight” container such as JBOSS. By “heavy weight” I am referring to an application that included the code and libraries for every common enterprise dependency the application might need. Application level dependencies were made available to any function by direct reference because they might be needed at some point.

The reality was that many of these enterprise application dependencies often went unused in the application. Regardless, the memory footprint of the JVM required to run these applications inside these enterprise containers were in the hundreds of megabytes to gigabyte range.

One of the primary goals of the Spring Framework was to “invert control” of the application from the container to the application itself by allowing the application to pick and choose loading only the dependencies it knows its going to need. This is typically accomplished, in part, by passing in the dependency references directly as function parameters. The benefits of this approach were multi-fold but two that stand out were massive reduction in code and memory required to run the application, and code that had a much higher degree of referential transparency which translates directly to higher maintainability and testability because outside references do not need to be changed accordingly or mocked for testing.

<script>
/* Assume this code is run in a web browser. This function has a hard dependency\
 on its containing environment which is a webbrowser. If the global reference fo\
r console is not found or if it does not have amethod  called "log" then we get \
a fatal error. Code of this nature is difficultto maintain or port. */
function containerDependent(){
    // console is a hard, global dependency - not good
    console.log( 'a message' );
}
/*This function has its dependency explicitly injected by the calling function. \
$log can refer to the console.log, stdout, /dev/null, or a testing mock. This fu\
nction is much easier to maintain and move around. It has a much higher degree o\
f functional or referential transparency. */
function dependencyInjected( $log ){
    $log('a message');
}
</script>

The same benefits of dependency injection apply to JavaScript and are a characteristic of functional programming style. AngularJS and its directives make heavy use of dependency injection as the preferred way of declaring what the outside dependencies for any directive might be.

Module Pattern

The Module pattern in JavaScript in its most basic form is a self-executing function that:

  • Provides variable scope encapsulation
  • Imports any dependencies via function parameters
  • Exports it own functionality as a return statement
<script>
var MyModule = ( function( dependency ) {
    var myScopedVar = dependency || {};
    return {
        //module logic
    };
})(dependency);
</script>

Variable scope encapsulation (functional scope) is the only current way to prevent variable declarations from polluting the global namespace causing naming collisions- a epidemic among inexperienced front-end developers.

Dependency injection via function parameter is how we avoid hard coded outside dependencies within the function or module which can reduce its maintainability and portability.

Returning a function or object that encapsulates the business logic of the module is how we include the modules functionality and make it available in our library.

This describes the basic module pattern in JavaScript, but in practice libraries have taken this pattern and extended it to provide common APIs, global registration, and asynchronous loading. See CommonJS and AMD.

http://www.commonjs.org/

http://requirejs.org/docs/whyamd.html

Loose Coupling of Dependencies

These patterns are all key players in creating loosely coupled, modular front-end code that may function as a reusable UI component. AngularJS and AngularJS directives arguably employ these patterns in its UI component building blocks better than any other framework to date, and is one of the reasons for its immense popularity. The AngularJS framework API has provisions for defining discreet views, controllers, and data objects all encapsulated within a directive, which can be configured and invoked with simple declarative markup.

<!---- define the directive -->
<script>
    myLibrary.directive('searchBox', ['serviceDeps', function factory($_ref){
        return {
       //view
            template: '<div class="x-search-box">' +
                '<form ng-submit="submit()">' +
                '<input ng-model="searchString" type="text"' + 
                'id="x-search-input" ng-focus="focus()" ng-blur="blur()">' +
                '<div class="x-sprite-search" x-click="submit()"></div>' +
                '</form>' +
                '</div>',
            //controller
            controller: function(deps){
               //business logic here
            },
            //data
            scope: {},
            link: function postLink(scope, elm, attrs) {
                    var config = attrs.config;
            }
        };
    }]);
</script>

<!---invoke the directive -->
<search-box config=""></search-box>


In the AngularJS world, EVERYTHING is a module. If you are familiar with asynchronous dependency loaders likeRequire.js, and how they allow us to include library and application dependencies without having to worry about when and where they come from, then AngularJS modules should be easy to understand.

Dont Repeat Yourself (DRY)

The AngularJS team, like the rest of us, hates to repeat the same blocks of code needlessly. Like a lot of the jQuery binding boilerplate, repeated code is a large contributor to the problem of code bloat these days. That’s why they decided that all AngularJS code must be encapsulated as AngularJS modules. AngularJS modules can be defined in any order, include dependency modules that haven’t been defined yet, and still work properly. The only prerequisite is that the Angular.js core is loaded first.

Just like AMD loaders, AngularJS registers all module definitions before resolving dependencies in the module definitions. So there is no concern about the order that your AngularJS module dependency files are loaded into the browser, as long as, they are loaded after angular.js and before the DOMContentLoaded event when AngularJS normally bootstraps. This, of course, solves the problem of having to repeat blocks of code in order to satisfy the dependency on that code in different modules.

Repeated JavaScript code that finds its way into the browser or codebase repository is a pervasive problem in larger enterprise organisations where front-end development may take place among different groups spread out over continents, and the AngularJS module system can be quite helpful in avoiding or preventing this type of bloat.

// Define an AngularJS module with dependencies referenced by "MyApp".
// Dependencies can be loaded in any order before the "bootstrap" process and
// AngularJS takes care of resolving in the correct order.
angular.module('MyApp', ['MyServiceDeps', 'ThirdPartyPlugin', 'AnotherDep']);

// A module with no dependencies is created like so
angular.module('MyApp', []);

// We can then retrieve the module for configuration like so:
angular.module('MyApp');
// Notice the only difference is the presence or absence of an array as the 2nd 
// argument.

Leave a Reply