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:
// 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,
};// 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:
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,
},
},
},
],
});// 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:
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';
},
},
],
});// 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);
},
},
],
});// 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:
// 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);
}
}// 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 }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:
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]
}Environment-Specific Providers
// 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:
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,
},
],
});@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();
}
}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:
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.