Dependency injection in Angular

Gurinderpal Singh Narang
4 min readJan 25, 2024

--

In Angular, dependencies refer to services and objects that a class requires to execute specific tasks. Dependency Injection (DI) is a coding pattern in which a class requests dependencies from an external source instead of creating them internally.

Now let’s discuss Dependency Injection (DI) in the context of an Angular application, where components, services, directives, and other types of objects/classes often depend on each other. Dependency injection (DI) in Angular provides a way to create and inject dependencies into components or services, making it easier to manage the relationships between different parts of our application.

To understand Dependency Injection (DI), let’s first discuss what a Provider and an Injector are.

Provider

A provider is an instruction to the Dependency Injection(DI) system on how to obtain a value for a dependency. A provider is a configuration that tells Angular Dependency Injection(DI) how to create and provide an instance of certain objects/dependencies. Most of the time, these dependencies are services that we create and provide.

@NgModule({
providers: [AnyService],
})

A provider implements the Provider interfaces. A provider object defines how to obtain an injectable dependency associated with a DI token. An injector uses the provider to create a new instance of a dependency for a class that requires it.

We can register the dependency in providers:[] metadata in 2 ways:

1. By directly registering the providers: [] of the NgModule or component or directive

@NgModule({
providers: [
MyService,
// other providers for this module
],
})
@Component({
selector: 'app-my-component',
template: '<p>Providers</p>',
providers: [
MyService,
// other providers for this component
],

2. We can also use providedIn property of the @Injectable decorator. providedIn syntax allows us to create what is known as a tree-shakable provider.

import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root', // Provided at the application level as a singleton
// or specify a specific module: MyModule
})
export class MyService {
// service implementation
}

Injector

An injector is an object in the Angular Dependency Injection(DI) system that can find a named dependency in its cache or create a dependency using a configured provider.

1. An injector provides a singleton instance of a dependency and can inject this same instance in multiple components.

// logger.service.ts
import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root', // Provided at the application level as a singleton
})
export class LoggerService {
log(message: string): void {
console.log(message);
}
}

In this example, LoggerService is marked with @Injectable({ providedIn: 'root' }). As a result, Angular provides a single instance of LoggerService for the entire application.

2. A hierarchy of injectors at the NgModule and component level can provide different instances of dependency on their own components and child components.

// module-b.module.ts
import { NgModule } from '@angular/core';
import { LoggerService } from './logger.service';

@NgModule({
providers: [
// ModuleB provides its own instance of LoggerService
LoggerService,
],
})
export class ModuleB { }

In the above example, ModuleB provides its own instance of LoggerService. All the components or services within ModuleB will also share the same instance.

3. We can configure injectors with different providers that can provide different implementations of the same dependency.

providers: [
{ provide: LoggerService, useClass: CustomLoggerService },
],
// logger.service.ts
import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root', // Provided at the application level as a singleton
})
export class CustomLoggerService {
log(message: string): void {
console.warn("This log is coming from the custom logger Service", message);
}
}

Now the LoggerService will refer to CustomLoggerService . This demonstrates how we can configure different providers to supply different implementations of the same dependency in Angular through dependency injection, whenever we inject LoggerService (which is now an instance of CustomLoggerService) and use it to log a message.

In a Dependency Injection (DI) system, two main roles exist, the dependency consumer and the dependency provider. These roles help establish a clear separation of concerns and contribute to the decoupling of components within a software system.

Dependency Consumer:

  • The dependency consumer is a component or class that depends on another service or object to perform certain tasks.
  • It doesn’t create instances of its dependencies but relies on the Dependency Injection (DI) system to provide those instances.
  • In Angular, components, services, and other objects often act as dependency consumers. They declare their dependencies in their constructors and rely on the Dependency Injection (DI) system to inject the appropriate instances.

Example (Angular component as a dependency consumer):

import { Component } from '@angular/core';
import { LoggerService } from './path-to-logger-service';

@Component({
selector: 'app-my-component',
template: '<button (click)="logMessage()">Log Message</button>',
})
export class MyComponent {
constructor(private logger: LoggerService) {}

logMessage(): void {
this.logger.log('Hello, Dependency Consumer!');
}
}

Dependency Provider:

  • The dependency provider is responsible for creating and configuring instances of a particular service or object.
  • In a Dependency Injection (DI) system, providers register the services they can create, allowing the Dependency Injection (DI) container to inject instances when requested.
  • Providers can specify alternative implementations or configurations for services, allowing for flexibility and modularity.

Example (Angular module providing a service):

import { NgModule } from '@angular/core';
import { LoggerService } from './path-to-logger-service';
import { CustomLoggerService } from './path-to-custom-logger-service';

@NgModule({
providers: [
{ provide: LoggerService, useClass: CustomLoggerService },
],
// other module metadata...
})
export class MyModule { }

In the above examples, MyComponent is a dependency consumer that relies on the LoggerService. The MyModule acts as a dependency provider by configuring the DI system to use CustomLoggerService whenever an instance of LoggerService is requested.

In summary, Dependency Injection(DI) is a mechanism for creating and delivering some parts of an application (dependencies) to other parts of an application that require them. This helps achieve a more modular and maintainable codebase. Components remain focused on their specific tasks without worrying about how to create or configure their dependencies, and the DI system takes care of providing the necessary instances.

Thanks for reading!!!

--

--