mykeels.com

Dependency Injection Explained in JavaScript

I’m probably telling you what you already know, and if you don’t, it’s not too late to find out…

Dependency Injection Explained in JavaScript

I’m probably telling you what you already know, and if you don’t, it’s not too late to find out…

Why Dependency Injection

JavaScript frowns at global variables.

When working with individual scripts in the browser, it’s easy to attach an object created from one script, to the window object and use it in another script. e.g.

<script>
   var foo = 'bar'
</script>
<script>
   console.log(foo) // should output 'bar'
</script>

In Node however, you’re working with modules which usually have no idea of the existence of others (or shouldn’t), and you have a lot of scripts which use code from other scripts, creating dependencies. e.g. in create-person.js,

import db from 'my-db-framework'
export const createPerson = function (person) {
  db.insert('tblPerson', person)
}

This code depends on the export ofmy-db-framework, which is cool, until you have to test, or/and db needs to be instantiated in a different way, and you have to edit multiple scripts that depend on db to get that working.

How Dependency Injection Helps

Dependency Injection takes care of that by passing an already created instance of db into the createPerson function, like:

export const createPerson = function (db, person) {
  db.insert('tblPerson', person)
}

So, createPerson depends on an instance of db, which we can instantiate in any form we like, which is cool for tests (because mocks), but not cool when writing code.

Factory Functions

I mean, do we have to pass an instance of db every time we want to create a new person?

Couldn’t we pass it once and not have to pass it anymore?

Here come factory functions to save the day:

export const GetCreatePerson = function (db) {
   return function (person) {
      db.insert('tblPerson', person)
   }
}

So now, when we require('./create-person'), we get a function we can pass db into, that returns the createPerson function which already has db in its closure.

const createPerson = require('./create-person')(db)
createPerson(new Person({ name: 'Mykeels' }))

Note: Factory Functions are given their name because they create stuff

Cool, yea?

What to look out for

When passing dependencies, you should probably ensure that the correct dependencies are passed into your factory functions, by checking them. e.g.

export const GetCreatePerson = function (db) {
   if (!db) throw new Error('db is not defined')
   else if (typeof(db.insert) !== 'function') {
      throw new Error('invalid instance')
   }
   return function (person) {
      db.insert('tblPerson', person)
   }
}

This way, you’ll always ensure that no one makes the mistake of passing a wrong, or improperly-created dependency.

Multiple Dependencies

If you have multiple dependencies to deal with, you can use multiple parameters, or use a single object to encapsulate them. I like to use propslike:

export const GetCreatePerson = function (props) {
   if (!props) throw new Error('props is not defined')
   else if (!props.db) throw new Error('db is not defined')
   else if (typeof(props.db.insert) !== 'function') {
      throw new Error('invalid instance')
   }
   return function (person) {
      props.db.insert(props.tables.tblPerson, person)
   }
}

What we’ve learned

This is a really good way to pass dependencies into scripts, and makes it possible to test scripts as unit, by simulating its dependencies via a process called mocking.

Cheers!

Related Articles

Tags