AngularJSProviders & Injectors

Providers & Injectors

A provider tells Angular's DI system how to create a value when something requests it. While providedIn: 'root' handles the most common case, Angular has a rich set of provider types that cover every scenario — using factory functions, existing values, class aliases, and more.

The Five Provider Types

Provider

Use When

useClass

Provide a class, optionally substituting a different class

useValue

Provide a plain value (string, number, object, function)

useFactory

Provide a value computed at runtime from other dependencies

useExisting

Create an alias — two tokens that resolve to the same instance

providedIn

Shorthand on @Injectable — no explicit provider needed

useClass

The most common provider type. It creates a new instance of the specified class:

TS
// Basic: provide the class itself
{ provide: UserService, useClass: UserService }
// equivalent to just adding UserService to providers[] in most cases

// Substitute: provide an alternative implementation
{ provide: LoggingService, useClass: ConsoleLoggingService }

// Override for testing or environment-specific behavior
{ provide: AuthService, useClass: MockAuthService }

// Real example: swap the logger in development vs production
const loggingProvider = {
  provide: LoggingService,
  useClass: environment.production ? NoopLogger : ConsoleLogger,
};

TS
// Define the interface / base class
abstract class LoggingService {
  abstract log(message: string): void;
}

@Injectable()
class ConsoleLogger extends LoggingService {
  log(message: string) { console.log(`[LOG] ${message}`); }
}

@Injectable()
class NoopLogger extends LoggingService {
  log(_: string) { /* intentionally empty in production */ }
}

// Bootstrap the app with the right implementation
bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: LoggingService,
      useClass: environment.production ? NoopLogger : ConsoleLogger,
    },
  ],
});
useValue

useValue provides a static value — an object literal, string, number, or even a function. No class instantiation occurs:

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

// Create a typed token
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');

export interface AppConfig {
  apiUrl: string;
  maxRetries: number;
  featureFlags: Record<string, boolean>;
}

// Provide the value
bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: APP_CONFIG,
      useValue: {
        apiUrl: 'https://api.myapp.com/v1',
        maxRetries: 3,
        featureFlags: {
          darkMode: true,
          betaFeatures: false,
        },
      },
    },
  ],
});

TS
// Inject and use it
@Injectable({ providedIn: 'root' })
export class ApiService {
  private config = inject(APP_CONFIG);

  get(path: string) {
    return this.http.get(`${this.config.apiUrl}${path}`);
  }
}
useFactory

useFactory calls a function to produce the dependency. Use it when the value needs to be computed at runtime based on other dependencies or environment conditions:

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

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

// Factory receives other injectables via the 'deps' array
bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: LOCALE_ID_TOKEN,
      useFactory: () => {
        // Determine locale from browser or user preferences
        const saved = localStorage.getItem('locale');
        return saved ?? navigator.language ?? 'en-US';
      },
    },
  ],
});

TS
// Factory with dependencies using the inject() function (Angular 14+)
export const DATABASE_TOKEN = new InjectionToken<Database>('Database');

bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: DATABASE_TOKEN,
      useFactory: () => {
        const config = inject(APP_CONFIG);
        const logger = inject(LoggingService);
        return new Database(config.apiUrl, logger);
      },
    },
  ],
});

TS
// Traditional deps array (pre-Angular 14)
{
  provide: DATABASE_TOKEN,
  useFactory: (config: AppConfig, logger: LoggingService) => {
    return new Database(config.apiUrl, logger);
  },
  deps: [APP_CONFIG, LoggingService],
}
useExisting

useExisting creates an alias — both tokens resolve to the same instance. This is useful for providing multiple injection tokens for the same service:

TS
// A component that needs to implement a shared interface
export abstract class Validator {
  abstract validate(value: string): boolean;
}

@Component({
  selector: 'app-email-input',
  providers: [
    // Make this component injectable as a Validator
    { provide: Validator, useExisting: EmailInputComponent },
  ],
})
export class EmailInputComponent implements Validator {
  validate(value: string): boolean {
    return /^[^@]+@[^@]+.[^@]+$/.test(value);
  }
}

TS
// Provide an existing service under a new token
@Injectable({ providedIn: 'root' })
export class NewAuthService implements AuthServiceInterface {
  login(email: string, password: string) { /* ... */ }
}

// Register old token as alias to new service
// — old code using OldAuthService gets the new implementation
{ provide: OldAuthService, useExisting: NewAuthService }
Note
useExisting differs from useClass: useExisting points to an already-registered provider (returns the same instance), while useClass creates a new instance of the given class.
Multi-Providers

Multiple providers can be registered for the same token by setting multi: true. Angular collects them into an array:

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

export const HTTP_INTERCEPTORS = new InjectionToken<HttpInterceptor[]>(
  'HTTP_INTERCEPTORS',
  { factory: () => [] }
);

// Register multiple interceptors
bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: LoggingInterceptor,
      multi: true,
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: RetryInterceptor,
      multi: true,
    },
  ],
});

// Inject: receives an array of all registered interceptors
@Injectable({ providedIn: 'root' })
export class HttpPipeline {
  private interceptors = inject(HTTP_INTERCEPTORS);
  // interceptors = [AuthInterceptor, LoggingInterceptor, RetryInterceptor]
}
Tip
Angular uses the multi-provider pattern internally for HTTP interceptors, route guards, validators, and app initializers. You can use the same pattern for your own plugin-style extension points.
Environment-Specific Providers

TS
// environment.ts
export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000',
};

// environment.prod.ts
export const environment = {
  production: true,
  apiUrl: 'https://api.myapp.com',
};

// Conditionally provide different implementations
import { environment } from '../environments/environment';

bootstrapApplication(AppComponent, {
  providers: [
    { provide: API_URL, useValue: environment.apiUrl },
    environment.production
      ? { provide: LoggingService, useClass: NoopLogger }
      : { provide: LoggingService, useClass: ConsoleLogger },
  ],
});
APP_INITIALIZER — Running Code Before the App Starts

APP_INITIALIZER is a built-in multi-provider token that lets you run initialization logic before Angular renders anything. Common uses: loading config from an API, setting up authentication, or registering locale data:

TS
import { APP_INITIALIZER, inject } from '@angular/core';
import { ConfigService } from './config.service';

function initializeApp(): () => Promise<void> {
  const configService = inject(ConfigService);
  return () => configService.loadConfig(); // must return a Promise or Observable
}

bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: initializeApp,
      multi: true,
    },
  ],
});

TS
@Injectable({ providedIn: 'root' })
export class ConfigService {
  private config: AppConfig | null = null;

  get(key: keyof AppConfig) {
    return this.config?.[key];
  }

  async loadConfig(): Promise<void> {
    const response = await fetch('/assets/config.json');
    this.config = await response.json();
  }
}
Warning
APP_INITIALIZER blocks rendering until all initializer promises resolve. Keep initialization logic fast. For non-critical data, load it lazily after the app starts instead.
provideX() Functions — Modern Provider Pattern

Angular 14+ introduced functional providers — functions prefixed with provide that return properly configured provider arrays. They are tree-shakeable and more readable than raw provider objects:

TS
import {
  bootstrapApplication,
  provideRouter,
  withHashLocation,
} from '@angular/platform-browser';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes, withHashLocation()),
    provideHttpClient(withInterceptors([authInterceptor, loggingInterceptor])),
    provideAnimations(),
  ],
});
Summary

Angular's provider system is flexible and powerful:

| Provider type | Purpose | |---|---| | useClass | Provide a class (possibly substituted) | | useValue | Provide a static value | | useFactory | Compute a value at runtime | | useExisting | Alias one token to another | | multi: true | Register multiple values for one token | | APP_INITIALIZER | Run async code before app renders |

Modern Angular favors provide*() functional APIs over raw provider objects — they are more readable and tree-shakeable.