Understanding Dependency Injection in Three Parts
Alright guys, in this video we are going to learn about dependency injection. Now here's the thing, we can start writing the code for Angular services without diving deep into dependency injection, but as always, I want to help you get a better understanding of the code that you write and maintain. So we are going to learn about DI in three parts.
First, we will take a look at the code that does not use DI and also take a look at its drawbacks. Next, I will explain DI as a design pattern and how it overcomes the drawbacks. Finally, I will talk about DI as a framework that Angular provides. So let's begin with some code that doesn't use DI.
The Problems with Tightly-Coupled Code
So consider three classes: one for a car, one for an engine, and one for tires. For simplicity, let's assume we only need an engine and few tires to build a car. So a car has two dependencies: engine and tires. In the car class, the constructor itself creates copies of the engine and tires. So when you instantiate a new car, the constructor instantiates a new engine and new tires. Simple enough, right? But there's a problem with this code.
Let's assume the engine evolves and its constructor can now accept a parameter, maybe the engine type like petrol or diesel. But when we change the engine, our car is broken. To repair our car class, we need to pass in a parameter to the engine constructor. Similarly, if the tires class accepts a new parameter, the car class needs to be fixed again. So our very first drawback is our code is not flexible. Anytime the dependencies change, the car class needs to be changed as well.
The second drawback is that this code is not suitable for testing. Anytime you instantiate a new car, you get the same engine and the same set of tires. What if we want to test our code? Maybe with a petrol engine, a diesel engine, new tires, old tires, and so on. It's not possible. Even if we were able to do it by changing code, what do we do if the engine and tires in turn have some dependencies? How do we keep track and create those dependencies? So basically, we are not in control of our code and this is where the second part of our video, that is dependency injection as a design pattern, is going to help us.
DI as a Design Pattern
Let's take a look at that now. Basically, dependency injection is a coding pattern in which a class receives its dependencies from external sources rather than creating them itself. So let's take a look at code that makes use of DI and understand this definition. Now, if you see here, we have moved the definition of the dependencies from inside the constructor to the parameters of the constructor. So the car class doesn't create the dependencies anymore, it just consumes them. The creation of those dependencies is external to this class. And by doing so, both the drawbacks we had are now solved.
To create a car, we are going to have the code something like this. We first make an engine and the tires and then pass them as parameters when creating a car. Now, even if the engine evolves to take a parameter, there is no change in how we make the car. So you can see that the engine now takes a parameter, but the car still accepts that engine without a problem. And same is the case with tires. Even if the tires now take a parameter, there is no change in how we make the car. Our code is much more flexible now. Even if we make changes to the dependencies, the car class remains intact.
And the same is applicable to testing as well. Since we are in complete control of the dependencies, we can mock the data to suit our testing needs. For example, we can create an old engine, an old set of tires, and test with an old car. Or we can create a new engine, a new set of tires, and test with a new car. So we can test for multiple scenarios using this approach. Well, that is about DI as a design pattern.
The Challenge of Manual Dependency Management
But now we have another problem at hand. With DI, we create a car by passing the dependencies as parameters. But here's the thing: we as the developer have to create those parameters first, right? Now we just have two dependencies, so we are fine. We create the engine and the tires and then pass it to the car. But what if the car had let's say 10 or 20 dependencies? We would have to create all those dependencies before passing them as parameters. And what if those dependencies in turn had dependencies on something else? Then we would first need to create those dependencies. So as the number of dependencies grow, it becomes really difficult to manage the code.
Take a look at this example. We have a car again, but this time the car has multiple dependencies. It is dependent on engine, tires, dependency A, dependency B, and so on, till dependency Z. So we will have to create all these dependencies ourselves. And if you have a look at the second car, it has a dependency Z, and dependency Z is in turn dependent on dependency A, B. So we will have to first create dependency A, B before creating dependency Z, and eventually the car. So this becomes extremely difficult for a developer.
Angular's DI Framework and the Injector
And this is where Angular's dependency injection as a framework comes into picture. The DI framework has something called an injector when you register all your dependencies. So the injector is basically like a container of all the dependencies, like engines, tires, dependency A, B, and so on. So if you want a car, ask for a car and the injector will provide a car for you. The framework will manage all the dependencies, so that you don't have to keep track of it. The DI framework makes the developer's job that much more easier.
If we relate to our example, our Employee List component might be dependent on service A, B, C, and so on. We register all these dependencies or services with the injector. Then, when the employee list component is initialized, the injector provides all the necessary dependencies for the proper functioning of the employee list component.
Using Services with DI in Angular
So coming back to where we left off in the last video, here is how you use a service in Angular. The first step, you create the service. And in our example it is the employee service class. The second step is to register the service with Angular's built-in injector. The last step is to declare the service as a dependency in the class that needs it. In our case, the employee list and employee detail are the classes that need the employee service.
So pictorially, here is how you can represent it. We have the injector, and with the injector we have registered our employee service. This employee service will be provided to the employee list component and the employee details component. So that is about DI as a framework in Angular.
Conclusion & Next Steps
Now, if the video was a bit too much, there is nothing to worry about. I recommend you go through the next video, take a look at the code, and then come back to this video for a much better understanding. Alright, in the next video, let's implement the steps to use a service in Angular. Thank you guys for watching, don't forget to subscribe, and I'll see you guys in the next video.