Angular Di

Angular Dependency Injection specialist for automated service management and decoupled logic integration

Angular DI is a community skill for mastering dependency injection in Angular applications, covering injection tokens, provider hierarchies, factory providers, multi-providers, and tree-shakable service patterns for modular architectures.

What Is This?

Overview

Angular DI provides patterns for leveraging Angular's dependency injection system effectively. It covers injection tokens for creating typed tokens that decouple implementations from their consumers, provider hierarchies with root, component, and route-level scoping for service lifetime control, factory providers using useFactory for creating services with runtime configuration, multi-providers that collect multiple implementations under a single token for plugin architectures, and tree-shakable providers with providedIn that eliminate unused services from production bundles. The skill enables developers to design loosely coupled, testable architectures using Angular's built-in DI system.

Who Should Use This

This skill serves Angular developers designing service architectures with proper scoping, teams building plugin systems using multi-providers and injection tokens, and engineers optimizing bundle size with tree-shakable provider patterns.

Why Use It?

Problems It Solves

Direct class instantiation creates tight coupling that makes testing and substitution difficult. Service scope confusion causes unintended singleton behavior or unnecessary duplicate instances. Runtime configuration of services requires patterns beyond simple class providers. Plugin architectures need extensibility points that collect multiple implementations dynamically.

Core Highlights

InjectionToken creates typed abstract tokens for decoupled dependencies. Provider hierarchy controls service scope from application root to individual components. Factory providers construct services with runtime configuration and conditional logic. Multi-providers collect implementations for extensible hook points.

How to Use It?

Basic Usage

import {
  InjectionToken, inject,
  Injectable } from
    '@angular/core';

// Typed injection token
interface AppConfig {
  apiUrl: string;
  debug: boolean;
}

const APP_CONFIG =
  new InjectionToken<AppConfig>(
    'app.config');

// Factory provider
function configFactory():
    AppConfig {
  return {
    apiUrl:
      window.location.hostname
        === 'localhost'
      ? 'http://localhost:3000'
      : 'https://api.example.com',
    debug:
      window.location.hostname
        === 'localhost',
  };
}

// Register in app config
// bootstrapApplication(
//   AppComponent, {
//     providers: [
//       { provide: APP_CONFIG,
//         useFactory:
//           configFactory },
//     ],
//   });

// Consuming service
@Injectable({ providedIn:
  'root' })
export class ApiService {
  private config =
    inject(APP_CONFIG);

  async get<T>(
    path: string
  ): Promise<T> {
    const res = await fetch(
      `${this.config.apiUrl}`
      + path);
    return res.json();
  }
}

Real-World Examples

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

// Multi-provider plugin system
interface FormValidator {
  name: string;
  validate(
    value: string
  ): string | null;
}

const FORM_VALIDATORS =
  new InjectionToken<
    FormValidator[]>(
      'form.validators');

const emailValidator:
    FormValidator = {
  name: 'email',
  validate: (v: string) =>
    v.includes('@')
      ? null : 'Invalid email',
};

const lengthValidator:
    FormValidator = {
  name: 'length',
  validate: (v: string) =>
    v.length >= 3
      ? null : 'Too short',
};

// Register validators
// providers: [
//   { provide:
//       FORM_VALIDATORS,
//     useValue: emailValidator,
//     multi: true },
//   { provide:
//       FORM_VALIDATORS,
//     useValue: lengthValidator,
//     multi: true },
// ]

// Consuming component
class FormFieldComponent {
  private validators =
    inject(FORM_VALIDATORS);

  validate(value: string) {
    return this.validators
      .map(
        (v) => v.validate(value))
      .filter(
        (e): e is string =>
          e !== null);
  }
}

Advanced Tips

Use inject() function in constructor-less services and functional guards for cleaner dependency resolution. Create component-scoped providers for services that should have separate instances per component. Use abstract classes as injection tokens when you need both a type and a token in a single declaration.

When to Use It?

Use Cases

Build a plugin system where third-party modules register handlers through multi-providers. Create environment-specific service configurations using factory providers with runtime detection. Implement component-scoped form state by providing services at the component level.

Related Topics

Angular dependency injection, injection tokens, provider scoping, factory providers, and multi-providers.

Important Notes

Requirements

Angular 14 or later for the inject() function. TypeScript for typed injection tokens and interfaces. Understanding of Angular injector hierarchy and resolution order.

Usage Recommendations

Do: use InjectionToken with type parameters for type-safe dependency resolution. Prefer providedIn root for application-wide singletons to enable tree-shaking. Use factory providers for services that need runtime configuration.

Don't: provide services in both root and component levels causing confusing duplicate instances. Use string tokens that lack type safety and are prone to collisions. Create circular dependencies between services without using forwardRef.

Limitations

Multi-providers merge all registrations which can cause unexpected behavior if providers are registered in wrong scopes. Factory providers cannot use inject() before Angular 14 requiring constructor-based injection. Deep injector hierarchies can make it difficult to trace which provider instance is being resolved.