XenonStack Recommends

Data Visualization

Angular Dependency Injection | A Quick Guide

Navdeep Singh Gill | 30 August 2024

Angular Dependency Injection

Introduction to Angular Dependency Injection

Dependency Injection is a core part of Angular and one of its most significant features. It refers to a technique in which a class receives its dependencies from external sources rather than creating them. The dependencies being referred to here are the objects or the services that a class needs, and so the Dependency Injection system is the technique by which a class requests these dependencies. So in Angular, the Dependency Injection framework provides dependencies to a class upon instantiation, and classes receive the external logic without knowing how to create it.

All the Angular developers out there who are now moving on to React or the one who wants hands-on experience on React. Click to explore about, Learn React | Advanced Guide for Angular Developers

The critical benefactor of Dependency Injection is classes. Along with the services, directives, pipes, components, and every such schematic in Angular benefit from Dependency Injection in some way or another. When developing a small part of the application, such as class, we might require external dependencies such as an HTTP service that makes backend calls. In this case, one way would be to create our dependency whenever we need it.

But there are specific problems with this approach. First, it is tough to test. Also, here, this class knows how to create its dependencies, but it also knows about its dependencies. So basically, it is impossible to replace this dependency at runtime.
Now, if we look at the alternate version of the code where the class does not know how to create its HTTP dependency and receives all the dependencies that it needs as input arguments, it becomes accessible to:

  • Replace the implementation of a dependency for testing.
  • To support multiple runtime environments.
  • To give new versions of a service to a third-party, which uses our service in their code base, .etc.

Why do we use Angular Dependency Injection?

One might think about why we use Dependency Injection in Angular and its core part. So in more generic terms, the Dependency Injection increases flexibility and modularity in the applications. Using it in the application makes the code flexible, testable, and mutable. The benefits could be further explained in the following key points.

Loosely Coupled

The component gets loosely coupled to the injected dependency with Dependency injection, such as a service. It just means that the component itself does not know how to create the dependency, and actually, it does not know anything about it. It just works with the dependency passed onto it.

Easier to Test

It becomes easier to test an injected component because it no longer depends on a specific implementation of the dependency, such as a service. It will work with any implementation of dependency that is passed on to it. We can create a mock service class and pass it while testing.

Component Reusability

Reusing the component becomes easier. Our component will now work with any service or dependency with dependency injection as long as the interface is honored. So our component becomes testable, maintainable, etc., because of the Dependency injection pattern. But how do we create an instance of service and pass it to the component? That is what's done by Angular Dependency Injection.

An architecture containing tools, libraries, and functionalities suitable to build and maintain massive web projects using a fast and efficient approach. Click to explore about, Python Flask Framework

How to build Angular Dependency Injection framework from scratch?

Dependency injection in Angular is implemented by the Angular Dependency Injection framework that creates and maintains the dependencies and then injects them into the components, directives, or services.

The Angular Dependency Injection framework comprises five key players that work together.

  • Consumer: The class, i.e., Component, Directive, or Service that needs the dependency and to which the dependency is injected is known as a consumer.
  • Dependency: The service we want in our consumer or the actual code we want to be injected is the Dependency.
  • Injection Token (DI Token): The Injection Token (DI Token) uniquely identifies something that we want to be injected, in other words, a Dependency. We use DI Token when we register dependency of our code.
  • Provider: The Providers maintain the list of Dependencies and their Injection Token, so it serves as a map between them. It uses the Injection Token to identify the Dependency.
  • Injector: An injector is a function that returns a dependency when a token is passed to it. It holds the Providers and is responsible for resolving the dependencies and injecting the Dependency to the Consumer. The Injector searches for Dependency in the Providers using Injection Token. Then it creates an instance of the dependency and injects it into the consumer.

To understand the dependency injection in Angular, we can try setting up Dependency Injection from scratch, and the first step is to add a Provider.

A way of writing applications using only pure functions and immutable values. Click to explore about, Functional Programming

What is an Angular Dependency Injection Provider?

If we create a class and try to inject this class into the Constructor of another class as a dependency, we get an error. It implies that our class is not linked to the Angular dependency injection system, and Angular needs to know how to create this dependency. So, for this, we need a provider.

The Angular dependency Injection Provider is a map between a token and a list of dependencies. It maintains the list of dependencies along with the injection token. For Angular to know how to create a dependency, it needs what is known as a provider factory function. It is simply an essential function that Angular can call to create a dependency. That provider factory function can even be created implicitly by Angular using some simple conventions, which usually happens for most of our dependencies.

So in the Providers metadata of the injector, the dependencies are registered. We can understand this with an example. So in the example, the service is registered with the injector of the AppComponent. The above example is the shorthand notation for the following syntax.

providers :[{ provide: ProductService, useClass: ProductService }]

The above syntax has two properties.

Provide

The first property is Provide holds the Token or DI Token. The Token can be either a type, a string, or an instance of InjectionToken. The Injector uses the token to locate the Provider in the Providers array.

Provider

The second property is known as the Provider definition object. It shows Angular how to create the instance of the dependency. Angular creates the instance of dependency in four different ways.

  • Creating a dependency from the existing service class (useClass).
  • Injecting a value, array, or object (useValue).
  • Using a factory function returns the service class or value (use factory).
  • I am returning the instance from an already existing token (use existing).

And we can also add the Services to the Providers array of the NgModule. This way, they are available to be used in all components and services of application as they are added at the module level. But we can also write that function ourselves if needed.

Asking for Dependency in the Constructor

The Components, Directives & Services (Consumers) declare the dependencies they need in their Constructor.

constructor(private productService:ProductService) {
}

The injector reads the dependencies via the Constructor of the Consumer. After the injector reads the dependencies, it looks for that dependency in the Provider. The Provider then provides an instance for dependency and injector and injects it into the consumer.

How to write our Provider?

To set up Angular dependency injection from scratch and understand the framework better, we can write our provider factory function.

The Provider Factory function is just a simple function that inputs any dependencies that the consumer needs. It calls the Constructor of the consumer, passes all the needed dependencies, and returns the new CoursesService instance as the output. But then there is a problem, how will the Angular dependency injection system know about this function, and how will it know that it needs to call it to inject a particular dependency. So there comes the role of Injection tokens.

We have to define the following things when we manually configure a provider:

  • useFactory: This contains a reference to the provider factory function that Angular calls whenever it needs to create dependencies and then injects them
  • Provide: This consists of the injection token linked to the dependency. The injection token helps Angular determine when a given provider factory function should be called.
  • deps: This is an array that contains any dependencies that the useFactory function needs to run, for example, an HTTP client in the case of a service that makes a call to the backend

Simplified Configuration of Providers: useClass

There is another way to explicitly define a provider factory function instead of a provider factory function with the help of useFactory, which is the useClass property. We use the Class Provider useClass whenever we need to provide an instance of the provided class. The type passed to use class helps the injector create a new instance and then injects it. When using the use class property, Angular will know that the value that we are passing is a valid constructor function:

Galen Framework has its special language Galen Specs for describing the positioning and alignment of elements on a Web page. Click to explore about, Responsive Design Testing with Galen Framework

What is Injection Token?

So how does Angular know what has to be injected where and which provider factory function needs to be called to create which dependency? The injection tokens provide this. An injection token is basically what identifies something that we want to be injected and uniquely identifies a category of dependencies. If we try to configure it manually, This token injection object will identify our dependency in the dependency injection system. It is an object, and it's unique, unlike, for example, a string.

So this token object helps Angular to identify a set of dependencies uniquely. We usually declare the Provider in Provider's metadata. This is what it looks like.

providers :[{ provide: ProductService, useClass: ProductService }]

The syntax consists of two properties that are provided (provide: ProductService) & Provider (use class: ProductService) The first property, Provide, has the DI Token that acts as a key. The Dependency Injection systems require this key to locate the Provider in the Providers array.

Creating an Injectable Service and Injecting services

Dependency Injection is wired into the Angular framework. It provides components with the injectable services, i.e., services could be injected into the component giving the component access to that service class.  So now, to understand and relate to all the topics covered so far, let's understand the Dependency Injection flow with an example by creating an injectable service and then injecting that service into a class.

Creating an Injectable Service

Let's consider a service that provides users data. To generate a new UserService class, use the following Angular CLI command.

ng generate service users/user

To define a class as a service in Angular, we use the @Injectable() decorator that provides the metadata, allowing Angular to inject it into a component as a dependency. Similarly, we use the @Injectable() decorator to indicate that a component or other class (such as another service, a pipe, or a NgModule) has a dependency.

When a new instance of a component class is created, the dependencies needed by this component class are determined by looking at the constructor parameter types. For example, the Constructor of UsersListComponent needs UsersService.
constructor(private service: UsersService) { }

When a component depends on service, Angular first checks if its injector consists of any existing instances, if the service which is requested does not exist yet, it is made by the injector using a registered provider and then adds it to the injector before service is returned to Angular.

When all requested services are returned, Angular calls the component's Constructor and those services as arguments.

Injecting Services

Now that we've created an injectable service, we can inject the service in a component's Constructor () by supplying a constructor argument along with the dependency type. We can understand this by injecting our UsersService in the component as follows, in which the UsersService is specified in the UsersListComponent Constructor.
constructor(userService: UserService) { }

What is Angular Hierarchical Dependency Injection?

We notice that in Angular, we specify the providers for the dependencies at multiple places such as:

  • At the level of a module
  • At the level of a component
  • At the level of a directive

We can do this because the Angular dependency system is hierarchical. Now let's understand what it means.

Whenever we need a dependency somewhere, Angular tries to find a provider for that dependency in the list of dependencies of that component. If Angular cannot find a provider at the component level, it tries to find a provider in the parent component. Once found, it instantiates and uses it, but if not found, it searches it in the parent of the parent and so on until the root component of the application is reached. If no provider is found in the entire process, we get the error message as 'No provider found.'

The hierarchical dependency injection allows us to build our application more modularly. With the hierarchical structure of DI, we can isolate sections of the application and give them their private dependencies or have parent components that only share specific dependencies with their child components and not with the rest of the component tree, etc.

Java vs Kotlin
Our solutions cater to diverse industries with a focus on serving ever-changing marketing needs. Click here for our Custom AngularJS Development Services

Conclusion

We can now state that the Angular Dependency Injection system is flexible, with many configuration options, and along with that, it's super easy to use. This Dependency Injection paradigm works through a hierarchy of injection. Injectors receive the instructions, and then they instantiate the requested dependency. So the classes receive dependencies externally rather than creating them or knowing about them.

And as the Angular DI system is hierarchical, which means it contains two separate injection hierarchies (components and modules). It makes it easy to define in a very fine-grained way which dependencies are visible in which parts of the application and which dependencies are hidden.