100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TypeScript

The @Injectable Decorator and Providers

Explore the @Injectable decorator in depth and the different ways to configure providers — class, value, factory, and existing — to control what Angular's DI system supplies.

Services & Dependency InjectionIntermediate9 min readJul 9, 2026
Analogies

The @Injectable Decorator and Providers

The @Injectable() decorator does two jobs: it marks a class as injectable (so Angular emits the metadata needed to instantiate it and resolve its own dependencies), and optionally it registers the class with an injector via its providedIn option. But Angular's DI system is more flexible than 'inject a class and get an instance of that same class' — providers let you control exactly what value an injection token resolves to, decoupling what code asks for from what it actually receives.

🏏

Cricket analogy: @Injectable() marking a class as instantiable is like a player being formally registered with the board so selectors can call them up, while providedIn is like being pre-assigned to a specific franchise squad rather than a free agent picked ad hoc.

Provider types

Angular supports four main provider shapes. A useClass provider maps a token to a different class than the token itself, useful for swapping implementations (e.g. a mock service in tests). A useValue provider supplies a static, already-constructed value — ideal for configuration objects or constants. A useFactory provider runs a function to compute the value at injection time, optionally with its own dependencies via deps. A useExisting provider makes one token resolve to the same instance as another existing token, useful for aliasing.

🏏

Cricket analogy: useClass, useValue, useFactory, and useExisting are like swapping a specialist spinner in for a pacer on a turning pitch (useClass), fielding a fixed pre-set batting order (useValue), calculating the XI based on pitch conditions at the toss (useFactory), and naming a vice-captain who's really just the same person as the deputy (useExisting).

typescript
import { InjectionToken, Injectable } from '@angular/core';

export interface AppConfig { apiUrl: string; }
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');

@Injectable({ providedIn: 'root' })
export class LoggerService {
  log(msg: string) { console.log(msg); }
}

@Injectable({ providedIn: 'root' })
export class ConsoleLoggerService extends LoggerService {}

// In app.config.ts (application-level providers):
export const appConfigProviders = [
  { provide: APP_CONFIG, useValue: { apiUrl: 'https://api.example.com' } },
  { provide: LoggerService, useClass: ConsoleLoggerService },
  {
    provide: 'RETRY_COUNT',
    useFactory: (config: AppConfig) => (config.apiUrl.includes('staging') ? 5 : 3),
    deps: [APP_CONFIG],
  },
];

InjectionToken for non-class values

Because plain interfaces and primitive types don't exist at runtime (TypeScript types are erased during compilation), you can't use them directly as DI tokens. InjectionToken<T> creates a unique, type-safe runtime token you can provide and inject in place of a class, commonly used for configuration objects, feature flags, or any dependency that isn't naturally a class instance.

🏏

Cricket analogy: InjectionToken is like the ICC creating a formal 'Player of the Match' award category that doesn't correspond to any single physical trophy class, giving a unique, recognized token you can present even though 'Player of the Match' isn't a tangible object type.

This pattern is conceptually similar to dependency injection containers in other ecosystems, such as .NET's IServiceCollection or Java Spring's @Bean definitions: you separate 'the contract something depends on' (the token) from 'what concretely satisfies that contract' (the provider), which is what makes swapping real implementations for test doubles straightforward without touching consuming code.

A frequent mistake is providing the same token at multiple levels (e.g. both providedIn: 'root' and again in a component's providers array) without realizing the component-level provider creates an entirely separate instance for that component's injector subtree, shadowing the root singleton for anything injected under that component. This is sometimes intentional (scoping a service to a feature) but often an accidental source of 'why isn't my state shared' bugs.

Providers can be supplied at the root level via providedIn or an application-wide providers array in app.config.ts, or at a narrower level via a component's or route's providers array, which determines the scope and lifetime of the instance created — a topic explored in depth alongside the injector hierarchy.

🏏

Cricket analogy: Providers at root versus component/route level are like a franchise having a central head coach available to the whole squad (root providedIn) versus a specialist fielding coach brought in just for one fielding drill session (component-level provider), scoping their involvement and lifetime accordingly.

  • @Injectable() marks a class for DI and can register it with an injector via providedIn.
  • useClass swaps in a different implementation class for a given token.
  • useValue supplies a static, pre-built value, commonly for configuration objects.
  • useFactory computes the provided value at injection time, optionally using its own deps.
  • useExisting aliases one token to resolve to the same instance as another.
  • InjectionToken<T> creates a type-safe runtime token for values that aren't classes, like interfaces or config objects.

Practice what you learned

Was this page helpful?

Topics covered

#TypeScript#AngularStudyNotes#WebDevelopment#TheInjectableDecoratorAndProviders#Injectable#Decorator#Providers#Provider#StudyNotes#SkillVeris