1-Dependency Injection: General Perspective
Dependency Injection (DI) is a widely used programming technique in software development. It involves providing a class or object with its dependencies externally, rather than having the class create them internally. This approach makes a class independent of its dependencies and enhances code reusability. DI achieves these goals by decoupling the creation of an object from its usage, allowing dependencies to be replaced without altering the class that uses them.
In complex software development, adopting a modular design is recommended. This involves decomposing functionality into specific modules or classes with well-defined interfaces. Traditionally, modules create their own dependencies, leading to tightly coupled and less maintainable code. Dependency Injection (DI) addresses this by resolving dependencies at runtime instead of compile time. Rather than creating their own dependencies, objects rely on an external entity to provide and inject these dependencies.
The Single Responsibility Principle (SRP) states that each software module should have one and only one reason to change.
— Robert C. Martin
2-Dependency Injection in Angular
Angular implements the Dependency Injection (DI) principle to facilitate the development of modular and scalable applications. With DI, components, services, and other objects can be loosely coupled, making it easier to promote code reuse and test. Angular applications become more flexible and easier to refactor.
How it works in Angular
I find Angular's documentation very clear, and I recommend checking it out if you want to delve deeper. click-here
Two main roles exist in the DI system: dependency consumer and dependency provider. By adding the @Injectable decorator, a class becomes a provider and can be injected as a dependency
@Injectable()
class FrstService {...}
After defining a dependency, the next step is to make it accessible within the Dependency Injection (DI) system. This can be achieved by providing the dependency, which can be done in multiple locations:
- At the Component level, using the
providers
field of the@Component
decorator. In this case theFirstService
becomes available to all instances of this component. For example:
@Component({
providers: [FirstService]
})
class FirstComponent {}
At the application level, using the providers
field of the ApplicationConfig
object passed to the bootstrapApplication
function to provide a service or other Injectable
. Here, the FirstService
is available to all components, directives, and pipes declared in the NgModule. Registering a provider in the ApplicationConfig ensures that the same instance of a service is available to all relevant components, directives, and pipes.
Here, in app.module.ts
:
Next:
const appConf: ApplicationConfig = {
providers: [
{ provide: FirstService }
]
}
Then, in main.ts
:
bootstrapApplication(AppComponent, appConf)
- At the application level, you enable injection into other classes throughout the application. This is accomplished by adding the providedIn: 'root' field to the @Injectable decorator (which is the option by default when you generate a new service).
ng generate service Services/FirtService
import { Injectable } from '@angular/core';
@Injectable({
// declares that this service should be created
// by the root application injector.
providedIn: 'root',
})
export class FirstService {
// methods here ..
}
Injecting a dependency
The most common way to inject a dependency is by declaring it in a class constructor. When Angular instantiates a new instance of a component, directive, or pipe class, it identifies the required services or dependencies by examining the constructor parameter types. For example, if the FirstComponent
requires the FirstService
, the constructor might resemble this :
@Component({ … })
class FirstComponent {
constructor(private service: FirstService) {}
}
Dependency Injection is commonly used in Angular applications for various purposes. For example, services are often injected into components to encapsulate business logic and data retrieval operations. This separation of concerns allows components to focus on presentation logic while delegating data manipulation to services. Additionally, DI enables the use of PaaS provided by Angular, such as the HttpClient for making HTTP requests or the Router for navigation. The benefits of DI include improved code maintainability, testability, and scalability. By separating concerns and promoting code reuse, DI helps developers build robust apps.