Dependency Injection in TypeScript
Tags: typescript, softwaredesign
Published: 2019-06-28 14:40:18

---
title: Dependency Injection in TypeScript
published: true
tags: TypeScript,Software Design
canonical_url: https://nehalist.io/dependency-injection-in-typescript/
---

 ![Dependency Injection in TypeScript](https://ghost.nehalist.io/content/images/2019/06/di-ts.png)

One thing I really like about mature frameworks is that they all implement some kind of dependency injection. Recently I've played around with this technology in TypeScript to get a better understanding of _how_ it works beneath the surface.

## What is dependency injection (DI)?

In case you have no idea what DI is, I **highly** recommend to get [in touch](https://en.wikipedia.org/wiki/Dependency_injection) with it. Since this post should not be about the _What?_ but more about the _How?_ let's try to keep this as simple possible at this point:

> Dependency injection is a technique whereby one object supplies the dependencies of another object.

_Quote from [Wiki](https://en.wikipedia.org/wiki/Dependency_injection)_

What does that mean? Instead of manually constructing your objects some _piece_ (often called _Injector_) of your software is responsible for constructing objects.

Imagine the following code:

```typescript
class Foo {
}

class Bar {
  foo: Foo;
  
  constructor() {
    this.foo = new Foo();
  }
}

class Foobar {
  foo: Foo;
  bar: Bar;
  
  constructor() {
    this.foo = new Foo();
    this.bar = new Bar();
  }
}
```

This is _bad_ for multiple reasons like having direct and non-exchangable dependencies between classes, testing would be _really_ hard, following your code becomes really hard, re-usability of components becomes harder, etc.. Dependency Injection on the other hand _injects_ dependencies into your constructor, making all these _bad_ things obsolet:

```typescript
class Foo {
}

class Bar {
  constructor(foo: Foo) {
  }
}

class Foobar {
  constructor(foo: Foo, bar: Bar) {
  }
}
```

_Better_.

To get an instance of `Foobar` you'd need to construct it the following way:

```typescript
const foobar = new Foobar(new Foo(), new Bar(new Foo()));
```

_Not cool._

By using an _Injector_, which is responsible for creating objects, you can simply do something like:

```typescript
const foobar = Injector.resolve<Foobar>(Foobar); // returns an instance of Foobar, with all injected dependencies
```

_Better._

There are numerous resons about _why_ you should dependency injection, including testability, maintainability, readability, etc.. Again, if you don't know about it yet, it's past time to learn something essential.

## Dependency injection in TypeScript

This post will be about the implementation of our very own (and very basic) `Injector`. In case you're just looking for some existing solution to get DI in your project you should take a look at [InversifyJS](http://inversify.io/), a pretty neat IoC container for TypeScript.

What we're going to do in this post is we'll implement our very own Injector class, which is able to resolve instances by injecting all necessary dependencies. For this we'll implement a `@Service` decorator (you might know this as `@Injectable` if you're used to Angular) which defines our services and the actual `Injector` which will resolve instances.

Before diving right into the implementation there might be some things you should know about TypeScript and DI:

### Reflection and decorators

We're going to use the [reflect-metadata](https://www.npmjs.com/package/reflect-metadata) package to get [reflection capabilities](https://en.wikipedia.org/wiki/Reflection_(computer_programming)) at runtime. With this package it's possible to get information about how a class is implemented - an example:

```typescript
const Service = () : ClassDecorator => {
  return target => {
    console.log(Reflect.getMetadata('design:paramtypes', target));
  };
};

class Bar {}

@Service()
class Foo {
  constructor(bar: Bar, baz: string) {}
}
```

This would log:

```
[[Function: Bar], [Function: String] ]
```

Hence we do know about the required dependencies to inject. In case you're confused why `Bar` is a `Function` here: I'm going to cover this in the next section.

**Important** : it's important to note that classes _without_ decorators do not have any metadata. This seems like a design choice of `reflect-metadata`, though I'm not certain [about the reasoning behind it](https://stackoverflow.com/questions/48547005/why-is-reflect-metadata-only-working-when-using-a-decorator/).

### The type of `target`

One thing I was pretty confused about at first was the type of `target` of my `Service` decorator. `Function` seemed odd, since it's obviously an `object` instead of a function. But that's because of how JavaScript works; classes are just [special functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes):

```typescript
class Foo {
    constructor() {
        // the constructor
    }
    bar() {
        // a method
    }
}
```

Becomes

```javascript
var Foo = /** @class */ (function () {
    function Foo() {
        // the constructor
    }
    Foo.prototype.bar = function () {
        // a method
    };
    return Foo;
}());
```

After compilation.

But `Function` is nothing we'd want to use for a type, since it's way too generic. Since we're not dealing with an actual instance at this point we need a type which describes what type we get after invoking our target with `new`:

```typescript
interface Type<T> {
  new(...args: any[]): T;
}
```

`Type<T>` is able to tell us what an object is instances of - or in other words: what are we getting when we call it with `new`. Looking back at our `@Service` decorator the actual type would be:

```typescript
const Service = () : ClassDecorator => {
  return target => {
    // `target` in this case is `Type<Foo>`, not `Foo`
  };
};
```

One thing which bothered me here was `ClassDecorator`, which looks like this:

```typescript
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
```

That's unfortunate, since we now do know the type of our object. To get a more flexible and generic type for class decorators:

```typescript
export type GenericClassDecorator<T> = (target: T) => void;
```

### Interfaces are gone after compilation

Since interfaces are not part of JavaScript they simply disappear after your TypeScript is compiled. Nothing new, but that means we **can't** use interfaces for dependency injection. An example:

```typescript
interface LoggerInterface {
  write(message: string);
}

class Server {
  constructor(logger: LoggerInterface) {
    this.logger.write('Service called');
  }
}
```

There'll be no way for our Injector to know what to inject here, since the interface is gone at runtime.

That's actually a pity, because it means we always have to type-hint our _real_ classes instead of interfaces. Especially when it comes to testing this may be become really unforunate.

There are workarounds, e.g. using classes instead of interfaces (which feels pretty weird and takes away the meaningfulness of interfaces) or something like

```typescript
interface LoggerInterface {
  kind: 'logger';
}

class FileLogger implements LoggerInterface {
  kind: 'logger';
}
```

But I really don't like this approach, since its redundant and pretty ugly.

### Circular dependencies causes trouble

In case you're trying to do something like:

```typescript
@Service()
class Bar {
  constructor(foo: Foo) {}
}

@Service()
class Foo {
  constructor(bar: Bar) {}
}
```

You'll get a `ReferenceError`, telling you:

```
ReferenceError: Foo is not defined
```

The reason for this is quite obvious: `Foo` doesn't exist at the time TypeScript tries to get information on `Bar`.

I don't want to go into detail here, but one possible workaround would be implementing something like Angulars [forwardRef](https://github.com/angular/angular/blob/master/packages/core/src/di/forward_ref.ts).

## Implementing our very own Injector

Okay, enough theory. Let's implement a very basic Injector class.

We're going to use all the things we've learned from above, starting with our `@Service` decorator.

### The `@Service` decorator

We're going to decorate all services, otherwise they wouldn't emit meta data (making it impossible to inject dependencies).

```typescript
// ServiceDecorator.ts

const Service = () : GenericClassDecorator<Type<object>> => {
  return (target: Type<object>) => {
    // do something with `target`, e.g. some kind of validation or passing it to the Injector and store them
  };
};
```

### The `Injector`

The injector is capable of resolving requested instances. It may have additional capabilities like storing resolved instances (I like to call them _shared instances_), but for the sake of simplicity we're gonna implement it as simple as possible for now.

```typescript
// Injector.ts

export const Injector = new class {
  // Injector implementation
};
```

The reason for exporting a constant instead of a class (like `export class Injector [...]`) is that our Injector is a [singleton](https://en.wikipedia.org/wiki/Singleton_pattern). Otherwise we'd never get the same instance of our `Injector`, meaning everytime you `import` the Injector you'll get an instance of it which has no services registered. (_Like every singleton this has some downsides, especially when it comes to testing._)

The next thing we need to implement is a method for resolving our instances:

```typescript
// Injector.ts

export const Injector = new class {
  // resolving instances
  resolve<T>(target: Type<any>): T {
    // tokens are required dependencies, while injections are resolved tokens from the Injector
    let tokens = Reflect.getMetadata('design:paramtypes', target) || [],
        injections = tokens.map(token => Injector.resolve<any>(token));
    
    return new target(...injections);
  }
};
```

That's it. Our `Injector` is now able to resolve requested instances. Let's get back to our (now slightly extended) example at the beginning and resolve it via the `Injector`:

```typescript
@Service()
class Foo {
  doFooStuff() {
    console.log('foo');
  }
}

@Service()
class Bar {
  constructor(public foo: Foo) {
  }

  doBarStuff() {
    console.log('bar');
  }
}

@Service()
class Foobar {
  constructor(public foo: Foo, public bar: Bar) {
  }
}

const foobar = Injector.resolve<Foobar>(Foobar);
foobar.bar.doBarStuff();
foobar.foo.doFooStuff();
foobar.bar.foo.doFooStuff();
```

Console output:

```
bar
foo
foo
```

Meaning that our `Injector` successfully injected all dependencies. _Wohoo!_

### Conclusion

Dependency injection is a powerful tool you should definitely utilise. This post is about _how_ DI works and should give you a glimpse of how to implement your very own injector.

There are still many things to do. To name a few things:

- error handling
- handle circular dependencies
- store resolved instances
- ability to inject more than constructor tokens
- etc.

But basically this is how an injector could work.

And, as always, the entire code (including examples and tests) can be found on [GitHub](https://github.com/nehalist/di-ts).

---

If you liked this post feel free to leave a ❤, follow me on [Twitter](https://twitter.com/nehalist) and subscribe to [my newsletter](https://nehalist.io/#subscribe). This post was originally published at [nehalist.io](https://nehalist.io/dependency-injection-in-typescript/) on February 5, 2018.