Angular’s DI system supports multiple provider configurations beyond simply registering a class. useClass swaps one implementation for another (production service vs mock). useValue injects primitive values and objects. useFactory creates services with runtime logic. InjectionToken provides type-safe tokens for non-class dependencies. Together these allow environment-specific configuration, A/B testing, and testable architecture without changing component or service code.
Provider Configurations
import { InjectionToken, inject } from '@angular/core';
import { environment } from '@env/environment';
// ── InjectionToken — for non-class dependencies ───────────────────────────
export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL');
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');
export interface AppConfig {
maxPageSize: number;
defaultPageSize: number;
featureFlags: { signalR: boolean; darkMode: boolean };
}
// ── app.config.ts — configure providers ───────────────────────────────────
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(withInterceptors([authInterceptor])),
// ── useValue — inject a constant ──────────────────────────────────────
{ provide: API_BASE_URL, useValue: environment.apiBaseUrl },
// ── useValue — inject a config object ─────────────────────────────────
{
provide: APP_CONFIG,
useValue: { maxPageSize: 100, defaultPageSize: 10,
featureFlags: { signalR: true, darkMode: false } } as AppConfig,
},
// ── useClass — swap implementation (environment-based) ─────────────────
environment.production
? { provide: PostsService, useClass: ProductionPostsService }
: { provide: PostsService, useClass: MockPostsService },
// ── useFactory — service needing runtime construction ─────────────────
{
provide: LoggingService,
useFactory: () => {
const config = inject(APP_CONFIG);
return new LoggingService(config.featureFlags.signalR ? 'verbose' : 'warn');
},
},
// ── useExisting — alias one token to another ──────────────────────────
// Both AuthService and IAuthService token resolve to the same instance
{ provide: IAuthService, useExisting: AuthService },
],
};
// ── Injecting tokens in services/components ───────────────────────────────
@Injectable({ providedIn: 'root' })
export class PostsService {
private baseUrl = inject(API_BASE_URL); // inject the token
private config = inject(APP_CONFIG); // inject the config object
private http = inject(HttpClient);
getPublished(page = 1) {
const size = this.config.defaultPageSize;
return this.http.get<PagedResult<PostSummaryDto>>(
`${this.baseUrl}/api/posts?page=${page}&size=${size}`
);
}
}
// ── Route-level providers — scoped to a route subtree ─────────────────────
// app.routes.ts
export const routes: Routes = [
{
path: 'admin',
providers: [
// AdminService only exists in the admin subtree
{ provide: AdminService, useClass: AdminService },
],
loadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent),
},
];
InjectionToken is the correct way to provide non-class values (strings, numbers, objects, interfaces) to Angular’s DI system. A plain string token (provide: 'apiUrl') works but is error-prone — a typo in the string name silently fails to inject the value. InjectionToken is type-safe and IDE-friendly: const API_URL = new InjectionToken<string>('API_URL') gives you a typed, referenceable token. Use InjectionToken for all non-class provider keys.useClass always creates a new instance of the specified class — it does not reuse an existing instance. If MockPostsService is already provided elsewhere, { provide: PostsService, useClass: MockPostsService } creates a separate instance. To reuse an existing instance, use useExisting instead: { provide: PostsService, useExisting: MockPostsService }. The difference matters when the class has stateful signals or cached data that should be shared.Common Mistakes
Mistake 1 — Using string literals as provider tokens (typo-prone, no type safety)
❌ Wrong — { provide: 'apiUrl', useValue: environment.apiBaseUrl }; string mismatch is silently null.
✅ Correct — export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL'); typed and refactorable.
Mistake 2 — useClass when you need useExisting (creates duplicate instance)
❌ Wrong — { provide: ILogger, useClass: ConsoleLogger } creates a second ConsoleLogger instance.
✅ Correct — { provide: ILogger, useExisting: ConsoleLogger } aliases to the already-registered instance.