Skip to Content

Javascript dependency injection: what you should know

Dependency injection might be the best technique you didn’t know you were already using. For javascript developers, it’s both exceptionally easy to implement and a useful tool for managing scope in large applications. While Martin Fowler usually gets credit for the term, James Shore said it best:

Dependency injection means giving an object its own instance variables. Really. That’s it.

As software applications grow, so do the complexity of their parts. Separating them into *dependencies* and *dependent objects* makes it easier to support each part in a logical manner, share functionality across the application, and keep development focused on the components that need it most. Dependency injection isn’t the only way to achieve separation, but ease of use and general utility make it a useful technique for any javascript developer’s toolkit.

Injecting Dependencies

Dependency injection in javascript typically follows one of two patterns. In constructor injection, a dependency is assigned to an instance variable whenever the dependent function is initialized. A car, for instance, might require some type of engine to run:

function Car (engine) {
  this.engine = engine;
};

var myCar = new Car(new V8Engine);

It’s a trivial example with interesting implications. Since the V8Engine has instead been extracted into a shiny new class of its own, it may now be used by anything else that needs an engine. It can be tested independently and passed to the car with some degree of confidence that–if the car needs an engine–it will be able to drive.

Setter injection takes things a step further. The engine assigned at the factory will be good enough for most cars, but what if we want to upgrade? If the dependency is moved from the constructor to a method in the function’s prototype, it can now be replaced as the program runs:

function Car () {};

Car.prototype.setEngine (engine) {
  this.engine = engine;
};

var car = new Car();
car.setEngine(new V8Engine);
car.setEngine(new V12Engine);

Setter injection circumvents the single-use limitation of constructor injection, but–since frequent changes to a class (whether via an injection or anything else) make program flow difficult to follow–this is a technique to be used with care.

It also isn’t uncommon to see a javascript application implement setter and construction injections together. Rather than reproduce code shared by both the constructor and setter, a constructor following this pattern will typically forward its dependencies on to the setter for assignment.

function Car (options) {
  if (options instanceof Object) {
    this.setEngine(options.engine);
  }
};

Car.prototype.setEngine (engine) {
  if (engine) {
    this.engine = engine;
  }
};

var car = new Car({ engine: new V8Engine });
car.setEngine(new V12Engine);

Martin Fowler’s original article described a third type of injection (interface injection), but Javascript’s lack of explicit interfaces relegate its discussion here to a footnote.

What that’s good for

Like all techniques for inverting program control, the primary aim use for dependency injection is to decouple a complex application into smaller constituent parts. This has several benefits. We’ve briefly explored the possibilities for injecting services or other objects into a dependent object, but–in more general terms–dependency injection can also help with:

  1. Testing. Separating a program into its constituent elements reduces the overlapping code that must be mocked, stubbed, or evaluated when testing any given part. As an added bonus, multiple dependencies delivering similar services may rely on similar test code to prove their functions.

  2. Resilience. Dependency injection allows application components that change frequently to be separated from code less prone to change over time. This allows future development to focus on maintaining the components that really need it rather than spreading maintenance across the entire application.

  3. Flexibility. When multiple components are available before or during runtime, dependency injection can be used to ensure that the application is using the best ones.

Wrapping up

Software is short on silver bullets. Dependency injection can be a valuable tool, but it comes with challenges of its own. Use it too much, and once-isolated dependencies will quickly evolve into an unmaintainable mess. But use it judiciously, and you’ll find a valuable tool in keeping up your javascript application.




  • Sandro Pasquali

    Clearly expressed, comprehensive, and terse. An excellent article that many should read.