Nu tot ce injectezi e o clasă. Un URL de API, o configurație, un set de feature flags, o funcție, o listă de plugin-uri — toate au nevoie de un token. InjectionToken<T> e cheia tipizată sub care le furnizezi și le ceri, iar funcțiile provideX() sunt felul modern de a expune configurarea unui feature (exact ca provideRouter() sau provideHttpClient()).
InjectionToken
import { InjectionToken, inject } from '@angular/core';
// Un token tipizat pentru o valoare care NU e o clasă (config, funcție, string, obiect)
export const API_URL = new InjectionToken<string>('API_URL');
// Cu valoare implicită: funcționează și dacă nimeni nu-l furnizează
export const PAGE_SIZE = new InjectionToken<number>('PAGE_SIZE', {
providedIn: 'root',
factory: () => 20,
});
// Folosire
export class UsersApi {
private readonly baseUrl = inject(API_URL);
private readonly pageSize = inject(PAGE_SIZE);
}Funcții provideX()
// Pattern-ul provideX(): API-ul public al unui feature sau al unei librării
export interface FeatureFlags {
newCheckout: boolean;
darkPromo: boolean;
}
const DEFAULT_FLAGS: FeatureFlags = { newCheckout: false, darkPromo: false };
export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>('FEATURE_FLAGS', {
providedIn: 'root',
factory: () => DEFAULT_FLAGS,
});
export function provideFeatureFlags(flags: Partial<FeatureFlags>): EnvironmentProviders {
return makeEnvironmentProviders([
{ provide: FEATURE_FLAGS, useValue: { ...DEFAULT_FLAGS, ...flags } },
]);
}{"newCheckout":false,"darkPromo":false,"betaBadge":true}{"newCheckout":true,"darkPromo":true,"betaBadge":true}// EnvironmentProviders (makeEnvironmentProviders) pot fi folosiți DOAR la nivel de
// aplicație sau rută — nu în `providers` de componentă. Asta e intenționat: e o „barieră”
// pentru configurări globale (router, http, animații, librării).
@Component({
// providers: [provideFeatureFlags(...)] ✗ eroare: EnvironmentProviders în componentă
providers: [{ provide: FEATURE_FLAGS, useValue: { ...DEFAULT_FLAGS, newCheckout: true } }], // ✓
})
export class BetaZone {}multi: true — plugin-uri
// Plugin-uri: mai mulți provideri pentru același token, adunați într-un array
export const EXPORTERS = new InjectionToken<Exporter[]>('EXPORTERS');
providers: [
{ provide: EXPORTERS, useValue: csvExporter, multi: true },
{ provide: EXPORTERS, useValue: jsonExporter, multi: true },
{ provide: EXPORTERS, useClass: PdfExporter, multi: true },
];
// Componenta nu știe câte formate există — le primește pe toate
readonly exporters = inject(EXPORTERS); // Exporter[]Alege un format.
Inițializatori
// Cod care trebuie să ruleze la pornire, ÎNAINTE ca aplicația să fie gata
export const appConfig: ApplicationConfig = {
providers: [
// înlocuiește vechiul APP_INITIALIZER; poate întoarce Promise / Observable
provideAppInitializer(() => inject(ConfigService).load()),
],
};
// La nivel de rută sau de „environment” (ex. când se creează injectorul unui feature lazy)
{
path: 'admin',
providers: [provideEnvironmentInitializer(() => inject(AdminAnalytics).start())],
loadChildren: () => import('./admin/admin.routes'),
}Token-uri opționale
Uneori un token e chiar opțional, de exemplu un tracker de analytics pe care doar unele medii îl configurează. inject(TOKEN, { optional: true }) întoarce null în loc să arunce eroarea NG0201. Dacă există o valoare implicită rezonabilă, pune-o mai bine în factory, ca să nu verifici null peste tot.
export const ANALYTICS = new InjectionToken<Analytics>('ANALYTICS');
export class Checkout {
private readonly analytics = inject(ANALYTICS, { optional: true }); // null dacă nimeni nu l-a furnizat
pay() {
this.analytics?.track('pay');
}
}Token-uri ușoare în librării
Dacă o componentă de librărie caută un copil cu contentChild(LibHeader), clasa LibHeader ajunge în bundle chiar și în aplicațiile care nu o folosesc niciodată. Soluția din documentația Angular: cauți după o clasă abstractă mică, iar componenta reală o furnizează pe ea însăși sub acel token. Dacă nimeni nu folosește componenta, bundler-ul o elimină.
// token-ul: o clasă abstractă goală, aproape zero cod în bundle
export abstract class LibCardHeader {}
@Component({
selector: 'lib-card-header',
providers: [{ provide: LibCardHeader, useExisting: CardHeader }], // componenta reală se furnizează sub token
template: '<ng-content />',
})
export class CardHeader extends LibCardHeader {}
@Component({ selector: 'lib-card', template: '...' })
export class Card {
// caută după token, nu după CardHeader: dacă nimeni nu folosește CardHeader, dispare din bundle
protected readonly header = contentChild(LibCardHeader);
}Token-uri în teste
Un token se înlocuiește în teste exact ca un serviciu: îl furnizezi în TestBed cu altă valoare. Așa testezi aceeași componentă cu flag-uri diferite sau cu alt URL de API, fără să atingi codul.
it('afișează noul checkout când flag-ul e pornit', () => {
TestBed.configureTestingModule({
providers: [
{ provide: FEATURE_FLAGS, useValue: { newCheckout: true } },
{ provide: API_URL, useValue: 'http://localhost/test-api' },
],
});
const fixture = TestBed.createComponent(CheckoutPage);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Checkout nou');
});Ce alegi
Clasă / @Service() → ai logică (metode), dependențe, stare
InjectionToken<T> → valori: config, URL-uri, funcții, constante, interfețe
InjectionToken + multi → plugin-uri / extensii (validatori, exportere, interceptori)
provideX() → API public de configurare pentru un feature / librărieIerarhia în care Angular caută un token e la Providers & ierarhia de injectori; token-urile folosite ca porturi între straturi, la Arhitectură hexagonală (ports & adapters).
Capcane frecvente
- Token-uri string (
@Inject('API_URL')) — fără tipuri, coliziuni posibile. Folosește mereuInjectionToken. - Uiți
multi: truela un provider → suprascrie toată lista în loc să adauge. - Interfață ca token:
inject(MyInterface)nu merge — interfețele nu există la runtime. Folosește un token sau o clasă abstractă. - Inițializatori lenți întârzie pornirea aplicației. Încarcă doar ce e strict necesar înainte de prima randare.
Fișă rapidă
new InjectionToken<T>('NAME') // trebuie furnizat de cineva
new InjectionToken<T>('NAME', { providedIn: 'root', factory }) // cu valoare implicită
{ provide: TOKEN, useValue: value }
{ provide: TOKEN, useFactory: () => create(inject(Other)) }
{ provide: TOKEN, useExisting: OtherToken }
{ provide: TOKEN, useClass: Impl, multi: true } // se adună într-un array
inject(TOKEN) // NG0201 dacă lipsește
inject(TOKEN, { optional: true }) // null dacă lipsește
makeEnvironmentProviders([...]) // doar în app.config / rute
provideAppInitializer(() => inject(Config).load()) // rulează înainte de prima randare