Dependency Injection in Angular

Inthe underlying design of Angular, Dependency Injection (DI) is an indispensable device that makes programs more maintainable, testable, and flexible. It’s a pattern by which objects do not put together their own dependencies but receive these dependencies from an external source. In Angular, DI is extensively used to manage services, components, and other injectables. Today, we are going to fully introduce it.

What is Dependency Injection?

At its core, Dependency Injection is about the separation of a component's creation from its behavior. This separation promotes loose coupling between components, enabling them to be more easily tested, reused in the future, and sustained over time.

For example, let's say we have a service named UserService that gets user data from an API request. If we follow the traditional approach and hardcode the service right inside a component, then inject it this way, the component can access all the services it needs without needing to know how those services get started or live—just use them!

How Angular Implements DI

  • Angular has its own dependency injection system, which extends by adding pieces. This is how it works:
  • Providers: Providers create instances of dependents. They can be set at different levels:
  • Root level: The whole application can see them.
  • Module Level: Available within a particular module.
  • Component Level: Available only within a component and its children.
  • Injectors: When a component asks for a dependent entity, Angular will consult its injector sequence to resolve that entity.
  • Tokens: A unique identifier for each of the injected elements. Tokens can be simple strings or custom objects for more flexibility.
  • Practical Example: Using Dependency Injection in Angular

Step 1: Create a Service

First, create a UserService that fetches user data.

// user.service.ts

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

@Injectable({

  providedIn: 'root', // This makes the service available app-wide

})

export class UserService {

  getUser() {

    return { id: 1, name: 'John Doe' };

  }

}

With the @Injectable decorator, we define the Service class, and providedIn: 'root' ensures that the service is available to the whole application.

Step 2: Use the Service in a Component

Inject UserService into a component.

// app.component.ts

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

import { UserService } from './user.service';

@Component({

  selector: 'app-root',

  template: `

    <h2>User Details</h2>

    <p>ID: {{ user?.id }}</p>

    <p>Name: {{ user?.name }}</p>

})

export class AppComponent {

  user: any;

  constructor(private userService: UserService) {

    this.user = this.userService.getUser();

  }

}

In the constructor, Angular's DI system automatically provides the service instance, avoiding manual instantiation.

Step 3: Run the Application

When the app runs, the AppComponent will display user details fetched by UserService.

Advanced DI Features

Angular offers advanced features to improve dependency injection:

1. Custom Tokens

You can use custom tokens to inject values that are not class types.

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

export const APPCONFIG = new InjectionToken('app.config');

@Injectable()

export class ConfigService {

  constructor(@Inject(APPCONFIG) private config: string) {}

  getConfig() {

    return this.config;

  }

}

// Provide the token in your module

@NgModule({

  providers: [

    ConfigService,

    { provide: APPCONFIG, useValue: 'production' },

  ],

})

export class AppModule {}


2. Factory Providers

Use factory providers when you need to create a dependency dynamically.\

function createAuthServiceMock() {

  return { isLoggedIn: true };

}


@NgModule({

  providers: [

    { provide: AuthService, useFactory: createAuthServiceMock },

  ],

})

export class AppModule {}

3. Hierarchical Injectors

Angular supports hierarchical injectors, allowing dependencies to be overridden at different levels in the application tree. For example, a service can be provided at the component level to override a global instance.

Best Practices for Working with DI

Use Services for Shared Logic: Keep your components short and simple. Move shared logic into services for better reusability and testability.

Avoid Circular Dependencies: Circular dependencies arise when two services depend on each other. Refactor your code to remove interdependencies.

Provide Dependencies at the Right Level: Provide dependencies at the smallest necessary scope. If a service is only used by a single component, provide it at the component level rather than the root.

Use Aliases for Clarity: When injecting multiple services, use descriptive variable names for better readability.

Test Your Services: Services should be self-contained and independently testable. Write unit tests to verify their behavior.

Dependency Injection is an essential part of Angular. It helps keep dependencies manageable, testable, and scalable. Whether you’re building small components or large-scale applications, using DI correctly ensures a well-structured and maintainable project.

Instance Of Java

We are here to help you learn! Feel free to leave your comments and suggestions in the comment section. If you have any doubts, use the search box on the right to find answers. Thank you! 😊
«
Next
Newer Post
»
Previous
Older Post

No comments

Leave a Reply

Select Menu