DEV Community

Daniel Ostrovsky
Daniel Ostrovsky

Posted on • Originally published at Medium on

Metaprogramming in JavaScript/TypeScript Part #3 (Dependency Injection)

This is the third article in the "Metaprogramming in JavaScript and TypeScript" series.

First two:

In this article, we will consider such a concept as dependency injection.

And in this example, we will try to understand the principle of Metaprogramming and get acquainted with the reflect-metadata library.

Let's start right away with an example:

Suppose we have three classes:

1) MoviesService — should return information about movies to us, preferably with comments.

2) CommentsService — works with comments.

3) CrudService — a standard service for working with API services

The initialization of the MoviesService class would look like this:

And it seems everything looks logical, but it's not good enough :).

Indeed, in the constructors of each one of the classes, I have already indicated which dependencies are necessary to initialize this class … so why do we need this again during initialization?

And what if, for unit testing, we need to substitute a certain CrudServiceStub instead of CrudService? There is also the issue of CrudService being SingleTone and not being initialized twice (MoviesService and CommentsService). All this, of course, can be solved in the old ways, but we are not here for this :)

Let's start with decorators.

Simple and even empty decorator

Since the decorator is a regular function, after compilation into JS, we will get:

But if we use this as decorator…

Now JS will look completely different:

We are interested in the __decorate functionOr rather the line:

Reflect.decorate(decorators, target, key, desc);

In order to understand what it does, let's add a console.log. To our decorator.

And the decorator itself will also be added to the CommentsService class

And so, we see that Reflect.getMetadata(‘design:paramtypes’, target)returns a list of dependencies of the class being decorated. Where this information comes from, it is better to read in the original source.

To simplify, TypeScript will automatically add the metadata to Reflect (store/object).

Let's try to decorate the class method

In other words, Reflect is an object that stores some data. What data? The metadata. TypeScript in this object adds information about the types in the signatures of the class and methods and the types that these methods return.

Knowing all this and having access to the Reflect object, we can write the following function:

All dependencies will be automatically resolved.

However, this is still not ideal — for example, CrudService it will be created twice. But at the beginning of the article, we talked about singleton.

To begin with, for a more straightforward implementation, instead of the resolve function, we will create an Injector and add the necessary functionality to it.

This may not be the most elegant solution, but it will work as an example :)))

Summing up:

I hope I managed to talk about the following points in this article:

Reflect on a class is a kind of object into which TypeScript adds information about the types in the constructor, the types of method signatures, and the types that these methods return when compiling.

With the help of the reflect-metadata library, we can get this information and use it for our purposes.

In the following article, we will analyze how to add the information we need to the Reflect object and how we can use it.

If you like this article, comment and react ♾️ times.

Follow me on Twitter, Medium Dev/To for blog updates.

Check out my website and Youtube for videos and public talks.

Feel free to ask a question.

Thanks a lot for reading!

Top comments (0)