How Angular2 Corrected My Understanding Of Dependency Injection

Dependency injection (DI) is a simple practice that boosts the testability of code and reduces coupling between classes.  There are three key flavors of dependency injection including (1) Constructor Injection, (2) Setter Injection, and (3) Interface injection.  I typically lump flavors (2) and (3) together, although their implementations may vary slightly.

For a long time, I avoided constructor injection like the plague. I had seen others implement their classes using constructor injection and rolled my eyes and said “I don’t understand why they would go on and do that…”.  And that was exactly right.  I didn’t understand constructor injection.  In fact, I really didn’t understand dependency injection as a whole.

Dependency Injection Gone Wrong

Let’s rewind to how things were in our code base before trying to introduce the dependency injection concepts.  To set the stage, let’s say we have some class Foo that depends on another class Bar.  In the constructor of Foo, we may instantiate a Bar instance to use later on when Foo needs it.  These examples are in C#, but the concepts apply to any language:

In the land of Dependency Injection, the keyword new should be somewhat of a red flag (unless instantiating a simple Data Transfer Object (DTO) with no behavior).  This means we are coupling ourselves with a specific implementation of IBar, namely Bar, which is not ideal.  Switching IBar implementations is not quite that simple and impacts Foo and any Foo unit tests.  Our team acknowledged that this was not good, so we figured we would adapt our code to make use of an Inversion of Control (Ioc) Container that we had inherited in our code base.  This container was nothing more than a singleton wrapper around the Microsoft Unity DI container.

So we updated our constructor to make use of this container as follows:

This is better, in that now we don’t have any direct references to the Bar implementation and are only aware of IBar.  However, we are now introducing another dependency, on the IocContainer.  This adds complexity to the unit tests because now each tests needs to configure the behavior of IocContainer according to what is needed.  This is also misusing the IocContainer as a ServiceLocator, which is not its intent.

We had our code in this state for a while, until I started to read into Angular.

Enter Angular2

Although I currently do very little web development, I was reading into Angular2 and watched a few Pluralsight courses that gave an intro into Angular2.  One thing that was repeated was that any service can be accessed in a component by simply adding it to the constructor of the component, as shown below:

I was excited to share how awesome this was and went back to the office on Monday to discuss this with a colleague who uses Angular.  Our conversation led to my understanding that this same behavior can be achieved using our our IocContainer and that we were greatly under-utilizing the IocContainer.  In reality the IocContainer and Dependency Injection work together to automatically inject the dependencies.  The IocContainer automatically resolves the types when instantiating the objects, so you never have to deal with invoking the constructor directly. This realization blew my mind, and I was so excited to test it out in some upcoming development.  Sure enough, it worked!

So in all honesty, I can’t give Angular2 all the credit, but it certainly sparked the conversation.

Revisiting Constructor Injection

Up until this point I had been avoiding constructor injection because I thought it was ridiculous to require the user of your code to explicitly provide all the dependent implementations.

Let’s revisit the simple Foo example utilizing the IocContainer appropriately.  This assumes that the IocContainer has been configured to register types for IFoo and IBar:

Notice that there is no reference to the IocContainer anywhere except for the application entry point.  This is key and greatly increases testability of each component.  Wherever possible, the IocContainer direct usage should be as limited as possible, towards the outskirts of your application.

Another realization that I had at this point is that the flavor of dependency injection really didn’t matter so much, as long as the DI container supports it.  With that said, I tend to favor constructor injection over setter/interface injection for two main reasons.  First, it is extremely clear what the dependencies are, since they are listed in the constructor.  Second, with setting/interface injection, the methods to inject the dependencies are public, meaning that the dependencies can be changed during the lifetime of the object.  Unless this is required, I prefer to not have that option.

Conclusion

This corrected understanding has greatly improved my code and has simplified the unit tests.  It is so easy to specify dependencies, and while you are developing a component, you don’t have to worry about where the dependencies are coming from.

Thank you Angular2 for embedding good dependency injection practices into the framework and for setting me straight on how I was using dependency injection and constructor injection!

Leave a Reply

Your email address will not be published. Required fields are marked *