Dependency Injection Containers in JavaScript
Tags: react, node, javascript, webdev
Published: 2020-04-11 14:30:48

Find me on [medium](https://medium.com/@jsmanifest)
Join my [newsletter](https://app.getresponse.com/webform_creator.html?webforms_id=SM2hz)

JavaScript is capable of many techniques due to its nature in flexibility. In this post, we will be going over the Dependency Injection Container.

This pattern actually provides the same goal as the Dependency Injection, but in a more flexible and powerful way by acting as the container that houses dependencies of functions (or classes) that require them when times they need it, such as during their initialization phase.

## Dependency Injection Without The Container

Let's quickly refresh our minds on what Dependency Injection is, how it looks like in code, what problems it solves, and what problems it suffers from.

The Dependency Injection is a pattern that helps to avoid hard coding dependencies in modules, giving the caller the power to change them and provide their own if they wanted to in one place.

These dependencies can be _injected_ into the _constructor_ (instantiation) phase _or_ can be set later by some _setter method_:

```js
class Frog {
  constructor(name, gender) {
    this.name = name
    this.gender = gender
  }

  jump() {
    console.log('jumped')
  }
}

class Toad {
  constructor(habitat, name, gender) {
    this.habitat = habitat
    this.frog = new Frog(name, gender)
  }
}

const mikeTheToad = new Toad('land', 'mike', 'male')
```

There are some issues with this:

**Issue #1**: If we needed to change how `Toad` was constructed and it required something fragile like the positioning of arguments or the data structure of them, we would have to manually change the code since it is _hardcoded_ into their block of code.

An example of this scenario is when there is a breaking change in the `Frog` class.

For one, if `Frog` _added_ a _third parameter_ in its constructor like `weight`:

```js
class Frog {
  constructor(name, gender, weight) {
    this.name = name
    this.gender = gender
    this.weight = weight
  }

  jump() {
    console.log('jumped')
  }
}
```

Then our `Toad` _must_ be updated because this new _dependency_ was added into our `Frog` instantiation:

```js
class Toad {
  constructor(habitat, name, gender, weight) {
    this.habitat = habitat
    this.frog = new Frog(name, gender, weight)
  }
}
```

So if we kept it this way, how many times do you think you would end up having to change `Toad` if you were in some frog startup company and that was one of the first pieces of code you started with?

**Issue #2**: You have to know what dependency to use for `Toad` everytime.

We _have_ to know that `Toad` now needs 4 arguments in the _exact same order_ for it to initiate an instance of `Frog` correctly, even their _data types_ otherwise bugs can easily occur.

And it can seem quite awkward if you know that a `Toad` _is_ essentially a frog, so knowing that, you might accidentally assume that `Toad` would be extending `Frog` then. So then you realize that an instance of `Frog` is being created inside `Toad` instead, and now you get all confused because you're an intelligent human being and the code was just throwing you off--realizing that the code does not align properly with the real world.

**Issue #3**: Unnecessarily involves more code

With the Dependency Injection pattern, these problems are solved by _inversing the control_ of the way the dependencies are instantiated:

```js
class Frog {
  constructor({ name, gender, weight }) {
    this.name = name
    this.gender = gender
    this.weight = weight
  }

  jump() {
    console.log('jumped')
  }
}

class Toad {
  constructor(habitat, frog) {
    this.habitat = habitat
    this.frog = frog
  }
}
```

Okay, that was easily. Now when there's another breaking change to `Frog` (such as the arguments being put into a JavaScript _object_), we don't even have to touch `Toad` or waste brain cells reading `Toad`, then `Frog`, then back to `Toad`, etc.

That's because we can now just change the part where we create an instance of `Toad` (which is better than having to go inside and change stuff in the `Toad` implementation--which is _bad_ practice! It _shouldn't_ have to worry about how frog is constructed--it should only know that it takes a frog as an argument and stores it in its `.frog` property to use later. _You_ take charge in its dependencies now.

```js
const mikeTheToad = new Toad(
  'land',
  new Frog({
    name: 'mike',
    gender: 'male',
    weight: 12.5,
  }),
)
```

So, we just practiced some clean code practices by abstracting out implementation details of `Frog` away from the `Toad` constructor. It makes sense: does `Toad` even have to care about _how_ `Frog` is constructed? If anything, it should have just extended it!

## Dependency Injection Container (DIC) Pattern

Now that we've refreshed our minds on Dependency Injection, let's talk about the Dependency Injection Container!

So why do we need the DIC pattern and why _isn't_ the Dependency Injection _without_ the container _enough_ in tough situations?

Here's the problem: It's simply just _not scalable_. The larger your project becomes the more you start losing confidence in maintaining your code in the long run because then it just becomes a mess over time. In addition, you also have to get the _order of injecting dependencies in the correct order_ so that you don't fall into the issue of something being `undefined` when you're instantiating something.

So in essence, 6 months later our code can evolve to something like this:

```js
class Frog {
  constructor({ name, gender, weight }) {
    this.name = name
    this.gender = gender
    this.weight = weight
  }

  jump() {
    console.log('jumped')
  }

  setHabitat(habitat) {
    this.habitat = habitat
  }
}

class Toad extends Frog {
  constructor(options) {
    super(options)
  }

  leap() {
    console.log('leaped')
  }
}

class Person {
  constructor() {
    this.id = createId()
  }
  setName(name) {
    this.name = name
    return this
  }
  setGender(gender) {
    this.gender = gender
    return this
  }
  setAge(age) {
    this.age = age
    return this
  }
}

function createId() {
  var idStrLen = 32
  var idStr = (Math.floor(Math.random() * 25) + 10).toString(36) + '_'
  idStr += new Date().getTime().toString(36) + '_'
  do {
    idStr += Math.floor(Math.random() * 35).toString(36)
  } while (idStr.length < idStrLen)

  return idStr
}

class FrogAdoptionFacility {
  constructor(name, description, location) {
    this.name = name
    this.description = description
    this.location = location
    this.contracts = {}
    this.adoptions = {}
  }

  createContract(employee, client) {
    const contractId = createId()
    this.contracts[contractId] = {
      id: contractId,
      preparer: employee,
      client,
      signed: false,
    }
    return this.contracts[contractId]
  }

  signContract(id, signee) {
    this.contracts[id].signed = true
  }

  setAdoption(frogOwner, frogOwnerLicense, frog, contract) {
    const adoption = {
      [frogOwner.id]: {
        owner: {
          firstName: frogOwner.owner.name.split(' ')[0],
          lastName: frogOwner.owner.name.split(' ')[1],
          id: frogOwner.id,
        },
        frog,
        contract,
        license: {
          id: frogOwnerLicense.id,
        },
      },
    }
    this.adoptions[contract.id] = adoption
  }

  getAdoption(id) {
    return this.adoptions[id]
  }
}

class FrogParadiseLicense {
  constructor(frogOwner, licensePreparer, frog, location) {
    this.id = createId()
    this.client = {
      firstName: frogOwner.name.split(' ')[0],
      lastName: frogOwner.name.split(' ')[1],
      id: frogOwner.id,
    }
    this.preparer = {
      firstName: licensePreparer.name.split(' ')[0],
      lastName: licensePreparer.name.split(' ')[1],
      id: licensePreparer.id,
    }
    this.frog = frog
    this.location = `${location.street} ${location.city} ${location.state} ${location.zip}`
  }
}

class FrogParadiseOwner {
  constructor(frogOwner, frogOwnerLicense, frog) {
    this.id = createId()
    this.owner = {
      id: frogOwner.id,
      firstName: frogOwner.name.split(' ')[0],
      lastName: frogOwner.name.split(' ')[1],
    }
    this.license = frogOwnerLicense
    this.frog = frog
  }

  createDocument() {
    return JSON.stringify(this, null, 2)
  }
}
```

We got a pretty nice app--a frog adoption facility where customers can come and adopt a frog. But the adoption process is not a simple give / receive money transaction. We're pretending that there's a law requiring this process to be conducted for every frog adoption facility being handing frogs to their new owners.

The whole adoption process ends when `setAdoption` from `FrogAdoptionFacility` is called.

Let's pretend you start developing code using these classes and ended up with a working version like so:

```js
const facilityTitle = 'Frog Paradise'
const facilityDescription =
  'Your new one-stop location for fresh frogs from the sea! ' +
  'Our frogs are housed with great care from the best professionals all over the world. ' +
  'Our frogs make great companionship from a wide variety of age groups, from toddlers to ' +
  'senior adults! What are you waiting for? ' +
  'Buy a frog today and begin an unforgettable adventure with a companion you dreamed for!'
const facilityLocation = {
  address: '1104 Bodger St',
  suite: '#203',
  state: 'NY',
  country: 'USA',
  zip: 92804,
}

const frogParadise = new FrogAdoptionFacility(
  facilityTitle,
  facilityDescription,
  facilityLocation,
)

const mikeTheToad = new Toad({
  name: 'mike',
  gender: 'male',
  weight: 12.5,
})

const sally = new Person()
sally
  .setName('sally tran')
  .setGender('female')
  .setAge(27)

const richardTheEmployee = new Person()
richardTheEmployee
  .setName('richard rodriguez')
  .setGender('male')
  .setAge(77)

const contract = frogParadise.createContract(richardTheEmployee, sally)

frogParadise.signContract(contract.id, sally)

const sallysLicense = new FrogParadiseLicense(
  sally,
  richardTheEmployee,
  mikeTheToad,
  facilityLocation,
)

const sallyAsPetOwner = new FrogParadiseOwner(sally, sallysLicense, mikeTheToad)

frogParadise.setAdoption(sallyAsPetOwner, sallysLicense, mikeTheToad, contract)

const adoption = frogParadise.getAdoption(contract.id)
console.log(JSON.stringify(adoption, null, 2))
```

If we run the code, it will work and create us a new adoption object that looks like this:

```json
{
  "t_k8pgj8gh_k4ofadkj2x4yluemfgvmm": {
    "owner": {
      "firstName": "sally",
      "lastName": "tran",
      "id": "t_k8pgj8gh_k4ofadkj2x4yluemfgvmm"
    },
    "frog": {
      "name": "mike",
      "gender": "male",
      "weight": 12.5
    },
    "contract": {
      "id": "m_k8pgj8gh_kdfr55oui28c88lisswak",
      "preparer": {
        "id": "n_k8pgj8gh_uxlbmbflwjrj4cqgjyvyw",
        "name": "richard rodriguez",
        "gender": "male",
        "age": 77
      },
      "client": {
        "id": "h_k8pgj8gh_hkqvp4f3uids8uj00i47d",
        "name": "sally tran",
        "gender": "female",
        "age": 27
      },
      "signed": true
    },
    "license": {
      "id": "y_k8pgj8gh_0qnwm9po0cj7p3vgsedu3"
    }
  }
}
```

We got a pretty nice app--a frog adoption facility where customers can come and adopt a frog. But the adoption process is not a simple give / receive money transaction. We're pretending that there's a law requiring this process to be conducted for every frog adoption facility being handing frogs to their new owners.

So it requires the facility (_Frog Paradise_) to generate a contract that requires the customer's signature. Then, a license is also created on the spot that the customer needs to have on them for legal protection. And finally, the adoption is completed after all is done.

Take a look at the `FrogOwner` class:

```js
class FrogParadiseOwner {
  constructor(frogOwner, frogOwnerLicense, frog) {
    this.id = createId()
    this.owner = frogOwner
    this.license = frogOwnerLicense
    this.frog = frog
  }

  createDocument() {
    return JSON.stringify(this, null, 2)
  }
}
```

It has three dependencies: `frogOwner`, `frogOwnerLicense`, and`frog`.

Lets pretend there was an update with `frogOwner` (an instance of `Person`) and it changed to become an instance of `Client`:

```js
class Client extends Person {
  setName(name) {
    this.name = name
  }
}
```

Now calls to initializing `FrogParadiseOwner` need to be updated.

But what if we had initialized `FrogParadiseOwner` throughout several locations of our code? If our code gets longer and the number of these instances increase, the more it becomes an issue to maintain.

This is where the _Dependency Injection Container_ can make the difference, because you would only need to change your code in one location.

This is what a dependency injection container can look like:

```js
import parseFunction from 'parse-function'

const app = parseFunction({
  ecmaVersion: 2017,
})

class DIC {
  constructor() {
    this.dependencies = {}
    this.factories = {}
  }

  register(name, dependency) {
    this.dependencies[name] = dependency
  }

  factory(name, factory) {
    this.factories[name] = factory
  }

  get(name) {
    if (!this.dependencies[name]) {
      const factory = this.factories[name]
      if (factory) {
        this.dependencies[name] = this.inject(factory)
      } else {
        throw new Error('No module found for: ' + name)
      }
    }
    return this.dependencies[name]
  }

  inject(factory) {
    const fnArgs = app.parse(factory).args.map((arg) => this.get(arg))
    return new factory(...fnArgs)
  }
}
```

With this in place, it becomes as easy as this to update changes:

```js
class Client extends Person {
  setName(name) {
    this.name = name
  }
}

const dic = new DIC()
dic.register('frogOwner', Client)
dic.register('frogOwnerLicense', sallysLicense)
dic.register('frog', mikeTheToad)

dic.factory('frog-owner', FrogParadiseOwner)
const frogOwner = dic.get('frog-owner')
```

Now instead of directly initializing it like before and having to change all other instances of the code:

```js
const frogOwner = new FrogParadiseOwner(Client, sallysLicense, mikeTheToad)
// some other location
const frogOwner2 = new FrogParadiseOwner(...)
// some other location
const frogOwner3 = new FrogParadiseOwner(...)
// some other location
const frogOwner4 = new FrogParadiseOwner(...)
// some other location
const frogOwner5 = new FrogParadiseOwner(...)
```

You can instead use the DIC to update it _once_ and you won't need to change any other parts of your code, because we _reversed the direction_ of the flow for that to the container:

```js
// Update here only by passing the dependency to the DIC
const dic = new DIC()
dic.register('frogOwner', Client)
dic.register('frogOwnerLicense', sallysLicense)
dic.register('frog', mikeTheToad)
dic.factory('frog-owner', FrogParadiseOwner)

const frogOwner = dic.get('frog-owner')
```

Let's explain what the DIC is doing:

You insert any classes or functions you want to be resolved by the DIC by passing it into the `.factory()` method which gets stored into the `.factory` property.

![dependency-injection-container-factory-method-in-javascript](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/dependency-injection-container-in-javascript/dependency-injection-container-factory-in-javascript.jpg)

For each of those functions passed into `.factory` you would have to register their arguments using `.register()` so that they can be picked up when the container is initializing the requested function. They get picked up from the `.dependencies` property. You can add things to the dependencies using the `.dependencies()` method.

![dependency-injection-container-dependencies-in-javascript](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/dependency-injection-container-in-javascript/dependency-injection-container-dependencies-in-javascript.jpg)

When you want to retrieve something, you use `.get` with some `key`. It uses the `key` to look through its `dependencies` and if it finds something there it will return it. Otherwise, it will proceed to look through its `factories` and if it finds something it will treat it as a function that you want it to resolve.

![dependency-injection-container-get-method-in-javascript](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/dependency-injection-container-in-javascript/dependency-injection-container-get-method-in-javascript.jpg)

Then it passes the invocation to `.inject` in which it reads the names of the function's dependencies (arguments) and grabs them from its `.dependencies` property, invoking the function and _injecting_ its arguments, returning the result.

![dependency-injection-container-inject-in-javascript](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/dependency-injection-container-in-javascript/dependency-injection-container-inject-in-javascript.jpg)

In our code examples I used `parse-function` to allow the `inject` method to grab the namees a function's arguments.

To do it without the library, you can add an extra argument to `.get` and have it pass down to its `.inject` like this:

```js
class DIC {
  constructor() {
    this.dependencies = {}
    this.factories = {}
  }

  register(name, dependency) {
    this.dependencies[name] = dependency
  }

  factory(name, factory) {
    this.factories[name] = factory
  }

  get(name, args) {
    if (!this.dependencies[name]) {
      const factory = this.factories[name]
      if (factory) {
        this.dependencies[name] = this.inject(factory, args)
      } else {
        throw new Error('No module found for: ' + name)
      }
    }
    return this.dependencies[name]
  }

  inject(factory, args = []) {
    const fnArgs = args.map((arg) => this.get(arg))
    return new factory(...fnArgs)
  }
}
```

```js
const dic = new DIC()
dic.register('frogOwner', Client)
dic.register('frogOwnerLicense', sallysLicense)
dic.register('frog', mikeTheToad)

dic.factory('frog-owner', FrogParadiseOwner)
const frogOwner = dic.get('frog-owner', [
  'frogOwner',
  'frogOwnerLicense',
  'frog',
])
console.log('frog-owner', JSON.stringify(frogOwner, null, 2))
```

Nonetheless we still get the same result:

```json
{
  "id": "u_k8q16rjx_fgrw6b0yb528unp3trokb",
  "license": {
    "id": "m_k8q16rjk_jipoch164dsbpnwi23xin",
    "client": {
      "firstName": "sally",
      "lastName": "tran",
      "id": "b_k8q16rjk_0xfqodlst2wqh0pxcl91j"
    },
    "preparer": {
      "firstName": "richard",
      "lastName": "rodriguez",
      "id": "g_k8q16rjk_f13fbvga6j2bjfmriir63"
    },
    "frog": {
      "name": "mike",
      "gender": "male",
      "weight": 12.5
    },
    "location": "undefined undefined NY 92804"
  },
  "frog": {
    "name": "mike",
    "gender": "male",
    "weight": 12.5
  }
}
```

Find me on [medium](https://medium.com/@jsmanifest)
Join my [newsletter](https://app.getresponse.com/webform_creator.html?webforms_id=SM2hz)