Dependency Injection

Prevent Angular Provider Misuse

Since many Angular applications are standalone these days, we are all used to using different provider functions. The most common ones nearly everyone has seen are provideHttpClient() and provideRouter(). These functions register providers under the hood so that you can use the provided functionalit

Prevent Angular Provider Misuse — Dependency Injection article by Dominik Pieper on Angular In Depth
Prevent Angular Provider Misuse — Dependency Injection article by Dominik Pieper on Angular In Depth
On this page · 2 sections

With the rise of standalone components in Angular, most developers have become familiar with the various provider functions available. Commonly encountered examples include provideHttpClient() and provideRouter(). Internally, these functions register providers, enabling the associated features. Based on their names, one might assume these belong in a component’s providers: [] array. In reality, they are intended for use in environment injectors.

Why this matters

Suppose you are building a custom logger library. At some point, you will likely create a provideLogger() function that registers a configuration provider.

export function provideLogger(config: Partial<LoggerConfig>) {
  return {
    provide: LoggerConfig,
    useValue: config,
  };
}

This function might be used within a component or directive injector without triggering any immediate errors. Over time, however, this can introduce bugs or lead to unpredictable behavior in your library. If provideLogger is designed to establish global logging settings, using it in different components with varying parameters can result in inconsistent logger behavior — even though such usage was never intended.

The provideLogger function used within the AppComponent
The provideLogger function used within the AppComponent

How to fix it

Angular offers the makeEnvironmentProviders function, which returns an EnvironmentProviders type rather than a standard provider array. This type wraps the providers, ensuring that your function is only invoked in appropriate contexts, such as within bootstrapApplication or an ApplicationConfig.

To implement this, update provideLogger to leverage makeEnvironmentProviders:

import { makeEnvironmentProviders } from "@angular/core";

export function provideLogger(config: Partial<LoggerConfig>) {
  return makeEnvironmentProviders([
    {
      provide: LoggerConfig,
      useValue: config,
    },
  ]);
}

Once this change is made, attempting to call provideLogger() inside a component or directive will produce an error. This error promptly indicates that the providers are intended for environment-level configuration and cannot be used there.

provideLogger with makeEnvironmentProviders
provideLogger with makeEnvironmentProviders
Prevent Angular Provider Misuse — figure 3

Prevent Angular Provider Misuse — figure 4
DP
Dominik Pieper

Writes about Dependency Injection. Active 2024.

All 1 article →