DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Dependency Injection in JavaScript
K (he/him)
K (he/him)

Posted on

Dependency Injection in JavaScript

Cover image by Papiertrümmer on Flickr

Why?

We all write code that depends on other code, this is completely normal. Even if we don't use any libraries, we will start to structure our codebase somehow. Maybe we modularize everything and now a module depends on another module, etc.

You probably heard that we should write code that is loosely coupled so we can replace parts of our software later, but what does this actually mean and how do we achieve this?

One way to do this is called Dependency Injection or short DI.

How?

DI boils down to one idea: remove explicit dependencies in the code and replace it with an indirection, but what does it mean when coding?

What is an explicit dependency?

You define some more or less static entities in your codebase. For example classes or functions.

class A {}

function f(x) { return x * x; }
Enter fullscreen mode Exit fullscreen mode

But, defining a class or a function doesn't make them explicit. The way they are used is the important factor here.

Their names can be used to reference them in other places of your code. For example, you could have a class B that uses class A.

class B {
  constructor() {
    this.a = new A();
  }
}
Enter fullscreen mode Exit fullscreen mode

Or you could call function f inside a function g that adds something to its result.

function g() {
  return f() + 10;
}
Enter fullscreen mode Exit fullscreen mode

And now the usage of function f and class A became explicit. Now class B only works if there is a class A defined and function g only works if there is a function f defined.

This may seem to many developers as a rather trivial fact and most of the time it has no greater implications, because the classes or functions never change.

But more often than not code changes and now the dependent code has to be re-written.

How to get rid of explicit dependencies?

The basic idea is, you don't call to functions or classes with their explicit names anymore. In statically typed languages this also means to get rid of the type annotations, but since JavaScript is dynamically typed, we just have to get rid of the class and function names.

Instead of writing

const a = new A();
Enter fullscreen mode Exit fullscreen mode

or

const result = A.someStaticMethod();
Enter fullscreen mode Exit fullscreen mode

You save a reference to A and pass this to the code that needs to call it. This allows you to change the reference to another class, when needed.

    class C {
      constructor(helperClass) {
        this.helper = new helperClass();
      }
    }
    ...
    let someHelperClass = A;
    ...
    if (someCondition) someHelperClass = B;
    ...
    const c = new C(someHelperClass);
Enter fullscreen mode Exit fullscreen mode

The same works with functions.

    function h(doSomething) {
      return doSomething() + 10;
    }
    ...
    let doSomething = f;
    ...
    if (someCondition) doSomething = g;
    ...
    const result = h(doSomething);
Enter fullscreen mode Exit fullscreen mode

The condition can come anywhere. Some DI frameworks even configure them via config files.

You can also create your objects and inject them instead of the reference to the class.

    class C {
      constructor(helper) {
        this.helper = helper;
      }
    }
    ...
    let someHelperClass = A;
    ...
    if (someCondition) someHelperClass = B;
    ...
    const c = new C(new someHelperClass());
Enter fullscreen mode Exit fullscreen mode

Practical Examples

You have a software that gets some data from services. You got multiple classes, each for one service, but they all share the same interface. Now you can create a condition via command line arguments, config file or environment variables that decides which class to use.

    class ServiceA { getData() {} }
    class ServiceB { getData() {} }
    class ServiceC { getData() {} }

    let Service;
    switch(process.env.APP_SERVICE) {
      case 'serviceB':
        Service = ServiceB;
      break;
      case 'serviceC':
        Service = ServiceC;
      break;
      default:
        Service = ServiceA;
    }
    ...
    class Application {
      constructor(Service) {
        this.service = new Service();
        this.run = this.run.bind(this);
      }
      run() {
        this.service.getData();
      }
    }
    ...
    const myApplication = new Application(Service);
    myApplication.run();
Enter fullscreen mode Exit fullscreen mode

You have UI components that render other UI components nested inside them. You could let them decide what child components the parent use like that

    const planets = ["mercury", "venus", "earth", "mars"];

    function List(planets) {
      return "<someMarkup>" + planets.map(planet => Item(planet)) + "</someMarkup>";
    }
    ...
    const markup = List(planets);
Enter fullscreen mode Exit fullscreen mode

Or you could simply pass the finished children into the parent

    function List(children) {
      return "<someMarkup>" + children + "</someMarkup>";
    }
    ...
    const markup(data.map(item => Item(item)))
Enter fullscreen mode Exit fullscreen mode

Now the parent can use whatever children you give it.

    const children = [FirstItem("Planets")]
    data.forEach(planet => children.push(Item(planet)));
    List(children);
Enter fullscreen mode Exit fullscreen mode

As you can see here, you don't have to pass a reference to the class or function as reference to get DI benefits. You can also create your results or instances before injecting it into the target code that depends on it.

Problems

Sometimes this gets a bit out of hand. Often DI is used rather simple. Like I said, when you pass a reference to a class or a function in you code around it's already DI, but you can take take things further by using external config files that decide what code is used, so the users have a way to modify the software without re-writing any code.

If you overdo it, you end up with big configurations and nobody really nows anymore what code is really running in the end.

Conclusion

Dependency Injection is a way of structuring code so it becomes more loosely coupled. It can be used in small parts of the application or govern the whole workings of it.

But as with everything, use in moderation. The more explicit the code, the easier it is to reason about it.

Top comments (11)

Collapse
 
binaryforgeltd profile image
Bart Karalus • Edited on

To put my two penny worth into this, I think one underrated benefit from this approach is that the code becomes more testable out of the box. If you think about component mocking, it is suddenly super easy to mock an object and inject it like nothing happened. :) But that is in general, not only JavaScript related.

Collapse
 
jillesvangurp profile image
Jilles van Gurp

This is in fact the reason a lot of Javascript code is notoriously hard to test. You basically need a complete browser to test even the most trivial stuff. Testability and good design go hand in hand. Anything that can be injected, can trivially be mocked. Once everything not core to the unit you are testing is injectable, the thing is highly testable.

The approach above is what is known as DYI dependency injection in the Java world. It's totally valid as DI is just a design pattern. But not very common because it does result in a lot of boiler plate code and it's a bit of a PITA to manually wire things up (not to mention error prone).

In the Java world, frameworks such as Spring provide a lot of DI infrastructure that has evolved to require less and less boiler plate. Combined with modern languages such as Kotlin, there is now a lot less need for stating the obvious than ever before.

Simply having a class with a constructor that takes arguments and a single annotation tells Spring "This is a bean, please construct an instance and pass it to places where it is needed as a dependency. It needs other beans to work as well. The names of those beans are the constructor arguments". If you then define those other beans in the same way, Spring takes care of all the plumbing, new calls to constructors of all the beans it has identified, configuration injection (with different profiles, default values, override mechanisms, etc), bean graph validation (no cycles, everything needs to resolve, etc) and then fires the whole thing up.

With stuff like Spring Boot, there is almost no code involved at all and you get a lot of things self initializing using sane defaults simply because the library is on the class path. I've coached a few frontend engineers dealing with some Spring Boot code. I wouldn't go as far as to say that they like it but they were all able to get productive pretty quickly.

Something like Spring is missing in the Javascript world and given the sorry state a lot of js projects end up in, it is actually needed. I see why code such as above is necessary. It's clearly better than the traditional spaghetti code approach (which, lets face it, is the common alternative). But it still looks like a lot of boiler plate to me to write, maintaint, test, and debug. And you don't even get typesafety with that. This just begs some decent automation and a bit more systematic approach to enforcing sane architecture. You get bits and pieces of that if you use react or similar frameworks but it requires a lot of discipline and expertise to stay on top of things.

Collapse
 
karfau profile image
Christian Bewernitz

I recently discovered that I can use ES6 default value for parameters to by default use the method that has the proper implementation, and injecting something else when needed. It goes at the end of the list of parameters, so it doesn't need to be provided.

Does anybody see any possible problem with that approach?

Collapse
 
kayis profile image
K (he/him)

Could you provide an example?

Collapse
 
karfau profile image
Christian Bewernitz

Lets say you have this amazing util function that looks up some data in some static/global read only Map and does some calculation that you need in many places in your code base, lets imagine the following signature: function wow(number): Metric.

In some other function foo in a different module I'm doing the following to be able to use it easily, but still be able to pass a mock or stub when I want to unit-test foo without invoking wow.

In TypeScript this could look like this:

import {wow as wowGLobal} from '../../[...]';

function foo(data: PayLoad, wow = wowGlobal) {
  const metric = wow(data.x);
  [...]
}

Is this helpful?

Thread Thread
 
kayis profile image
K (he/him) • Edited on

Ah, got it.

I probably would have implemented with a curried function.

const createFoo = wow => (data: PayLoad) => {
  const metric = wow(data.x);
  ...
}

in the regular place I'd use

foo = createFoo(wowGlobal);

and for testing something different, but I guess your way works too, it's just a bit more implicit.

Thread Thread
 
restuta profile image
Anton Vynogradenko • Edited on

To be annoyingly pedantic, createFoo above is not a curried function, because it can't be called like createFoo(wow, data) and like createFoo(wow, data) at the same time, it's a higher order function with aurity of 1

Thread Thread
 
kayis profile image
K (he/him)

I allow it!

Collapse
 
takion profile image
Jo

Thanks for this good article.

I coded my own JavaScript Dependency Injection Framework called Di-Ninja
github.com/di-ninja/di-ninja

It's full featured and is currently the only one in javascript, as I know, that implement the awesome Composition-Root design pattern,
helping you to keep all things decoupled and to wire application components and config at one unique root place.
blog.ploeh.dk/2011/07/28/Compositi...

It work well with NodeJS and Webpack

Any feedback would be appreciated

Collapse
 
ernsheong profile image
Jonathan ES Lin • Edited on

Hey Jo, di-ninja looks very promising. Curious as to why you decided to code your own vs. bottlejs, InversifyJS, etc. out there?

Collapse
 
takion profile image
Jo

Hey Jonathan,

thanks for your interest,

bottlejs is just an enhanced registry design pattern used as a factory and service locator, and InversifyJS provide only one of the two approach offered by Di-Ninja: the decorator approach. This approach, like the implicit strategy behind bottlejs and many others DiC, encourage coupling between the components of your application and the DiC library itself, and, only for the decorator approach, the decoupling between component rely on arbitrary interface or name.

Di-Ninja is the only one that offer Composition-Root design pattern, allowing

  • full decoupling between component: all exported components are factories that will only expect parameters
  • full decoupling from DiC: that means you can totally implement Di-Ninja as a top overlay without needing to rewrite any of your components (off course you'll have to remove singletons anti-pattern and replace them with factories if your goal is to make a clean design), your components doesn't need to know anything about Di-Ninja, only expect their own dependencies as parameters variables, and you can remove totally Di-Ninja and replace it with a raw Composition-Root, you'll have more code and it will be less readable but you can preserve the top overlay architecture, so Di-Ninja encourages you to best-practice by a REAL Inversion of Control paradigm

Another point is that InversifyJS require TypeScript, Di-Ninja can work with TypeScript, Babel (my preferred), and even without an application level transpiller, and it work fine on NodeJs, Browser (tested with webpack) and recently with React-Native environnement.

And there is many other features offered by Di-Ninja in the context of Composition-Root design pattern paradigm, but I will not list them all here, take a look on full documentation github.com/di-ninja/di-ninja to discover them ;) .

Join us at DEV

Find what you were looking for? Join hundreds of thousands of developers on DEV so you can:

Β 
🌚 Enable dark mode
πŸ”  Change your default font
πŸ“š Adjust your experience level to see more relevant content