I am a professional front end web developer based in London, England. The purpose of this blog is to write about issues in the field of web development as well as about open source projects that I am working on.

Blog posts

Dependency Injection

authorRichard Hunter date createdMar 02 2017 12:00 AM editedMay 10 2017 04:56 AM

Javascript applications are composed of objects which interact with each other. Objects that an object interacts with are known as 'dependencies'. When an object is created, in order for it to function correctly, it is necessary for its dependencies to also be created. The creation of objects and the wiring together of dependencies is one of the challenges of software development. There are various solutions to this problems. The best one, in my opinion, is dependency injection, hereafter referred to as 'DI'. In this article, I will discuss why DI is the best solution to the problem of object creation, and I will discuss an implementation of it that I have written and used in various software projects that I have been involved in.

Strategies for object creation

There are 3 common ways in which objects are created in Javascript programs. I will discuss each in turn.

Strategy 1: Objects are responsible for their dependencies

The first way of creating objects is by instantiated them by calling their constructor function with the 'new' keyword. Objects are responsible for creating their own dependencies. Usually, the code for doing this is within the object's constructor function as the following example shows.

    

        function Foo() {
            this.dep1 = new Dep1();
            this.dep2 = new Dep2();
        }

        let foo = new Foo();

    

The advantage of this approach is that it is easy to understand and implement. In fact, it is probably the approach that most front-end developers recognize and use. For small applications and for prototypes it is probably entirely sufficient. The problems start as the application gets larger.

  1. When you are dealing with dozens of objects rather than just a couple, the code base quickly becomes cluttered with a lot of object creation code. This makes the code harder to read and harder to maintain.
  2. Because the code for creating dependencies is within the constructor function, it becomes hard, to swap in different implementations for them. This means that objects cannot be tested in isolation from their dependencies. It isn't easy in a unit test to replace dependencies with mock objects.
In short, this is a strategy that does not scale well.

Strategy 2: Pass in dependencies from the outside

If objects being responsible for creating their own dependencies is a bad idea, then an obvious solution is to create dependencies separately and inject them into the objects that need them. (Note that word 'inject': We are getting close to DI but we are not quite there yet.) The way to inject dependencies into an object is by supplying them as arguments to the object's constructor function when you instantiate it.



    function Foo(dep1, dep2) {
        this.dep1 = dep1;
        this.dep2 = dep2;
    }

    //  dep1 and dep2 created elsewhere

    new Foo(dep1, dep2);

The advantage of this approach is that an object has been decoupled from its dependencies. We are now able to pass in whatever implementation we like for a dependency. It is now possible to pass different implementations for each dependency. In a unit test, instead of passing in the actual implementation of each dependency, which might do things like make an Ajax call to the server, we can pass in a mock object over which we have total control.

We have not solved the problem of having to write the object creation code however; we have simply shifted it out of the constructor functions to somewhere else. We also have created a new problem. The dependencies of an object have dependencies themselves, and these in turn have dependencies. Therefore, the entirety of objects within our application form a tree like structure. This means that every object in the tree needs to have passed to it not just its own dependencies but all those of its descendant objects as well! This leads to constructor functions with an unmanageable number of parameters.

Strategy 3: DI

So far we have two solutions that each present us with difficulties. The first solution makes unit testing extremely hard to do. The second solution solves this problem but results in code that is difficult to maintain. This brings us, finally, to dependency injection. DI is to some extent a synthesis of the best aspects of the other two solutions. As the name suggests, it involves the injection of dependencies into objects, but like the first solution it also gives objects a degree of control over their dependencies that the second solution does not allow.

So what is DI? In a nutshell, in a DI system, objects declare the dependencies that they need, and the system creates these dependencies, then injects them into the objects that need them. There are numerous ways for an object to declare its dependencies, but essentially it involves some way of annotating an object to specify the types of the dependencies that it requires. This could be an array of string tokens, or by using the names of the parameters in the constructor function, or even using decorators, which are a proposed new feature for Javascript, ( although available in Typescript.) The application itself doesn't have to concern itself at all with creating objects. This is all handled by the DI system. Apart from the annotations, the object definitions do not have to be altered in any way at all, and can continue to be instantiated in the normal way without DI if the developer so wishes it. (In fact, this is exactly what we shall do in unit tests). So to sum up the advantages of DI, we have decoupled objects from their dependencies and we have decoupled application code from the chore of object creation.

DI is common in the server world and is a staple of software development frameworks such as Spring for JAVA. It is less common in the front end sphere. As front end applications become more elaborate there is a growing need for it however. This need has been met most prominently by the Angular framework, which features an implementation of DI. There still remains a lack of open source DI implementations available out in the wild which is why I decided to create my own.

Diogenes: a DI implementation

Diogenes is a small DI library written by myself. It is hosted on NPM and can be imported as a node module. The source code is on Github. Here is an example of what the code using it looks like.



    var injector = new Injector();

    var Foo = function (options) {
        this.dep1 = options.dep1;
        this.dep2 = options.dep2;
    }

    Foo.inject = ['dep1', 'dep2'];

    injector.register('foo', DataService, Injector.INSTANCE );

    //  declarations and registration of dep1 and dep2 not shown

    let foo = injector.get('foo');

As you can see, there is nothing particularly unusual about the constructor function. However, note the constructor function's static 'inject' property. This is an array of strings, each of which identify the dependency which we want our DI system to inject. When the DI system starts up, it reads these tokens, creates the dependency, then injects it into the options parameter of the constructor function. You can see that the constructor function is able to easily get the dependencies out of this options object and assign them to instance properties. We also need to do some work to associate these tokens to particular dependencies. This is done with the register() method of the injector object. This method takes three arguments in the example above. The first argument is our token string. The second argument is our 'service provider'. A service provider is an object which tells the DI system how to create an object. In our example, it is simply a constructor function. The third argument is a constant which gives additional information to the DI system as to what kind of dependency we want to create. Injector.INSTANCE simply means that whenever an object requests a dependency with the 'foo' token it will be returned a new instance of DataService. There are other constants, such as Injector.CACHE_INSTANCE, which always returns the same instance of the service provider. The last thing of note is the get() method on the injector object. Whilst dependencies are automatically passed in to objects, sometimes we may wish to manually get a reference to them. The get() method allows us to do this. As well as other constants, there are other methods on the injector which I wont go into here as this is not a tutorial. Visit the Github page for more information. I do use this a lot in my personal projects, including my white label vanilla Javascript single page app demo which you can see in my portfolio. I have also used it in a commercial project where it worked well.

Conclusion

DI is by far the best way of creating the large number of interconnected objects which make up modern Javascript applications, bringing the benefits of reduced boilerplate and better testing. It is to be hoped that in the future more and more developers will become switched on to it, and also that it will become an integral part of other MVC frameworks besides just Angular. In the meantime, please feel free to try Diogenes in your own projects and tell me what you think.