Automatic Dependency Injection in JavaScript
Tags: javascript, ioc, node
Published: 2019-06-01 00:57:20

> Originally posted at [michaelzanggl.com](https://michaelzanggl.com/articles/automatic-dependency-injection-in-javascript). Subscribe to [my newsletter](https://michaelzanggl.com/) to never miss out on new content.

In the previous post of this series we were implementing our very own ioc container by creating bindings with `ioc.bind` and `ioc.singleton`.
But this setup can be a little cumbersome. That's why many frameworks also come with automatic dependency injection.

Laravel can do this thanks to PHP's typehinting mechanism

```php
public function __construct(UserRepository $users)
{
    $this->users = $users;
}
```

Angular makes use of [TypeScript's emitDecorateMetadata](http://nicholasjohnson.com/blog/how-angular2-di-works-with-typescript/).

```typescript
class Pterodactyls {}

@Component({...})
class Park {
    constructor(x: Pterodactyls, y: string) {}
}
```

But these luxuries don't come in vanilla JavaScript. So in this article we will implement automatic injection in a similar fashion it was done [on the MVC framework Adonis.js](https://adonisjs.com/).

You can find the complete code on the same [GitHub](https://github.com/MZanggl/ioc-node) as in the last post.

We start off with (a little improved version of) the code from last time:

```javascript
module.exports = function createIoC(rootPath) {
    return {
        _container: new Map,
        _fakes: new Map,
        bind(key, callback) {
            this._container.set(key, {callback, singleton: false})
        },
        singleton(key, callback) {
            this._container.set(key, {callback, singleton: true})
        },
        fake(key, callback) {
            const item = this._container.get(key)
            this._fakes.set(key, {callback, singleton: item ? item.singleton : false})
        },
        restore(key) {
            this._fakes.delete(key)
        },
        _findInContainer(namespace) {
            if (this._fakes.has(namespace)) {
                return this._fakes.get(namespace)
            }

            return this._container.get(namespace)
        },
        use(namespace) {
            const item = this._findInContainer(namespace)

            if (item) {
                if (item.singleton && !item.instance) {
                    item.instance = item.callback()
                }
                return item.singleton ? item.instance : item.callback()
            }

            return require(path.join(rootPath, namespace))
        }
    }
}
```

The idea is to avoid newing up classes manually and using a new method `ioc.make` instead. Let's write the simplest test we can think of.

```javascript
describe('auto injection', function() {
    it('can new up classes', function() {
        const SimpleClass = ioc.use('test/modules/SimpleClass')
        const test = ioc.make(SimpleClass)
        expect(test).to.be.instanceOf(SimpleClass)
    })
})
```

And `SimpleClass` looks like this

```javascript
// test/modules/SimpleClass.js

class SimpleClass {}

module.exports = SimpleClass
```

Running the test should fail because we have not yet implemented `ioc.make`. Let's implement it in `index.js`

```javascript
const ioc = {
    // ...
    make(object) {
        return new object
    }
}
```

The test passes!
But it is a little annoying to always have to first do `ioc.use` and then `ioc.make` to new up classes. So let's make it possible to pass a string into `ioc.make` that will resolve the dependency inside.

A new test!

```javascript
it('can make classes using the filepath instead of the class declaration', function() {
    const test = ioc.make('test/modules/SimpleClass')
    expect(test).to.be.instanceOf(ioc.use('test/modules/SimpleClass'))
})
```

and `ioc.make` becomes

```javascript
if (typeof object === 'string') {
    object = this.use(object)
}
            
return new object
```

Nice! With this, we can already new up classes. And the best thing is, they are fakable because `ioc.use` first looks in the fake container that we can fill with `ioc.fake`.

With that out of the way, let's build the automatic injection mechanism. The test:

```javascript
it('should auto inject classes found in static inject', function() {
        const injectsSimpleClass = ioc.make('test/modules/InjectsSimpleClass')

        expect( injectsSimpleClass.simpleClass ).to.be.instanceOf( ioc.use('test/modules/SimpleClass') )
})
```

And we have to create the class `InjectsSimpleClass.js`

```javascript
// test/modules/InjectsSimpleClass.js

class InjectsSimpleClass {
    static get inject() {
        return ['test/modules/SimpleClass']
    }

    constructor(simpleClass) {
        this.simpleClass = simpleClass
    }
}

module.exports = InjectsSimpleClass
```

The idea is that we statically define all the classes that need to be injected. These will be resolved by the ioc container and newed up as well.

`ioc.make` will become:

```javascript
if (typeof object === 'string') {
    object = this.use(object)
}

// if the object does not have a static inject property, let's just new up the class
if (!Array.isArray(object.inject)) {
    return new object
}

// resolve everything that needs to be injected
const dependencies = object.inject.map(path => {
    const classDeclaration = this.use(path)
    return new classDeclaration
})

return new object(...dependencies)
```

Not bad. But something about `return new classDeclaration` seems wrong... What if this injected class also has dependencies to resolve? This sounds like a classic case for recursion! Let's try it out with a new test.

```javascript
it('should auto inject recursively', function() {
    const recursiveInjection = ioc.make('test/modules/RecursiveInjection')
    expect(recursiveInjection.injectsSimpleClass.simpleClass).to.be.instanceOf(
            ioc.use('test/modules/SimpleClass')
        )
    })
```

And we have to create a new file to help us with the test.

```javascript
// test/modules/RecursiveInjection.js

class RecursiveInjection {

    static get inject() {
        return ['test/modules/InjectsSimpleClass']
    }

    constructor(injectsSimpleClass) {
        this.injectsSimpleClass = injectsSimpleClass
    }
}

module.exports = RecursiveInjection
```

The test will currently fail saying `AssertionError: expected undefined to be an instance of SimpleClass`. All we have to do is switch out 

```javascript
const dependencies = object.inject.map(path => {
    const classDeclaration = this.use(path)
    return new classDeclaration
})
```

with

```javascript
const dependencies = object.inject.map(path => this.make(path))
```


Altogether, the `make` method looks like this

```javascript
if (typeof object === 'string') {
    object = this.use(object)
}

// if the object does not have a static inject property, let's just new up the class
if (!Array.isArray(object.inject)) {
    return new object
}

// resolve everything that needs to be injected
const dependencies = object.inject.map(path => this.make(path))

return new object(...dependencies)
```


And that's pretty much it! The version in the repo handles some more things like not newing up non-classes, being able to pass additional arguments, aliasing etc. But this should cover the basics of automatic injection. It's surprising how little code is necessary to achieve this.