Dependency injection in Angular

Dependency injection is a design pattern used in object-oriented programming, where the dependencies of a class are injected into it instead of being created inside it. Angular supports this design pattern and you can use it in your applications to increase flexibility and modularity.

Prerequisites

You should be familiar with the Angular apps in general, and have the fundamental knowledge of component, directives and NgModules.

Understanding dependency injection

Introduction to Dependency Injection (DI)

Dependency injection, or DI, is one of the fundamental concepts in Angular. the DI is wired into the Angular framework and allows classes with Angular decorators (decorator pattern allows a user to add new functionality to an existing object without altering its structure. This type of design pattern comes under structural pattern as this pattern acts as a wrapper to existing class), such as components, Directives, Pipes, and Injectables, to configure dependencies that they need.

The Decorator Pattern

The decorator pattern allows a user to add new functionality to an existing object without altering its structure. This design pattern is a structural pattern as it acts as a wrapper to an existing class.

Roles in the DI System

Two main roles exist in the DI system: dependency consumer and dependency provider

A dependency consumer is typically a component or service that requires an instance of another service or component to provide a specific functionality.

A dependency provider is a token used to register a service or other dependency with Angular’s injector. The injector is responsible for creating instances of the dependency and injecting them into components, services, and other classes as needed.

The Injector

Angular facilitates the interaction between dependency consumers and dependency providers using an abstraction called Injector. When a dependency is requested, the injector checks its registry to see if there is an instance already available there. If not, a new instance is created and stored in the registry. Angular creates an application-wide injector (also known as “root” injector) during the application bootstrap process, as well as any other injectors as needed. In most cases you don’t need to manually create injectors, but you should know that there is a layer that connects providers and consumers.

For example:

import { Component } from '@angular/core'; 
import { DataService } from 'data.service';
...
@Component({
 selector: 'app-root',
 template:
  <ul>
   <li *ngFor="let item of items">{{ item }}</li> 
  </ul>
 providers: [DataService]
})
export class AppComponent {
 items: string[] = [];

 constructor(private dataService: DataService) {}

 ngOnInit() {
  this.items = this.dataService.getItems();
 }
}

The AppComponent class is the dependency consumer, as it depends on the DataService to provide data to display.

The AppComponent is annotated with the @Component decorator, which includes a provider array which specifies that the DataService should be provided as a dependency for this component. This means that an instance of the DataService will be created and injected into the AppComponent when it is instantiated.

in the ngOnInit method, the getItems method of the DataService is called to fetch the items to display in the template. The items property of the AppComponent is then assigned returned data. By using a service to provide the data, the AppComponent is decoupled from the details of how the data is retrieved, which allows it to be easily swapped out with another implementation in the future.

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

@Injectable({
 providedIn: 'root'
})
export class DataService
 private items = ['Item I', 'Item 2', 'Item 3'];

getItems(): string[] {
  return this.items;
 }
}

@Injectable Decorator

The DataService is annotated with the @Injectable decorator. Which tells Angular that this class can be used as a provider for a dependency. The providedIn: ‘root’ option specifies that this service should be provided at the root level of the application. Which means that it will be available for injection throughout the app. This approach is often preferred because it provides a single instance of the service. We can share across the entire application.

Configuring Dependency Providers

You can configure dependency providers at both the component and module levels. When you provide a service at the module level. It is available for injection throughout the entire module. While providing it at the component level limits its scope to that component and its child components.

In the example above, the DataService is provided at a component level by adding it to the providers array in the @Component decorator.It means that the DataService can only be injected and used with the AppComponent and its child components.

To configure dependency providers at the module level. You could, for example, do this:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; 
import { DataService } from './data.service'; 
import { AppComponent } from './app.component';

...
@NgModule({
 imports: [BrowserModule], 
 declarations: [AppComponent],
 providers: [DataService], 
 bootstrap: [AppComponent]
}) 
export class AppModule { }

The DataService is provided at the module level by adding it to the providers array in the @NgModule decorator. This means that any component within this module can inject the DataService and use it to access data.

Conclusion

Dependency injection is a powerful feature of Angular that makes it easy to manage dependencies between components and services. By relying on the Angular injector to provide objects and configuration options. Developers can write more modular, maintainable code that is easier to test and refactor.

Would you like to read more articles by Tekos’ Team? Everything’s here.

Author

Hieu Bui Avatar

Leave a comment

Your email address will not be published. Required fields are marked *

Comment
Name
Email
Website

Skip to content