Introduction to Dependency Injection in Angular
Table of contents
The Ukrainian IT market is going well beyond the expected curve during a worldwide remote-work transition. In 2020, the IT export from Ukraine grew over 20% and helped break the export records by increasing the annual export total to over five billion US dollars.
Ukraine’s year-on-year growth in the IT sector is largely credited to minimal restrictions, stable tax policies on the industry, the unparalleled professionalism of its IT specialists and their ever-increasing urge to design better. On that note, let’s get down to business and look into one of our widely implemented design principles that improve the backend of applications significantly. This article is an overview piece and offers an introductory view on what is dependency injection in Angular.
What is dependency injection?
A dependency is an object on which another object depends. Multiple dependencies can complicate the code structure and make it difficult to read, reuse, or test.
This is where dependency injection comes into play to simplify and create a seamless coding experience. The purpose of dependency injection is to pass dependencies from outside the class to avoid code coupling which might occur due to the initialization of dependencies inside the class.
The class containing the dependent object is referred to as the “client”, and the class containing the dependency is referred to as the “service”. The objective is to inject dependencies from service to client through an injector so that the client and service classes are not tightly coupled.
To sum it up, dependency injection is a design principle that allows classes to borrow dependencies from external sources instead of complicating the code by creating the dependencies themselves.
What are the types of dependency injection?
There are three types of dependency injection, as follows:
Constructor injection is when a dependency or service is passed through the client class’ constructor. In this case, the dependency has a longer lifetime compared to the client method’s lifetime and can be used across multiple methods. It is important here to prevent the injector from passing null values by using validation code.
Property injection, also known as the setter injection, is when the dependency is passed to the client through the client class’ public property. This type of injection is only applicable when the dependency supplied is optional. Property injection is not a highly recommended option due to the lack of clarity on whether the dependency will be passed or not during class creation. Moreover, property injection leads to a temporal coupling which means the client needs to invoke one member of the class before another. Therefore, critical applications that could potentially suffer from optional dependencies should not be using property injections.
In method injection, an interface is created for the exchange of dependencies. The interface declares a method for passing the dependency, and this interface is implemented by the client class to be used as a base for the injector to pass the dependency. Method injection is flexible since it allows space for the dependency to vary as well as be renewed after use.
Dependency Injection in Angular
Dependency Injection is one of Angular’s primary strength’s and perhaps even its top user selling point. Angular framework has a Dependency Injection (DI) that allows classes to borrow dependencies once instantiated, increasing the modularity and simplicity of an application’s code. Let’s take a rundown through the different elements to have better clarity on what is dependency injection in Angular.
The client class or the consumer is the class that requires the dependency to function. The best part about consumers in Angular’s DI framework is that it can work on top of an encapsulated layer of code logic. It doesn’t need to know how to create dependencies or how they even came to be. Even the classes or methods that are dependent on the consumers have the benefit of encapsulation.
The dependency is the service that is rendered to the client class. A dependency can either have a one-time use or be accessed multiple times across multiple clients. Services that supply dependencies are the strong suit of Angular, and they can readily be availed by various consumers.
The provider is like a registry system that maintains the record of the dependencies and can be used during dependency identification. It uses the DI token during identification. The provider registers dependencies by accepting services and registering them with the injector. The provider is like a guide for the injector and helps it to create instances of services by outlining the instructions necessary for the functioning. Registered services are referred to as singletons which means that the information on instantiating that service exists only with one injector.
The DI token is used to identify providers. It is used during the registration of the dependencies. Three types of tokens are supported by Angular: Type token, String token, and Injection token.
Injectors keep metadata and instructions on how to create services. They are used as a tool to extract information from providers on the whereabouts of unique dependencies. Thereafter, they create instances of the service (dependency) and supply them to the client. Injectors are the core of the DI framework since they store all service and dependency data within them, allowing the consumer or client classes to be crisp, modular and highly readable. They take the load of creating the services from the clients.
These are the high-level and primary elements of Angular which further have several sub-level elements. For a more in-depth understanding, feel free to refer to Angular’s official docs.
Benefits of Dependency Injection in Angular
Angular is a favorite among many developers due to the facility of clean coding and support for modularity. Here are some benefits of dependency injection in Angular that clarify the popular question, why is dependency injection useful?
Avoiding tight coupling
As discussed earlier, encapsulation is a great way to avoid the tight coupling of different modules. The client class is barely aware of how and where the dependencies are created, and also does not have to bother about the code logic. This encourages modularity and easy-to-read code.
Each service that is instantiated, depending on its lifetime, can be reused across several client classes. A library of such services can become extremely useful when the project has to be successively scaled over time.
Imagine going over code that is as simple as reading an instruction manual with clear steps. It has no clutter and just a high-level view of each step. This is not only easy to test with ease but also easily modifiable in case of some bugs. If something needs to be changed, just one tweak in the dependency can reflect the change across all the consumers – just like dominos!
Easier to scale and maintain
Since it is much easier to fix one spot instead of tending to multiple when using dependency injection, it lowers the maintenance cost significantly. Developers can vouch for the fact that development cost is much lower than maintenance cost and it is vital to keep the latter in check. Moreover, as the project grows, due to the library of reusable dependencies, it is easier for developers to pick and implement common code bytes instead of starting from scratch. Also, the ability of services to depend on other services, makes the progressive growth of the project even more manageable.
Dependency injection encourages developer-agnostic code since it is bound with multiple classes that might have been created by different teams of developers. Keeping the dependency code generic is therefore critical, and also beneficial for developers to implement and understand it when and if a project is handed to them midway.
Since Angular does the heavy lifting by taking care of tasks like registering dependencies with the injector and generating services, it is possible to write lightweight code with the least clutter.
Dependency Injection with Code Examples
In the case of layers of dependencies, as was seen in the additional step, the provider is the supplier of all data needed by the injector to create the instances as needed.
To summarize, view the DI framework as a hierarchical system where classes depend on injectors which create services based on the information they have and the requests they receive. The hierarchies are also in terms of service to serve dependencies, enabling a smooth flow of code from the high-level encapsulated level of the consumer to the low-level definitions of dependencies.
Angular is heavily focused on improvising and offering an easy-to-use interface through its DI framework. Even their official documents clearly explain and emphasize why they think dependency injection as a design principle is highly effective.
Dependency injection is one of the best coding practices that allow developers to implement the design principle of separation of concerns. It encourages sustainable, value-adding and independent code that is extremely valued and in high demand. With Angular, implementing dependency injection is made easier due to its swift DI framework that encourages modular and flexible coding. Through this introductory piece, hopefully, you have been encouraged to take a plunge with Angular. After all, it is never too late to make code more crisp and presentable!