Frontend Patterns

Pattern

Dependency Injection

Provide dependencies from the outside rather than creating them internally for better testability.

State Management Intermediate

Dependency Injection

Problem

Components that instantiate their own dependencies are impossible to test in isolation. You can’t test a component that hardcodes API calls without hitting real endpoints. You can’t swap a real analytics service for a mock one. Every test requires complex setup to stub global dependencies or mock modules.

Solution

Pass dependencies as parameters or props instead of instantiating them within components. This makes dependencies explicit, simplifies testing by allowing mock injection, and improves flexibility by letting different contexts provide different implementations.

Example

This example demonstrates dependency injection by passing an API client as a constructor parameter rather than hardcoding it, enabling easy testing with mocks.

// Component accepts dependency via constructor instead of creating it internally
class UserProfile extends HTMLElement {
  constructor(api = defaultApi) {
    super();
    // Dependency is injected, making it easy to swap implementations
    this.api = api;
    this.user = null;
  }

  async connectedCallback() {
    // Use injected API to fetch data
    this.user = await this.api.getUser();
    this.render();
  }

  render() {
    this.innerHTML = `<div>${this.user?.name || ''}</div>`;
  }
}

// In tests, inject a mock API instead of the real one
const mockApi = { getUser: () => Promise.resolve({ name: 'Test User' }) };
const component = new UserProfile(mockApi);

Benefits

  • Makes dependencies explicit and visible in component signatures.
  • Dramatically simplifies testing by allowing mock injection.
  • Enables different contexts to provide different implementations.
  • Reduces tight coupling between components and their dependencies.
  • Makes components more reusable across different environments.

Tradeoffs

  • Can lead to prop drilling if dependencies need to pass through many layers.
  • Adds verbosity to component APIs with additional parameters.
  • May require context or provider patterns to avoid excessive prop passing.
  • Can make components feel less self-contained and autonomous.
  • Requires discipline to avoid mixing injected and hardcoded dependencies.
Stay Updated

Get New Patterns
in Your Inbox

Join thousands of developers receiving weekly insights on frontend architecture patterns

No spam. Unsubscribe anytime.