If you have built anything with Angular you have no doubt had some experience with Dependency Injection (DI), but not everyone uses Angular and even folks using Angular may not really understand what DI is doing. To really explore this we can build out our DI container in~20 lines of TypeScript (You can use vanilla JavaScript but I feel the type annotations help).

First, let’s start with a simple definition of DI. From Wikipedia “In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object.” While that sounds nice let’s look at a concrete example.

class HttpService {

get(url: string) {...}

post(url: string) {...}

} class UserService {

constructor(private http: HttpService) {} login(email: string, password: string) {

return this.http.post('/login', { email, password})

}

} const userService = new UserService(new HttpService())

In the above example we don’t want our UserService to care about how to make an http request, we want it to care about how to login. While the example above meets our requirements now it could be become cumbersome as we get more dependencies. We don’t want to care how you create an HttpService, we want to say “HEY I NEED TO MAKE HTTP REQUESTS GIVE ME THE HTTP SERVICE!!!!” This is when an injection container becomes useful. The previous example using one of these mystery containers might look something like this.

class HttpService { ... } class UserService { ... } // this is the thing we are going to build

const injector = new Injector(); const userService = injector.get(UserService);

OK cool, so we want our injector to do a couple things:

It should create a new instance of the class it is given. It should create new instances of the dependencies of the given class. It should return SINGLETON objects. This means that if an instance has previously been created it will return that instance.

Step 1–2: Creating instance and dependencies

First we need to create a new instance. Before continuing, stop and think about what pieces of information we need in order to do this dynamically… thought about it? We need to know WHAT the dependencies are. To make things explicit we are going to statically declare the dependencies on our class.

class HttpService {} class AuthService {

static deps = [HttpService];

...

}

Now we can have everything we need to create new instances and can create our first version of our Injector!

// This type defines a "Provider"

// as a class with a static "deps" property

type Provider<T> = {

deps: Provider<any>[]; new (...args: any[]): T;

}; class Injector {

get<T>(P: Provider<T>): T {

return new P(...P.deps.map(dep => this.get(dep)));

}

}

And that is it! What is happening is we create a new instance of “P” and we recursively call “Injector.get” to get an instance of each dependency, neat! At the moment each time “Injector.get” is called new instances are created.

Step 3: Caching results and returning singletons

Now that we are successfully creating new instances of our services and our services dependencies, we need to remember them somehow so they can be returned later. We need a cache!

type Provider<T> = { ... }; class Injector {

// singleton cache

private cache = new WeakMap<Provider<any>, any>(); get<T>(P: Provider<T>): T {

// if provided is in cache return it

if (this.cache.has(P)) {

return this.cache.get(P);

} // if no existing instance create a new one

const instance = new P(...P.deps.map(dep => this.get(dep)));



// then cache the resulting instance

this.cache.set(P, instance);



return instance

}

}

Now that we have our cache we can always return a singleton which means that our objects can now hold state if we wish.

Now let’s look at a more complicated example of what we can do with the above injector.

And that is it! Hopefully this simple example of a DI container helps you understand what DI is doing and roughly how it works.

What other features could you add to this Injector implementation? What if an Injector could have a parent? What would it look like to override a provider? Have a hack and let me know what you come up with!

If you want to see a more complicated implementation take a look at here.