Dependency Injection

Dependency Injection is an amazing way to write code. Two days ago I finished writing the code for a permission service. Going into this project we, the group of engineers tasked with a system refactor, decided to use dependency injection to improve testability. I spent a few days making a point that DI is going to make our lives easier in many ways, and after a few good points the team was convinced. Dependency injection is allowing us to inject any database store, any data source needed, and somehow, it magically makes our code more portable.

It's like a dresser drawers filled with clothes, how it can be emptied, the drawers removed, and then the framework is easier to move, because it's lighter. The drawers can be removed and then new steel drawers added. When designing a system to accept injected dependencies, rather than calling on them, code becomes neater, cleaner, and more reliable and testable.

Juxtaposing DI and non-DI dependency handling, I am finding that the explicit declaration of my dependencies tends to cause me to funnel Dependencies into logical groupings. Looking at the code below, SchoolTiles is a resource injected into the schoolTiles directive. That's great because now I can inject a Mock into the directive, or I can completely replace the resource with another resource.

Now notice the way I am using underscore. I am not using DI; instead I am creating a local reference to the global window._ variable. The issue with this is that now my environment has to have underscore, even when I am just testing. This makes testing difficult, and if I have enough of these kinds of dependencies then I must crowd the global namespace, and manage dependencies for all directives, services, and controllers in one global space. That can be messy and confusing.

angular.module ('Tiro.directives').directive ('schoolTiles', ['SchoolTiles', function (SchoolTiles) {
    return {
        replace: true,
        scope: {
            school: '=school'
        },
        templateUrl: '/app/partials/schoolTiles.html',
        link: function (scope, element, attrs) {
            var _ = window._;
            scope.tiles = [];
            scope.getTiles = function () {
                if (_.isUndefined(scope.school.unitId)) {
                    return;
                }
                SchoolTiles.getByUnitId ({unitId: scope.school.unitId}, function (tiles) {
                    _.each(tiles, function(tile) {
                        _.extend(tile, scope.school);
                    });
                    scope.tiles = tiles;
                });
            };
            scope.$watch('school', function() {
                scope.getTiles ();
            });
        }
    };
}]);

Now consider changing the underscore dependency to be injected. It's not a lot of code changes. Changing a few lines here will lead to code that is much more testable, and easy to understand, because all the external dependencies are listed at the top.

angular.module ('Tiro.directives').directive ('schoolTiles', ['SchoolTiles', 'underscore', function (SchoolTiles, _) {
    return {
        replace: true,
        scope: {
            school: '=school'
        },
        templateUrl: '/app/partials/schoolTiles.html',
        link: function (scope, element, attrs) {
            scope.tiles = [];
            scope.getTiles = function () {
                if (_.isUndefined(scope.school.unitId)) {
                    return;
                }
                SchoolTiles.getByUnitId ({unitId: scope.school.unitId}, function (tiles) {
                    _.each(tiles, function(tile) {
                        _.extend(tile, scope.school);
                    });
                    scope.tiles = tiles;
                });
            };
            scope.$watch('school', function() {
                scope.getTiles ();
            });
        }
    };
}]);

As you can see this code is nearly identical. However, when testing this code, underscore can be injected, even just by injecting a fixture, a literal object containing the _.each, _.extend, and _.isUndefined. this code is intended for the browser, but now because we can inject the dependency, we can inject the Node.js version of underscore. That is allowing me to create headless unit tests for this code. That will make for a continuous integration strategy with more code coverage and fewer global dependencies to manage. Delegating this dependency management down to the test level is a brilliant time saver for the maintainers of the testing and deployment solution: something such as Jenkins. That's an awesome thing.