Two Simple JavaScript Dependency Injection Techniques
Recently I have been involved in a discussion about let's just say modern JavaScript techniques. In the discussion a point was made about how difficult it is to perform dependency injection, which I completely disagree. Not only is JavaScript dependency injection (DI) relatively easy, JavaScript's dynamic nature makes it extremely easy to stub and mock objects and their members.
To me the harder part is writing or finding a library that is a testable mock, this is one of the strengths Angular has over other libraries, shipping with testible mock objects, at least that is what I have read. My favorite to actually test dependencies is with sinonjs, a mocking and stubbing library. Sinon does not do anything with dependency injection, instead it leverages JavaScript's dynamic nature and replaces dependencies with mocks, stubs and spies.
I tend to design my libraries with natural DI capabilities, which can be very handy if you do want to replace an entire object dependency. It is actually a product of designing my libraries to depend on an implied interface (remember JavaScript has no real interface concept) so I can swap out libraries later.
There are effectively 2 places I can inject a mock library when I design a module. Let me show with my SPA library’s code. First is the self-executing anonymous function (SEAF). This is something I actually picked up from jQuery:
(function (window, $, undefined) {
//actual module definition here
})(window, $);
Here there are 3 parameters defined and a pair of arguments passed. I do window more for minification purposes. The $ is where I would ‘inject’ jQuery or a library that supports the jQuery interface. I actually wrote a small utility library called dollarbill library that is compatible with the jQuery interface for methods I actually use. There are several other jQuery like micro libraries available, so just assume you might want to use one of them instead of the full jQuery dependency. Because the SEAF lets you pass in an object for the $ you are decoupled from actually using jQuery and should be able to use a similar library. The last argument is a defensive mechanism to guard against someone mucking with the definition of undefined.
Let me make a little side statement about implementing an interface. I am not one that believes you need a 100% coverage when implementing an interface for a library like jQuery. There are actually several modules that comprise jQuery, several I do not use any longer, like animations. I rely on CSS to perform animations today, not JavaScript. So my implementation of the jQuery interface would completely omit those members. If you actually look at many C# and Java classes they often implement multiple interfaces. The same concept applies here. When I say I am implementing a jQuery interface I am implementing members in my library that I actually use.
Here is the SPA module’s constructor. It has a parameter that is assumed to be a JavaScript object with properties that can override the default values, stored in the module’s settings property (shown below). jQuery has a great method for this, extend, which is easy to replicate. Underscore has a similar method. Since I passed in a $ object I assume it implements the extend method since jQuery has it.
var spa = function (customSettings) {
//self instantiating module :) Thanks jQuery!!!
var that = new spa.fn.init();
//This merges any properties with default settings
that.settings = $.extend({}, that.settings, customSettings);
//injected third party library with a default fallback. If the default fails then things should blow up, blah blah.
that.bp = that.settings.bp || backpack();
//more initialization goes here
}
Finally here is the default settings, notice even though I don’t have bp defined here it can be applied using the extend method and then used. The settings parameter lets me inject an object that implements the methods used in my Backpack library, even though it is not defined in the settings object. I also took care to even incorporate a default fallback. If that does not work then things should blow up in the development phase, at least that is the hope and the developers will ensure the dependency is included.
settings: {
routes: [],
viewSelector: ".content-pane",
currentClass: "current",
mainWrappperSelector: "main",
NotFoundView: "NotFound",
NotFoundRoute: "404",
defaultTitle: "A Single Page Site with Routes",
titleSelector: ".view-title",
forceReload: "_force_reload_",
autoSetTitle: true,
parseDOM: true,
initView: true,
viewTransition: "slide",
asyncUrl: undefined
}
So this is a simple example of how you can do DI in a module showing how I can inject a jQueryish library and a library that supports my Backpack library’s core interface. By the way Backpack is a library I wrote to manage storing my views in localStorage, but I plan on creating a new library that implements the same interface to store the values in IndexDB. My SPA library does not care where the view objects are persisted, it just cares about the interface and will try to make Backpack the default if nothing is supplied.
I believe most of this I stole learned from studying jQuery’s structure, but have seen it used in several other well written libraries. Angular essentially does this, just a slightly different syntax. I will be diving deeper into designing modules like this and my SPA and Backpack libraries and much more in my upcoming book, High Performance Single Page Applications later this month. So please stay alert for when the book is available. I am wrapping up the content this week and hope for release in the next couple of weeks.