DEV Community

Cover image for Mastering Dependency Injection in Angular
VARNIT JAIN
VARNIT JAIN

Posted on

Mastering Dependency Injection in Angular

Dependency Injection (DI) is a design pattern that's critical to building scalable and maintainable applications in Angular. Yet, for many developers new to Angular, DI can be a bit of a mystery. In this blog post, we'll break down what DI is, why Angular uses it, and how you can implement DI in your applications for efficient, clean, and scalable code.

1. What is Dependency Injection?

Dependency Injection is a design pattern that deals with how components acquire dependencies. In simpler terms, if a component needs a service to perform its operations, DI allows Angular to "inject" that dependency into the component instead of the component having to create
This approach has a few major benefits:

  • Modularity: Components and services are loosely coupled and modular.

  • Testability: Code is easier to test because you can mock dependencies.

  • Flexibility: Dependencies can be swapped without changing code in the component.

2. How Angular Implements DI

Here's a detailed blog draft you can post on Medium, covering an overview of Angular's Dependency Injection (DI) system — an essential yet often misunderstood concept in Angular development.

Mastering Dependency Injection in Angular: A Guide for Beginners
Dependency Injection (DI) is a design pattern that's critical to building scalable and maintainable applications in Angular. Yet, for many developers new to Angular, DI can be a bit of a mystery. In this blog post, we'll break down what DI is, why Angular uses it, and how you can implement DI in your applications for efficient, clean, and scalable code.

  1. What is Dependency Injection? Dependency Injection is a design pattern that deals with how components acquire dependencies. In simpler terms, if a component needs a service to perform its operations, DI allows Angular to "inject" that dependency into the component instead of the component having to create it.

This approach has a few major benefits:

Modularity: Components and services are loosely coupled and modular.
Testability: Code is easier to test because you can mock dependencies.
Flexibility: Dependencies can be swapped without changing code in the component.

  1. How Angular Implements DI Angular uses DI extensively through its injector system, which is part of Angular’s core. The injector is responsible for:
  • Providing dependencies to components and services.

  • Managing dependency lifecycles.

In Angular, dependencies are usually provided at the module level or component level and are registered with a @Injectable decorator. The injector resolves dependencies by looking at a component’s or service’s constructor to determine which dependencies are required.

3. Basic Setup: Creating a Service with DI

Let’s create a simple service in Angular to demonstrate how DI works. In this example, we'll create a UserService that will be injected into a UserComponent.

Step 1: Create the Service
To create the service, run:

ng generate service user
Enter fullscreen mode Exit fullscreen mode

This generates a user.service.ts file, which should look something like this:

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

@Injectable({
  providedIn: 'root',
})
export class UserService {
  constructor() { }

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

Enter fullscreen mode Exit fullscreen mode

Here, the @Injectable decorator marks UserService as a service that can be injected. The providedIn: 'root' tells Angular to provide this service at the root level, meaning it will be a singleton (only one instance will exist).

Step 2: Injecting the Service into a Component
To inject this service, we’ll add it to our UserComponent as a dependency. Let’s assume we have a user.component.ts file that looks like this:

import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';

@Component({
  selector: 'app-user',
  template: `
    <p>User: {{ user?.name }}</p>
  `,
})
export class UserComponent implements OnInit {
  user: any;

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.user = this.userService.getUser();
  }
}

Enter fullscreen mode Exit fullscreen mode

4. Configuring Injection Scopes

Angular allows you to specify different injection scopes, which can be quite powerful:

  • Singleton Services: Use the providedIn: 'root' syntax to ensure the service is a singleton across the application.

  • Component-scoped Services: If you specify a service at the component level, it will be created specifically for that component instance, offering a unique instance per component.

To register a service only for a specific component, provide it in the providers array in the component metadata:

@Component({
  selector: 'app-custom',
  providers: [CustomService],
})
export class CustomComponent { /*...*/ }

Enter fullscreen mode Exit fullscreen mode

5. Advanced DI Patterns: Factory Providers and Injection Tokens

Factory Providers
Factory providers are useful when you need to create a dependency based on some runtime logic. Here’s a simple example:

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

@Injectable({
  providedIn: 'root',
})
export class LoggerService {
  constructor(private level: string) {}

  log(message: string) {
    if (this.level === 'debug') {
      console.log(message);
    }
  }
}

export function loggerFactory() {
  return new LoggerService('debug');
}

Enter fullscreen mode Exit fullscreen mode

Then provide it in a module or component using the useFactory syntax:

@NgModule({
  providers: [
    { provide: LoggerService, useFactory: loggerFactory },
  ],
})
export class AppModule { }

Enter fullscreen mode Exit fullscreen mode

Injection Tokens
Injection Tokens are particularly useful when you have multiple services of the same type or need to inject non-class values, like configuration settings.

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

export const API_URL = new InjectionToken<string>('API_URL');

@NgModule({
  providers: [
    { provide: API_URL, useValue: 'https://api.example.com' },
  ],
})
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

You can then inject API_URL into any component or service as you would a class-based service.

Conclusion

Dependency Injection is a powerful tool in Angular that helps keep your code clean, modular, and testable. Understanding how DI works — from simple service injection to advanced patterns like factory providers and injection tokens will greatly enhance your ability to write scalable and maintainable Angular applications.

Top comments (0)