Dependency Injection Avanzata in Angular 2026: Provider, Token e Domande da Colloquio
Il sistema di dependency injection di Angular offre un controllo preciso sull'istanziazione dei servizi attraverso provider, token e iniettori gerarchici. Questa analisi approfondita esplora le strategie dei provider, la funzione inject() e le domande tecniche da colloquio.

Il sistema di dependency injection di Angular garantisce un controllo granulare sull'istanziazione dei servizi attraverso provider, token e iniettori gerarchici. La padronanza di questi concetti distingue gli sviluppatori Angular esperti dai principianti nei colloqui tecnici e nel codice di produzione.
Angular mantiene due alberi di iniettori paralleli: l'albero ModuleInjector per i servizi forniti a livello di modulo e l'albero ElementInjector per le dipendenze a livello di componente. La risoluzione inizia a livello di elemento e risale verso la radice.
Strategie di Configurazione dei Provider in Angular DI
Angular offre diverse strategie di configurazione dei provider, ciascuna adatta a casi d'uso specifici. Le configurazioni più comuni utilizzano useClass, useValue, useFactory e useExisting.
import { ApplicationConfig, InjectionToken } from '@angular/core';
import { LoggerService } from './services/logger.service';
import { DebugLoggerService } from './services/debug-logger.service';
import { API_CONFIG, ApiConfig } from './config/api.config';
export const appConfig: ApplicationConfig = {
providers: [
// useClass: fornire un'implementazione diversa
{ provide: LoggerService, useClass: DebugLoggerService },
// useValue: fornire un oggetto di configurazione statico
{
provide: API_CONFIG,
useValue: { baseUrl: 'https://api.example.com', timeout: 5000 }
},
// useFactory: creare dipendenza con logica runtime
{
provide: 'FEATURE_FLAGS',
useFactory: () => {
const env = import.meta.env.MODE;
return { debugMode: env === 'development', analytics: env === 'production' };
}
},
// useExisting: creare un alias verso un altro provider
{ provide: 'Logger', useExisting: LoggerService }
]
};La strategia useClass sostituisce le implementazioni senza modificare il codice consumer. La strategia useValue fornisce oggetti statici come configurazioni. La strategia useFactory gestisce decisioni runtime, mentre useExisting crea alias per l'accesso polimorfico.
InjectionToken per Dipendenze Non-Classe Type-Safe
Mentre @Injectable funziona per servizi basati su classi, valori non-classe come oggetti di configurazione, primitive o funzioni richiedono un InjectionToken. Questo token funge da chiave univoca nel registro DI di Angular.
import { InjectionToken } from '@angular/core';
export interface ApiConfig {
baseUrl: string;
timeout: number;
retryAttempts: number;
}
// Il parametro generico garantisce type safety al punto di iniezione
export const API_CONFIG = new InjectionToken<ApiConfig>('api.config', {
providedIn: 'root',
factory: () => ({
baseUrl: 'https://api.sharpskill.dev',
timeout: 30000,
retryAttempts: 3
})
});
// Token per valori primitivi
export const MAX_UPLOAD_SIZE = new InjectionToken<number>('max.upload.size', {
providedIn: 'root',
factory: () => 10 * 1024 * 1024 // 10MB
});Il parametro di tipo generico su InjectionToken<ApiConfig> si propaga alla chiamata inject(). TypeScript riconosce che il valore iniettato corrisponde al tipo dichiarato, intercettando errori in fase di compilazione anziché a runtime.
La Funzione inject() vs Iniezione via Costruttore
Angular 14 ha introdotto la funzione inject() come alternativa all'iniezione basata su costruttore. In Angular 20+, inject() è diventato l'approccio preferito, specialmente nei componenti standalone e nei contesti funzionali.
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { API_CONFIG } from '../tokens/config.tokens';
@Injectable({ providedIn: 'root' })
export class UserService {
// Approccio moderno: inject() a livello di campo
private readonly http = inject(HttpClient);
private readonly config = inject(API_CONFIG);
getUser(id: string) {
return this.http.get(`${this.config.baseUrl}/users/${id}`);
}
}
// Alternativa: iniezione via costruttore (ancora valida)
@Injectable({ providedIn: 'root' })
export class UserServiceLegacy {
constructor(
private readonly http: HttpClient,
@Inject(API_CONFIG) private readonly config: ApiConfig
) {}
}La funzione inject() elimina il boilerplate dei decorator per i token e abilita la dependency injection in contesti non-classe come guard funzionali, resolver e interceptor.
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
// Guard funzionale con inject()
export const authGuard: CanActivateFn = () => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isAuthenticated()) {
return true;
}
return router.createUrlTree(['/login']);
};La funzione inject() funziona solo all'interno di un contesto di iniezione: durante la costruzione della classe, nelle funzioni factory o nei costrutti funzionali Angular. Chiamarla al di fuori di questi contesti genera un errore runtime.
Pronto a superare i tuoi colloqui su Angular?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Iniettori Gerarchici: ElementInjector vs EnvironmentInjector
Angular mantiene due gerarchie di iniettori parallele che determinano lo scope dei servizi e l'ordine di risoluzione. Comprendere questa architettura è essenziale per controllare la durata e la visibilità dei servizi.
import { Injectable } from '@angular/core';
// Singleton a livello root: singola istanza per l'intera app
@Injectable({ providedIn: 'root' })
export class GlobalDataService {
private data = new Map<string, unknown>();
set(key: string, value: unknown) { this.data.set(key, value); }
get(key: string) { return this.data.get(key); }
}
// Scope componente: nuova istanza per componente
@Injectable()
export class ComponentDataService {
private data = new Map<string, unknown>();
set(key: string, value: unknown) { this.data.set(key, value); }
get(key: string) { return this.data.get(key); }
}import { Component } from '@angular/core';
import { ComponentDataService } from './services/component-data.service';
@Component({
selector: 'app-dashboard',
standalone: true,
// Questo componente e tutti i figli ottengono la stessa istanza
providers: [ComponentDataService],
template: `
<app-widget />
<app-stats />
`
})
export class DashboardComponent {}Quando un componente dichiara un provider, Angular crea una nuova istanza con scope nell'ElementInjector di quel componente. I componenti figli ereditano l'accesso ai provider del genitore a meno che non ne dichiarino di propri.
L'algoritmo di risoluzione segue questo percorso:
- Controllare l'ElementInjector del componente richiedente
- Risalire l'albero ElementInjector verso gli antenati
- Controllare l'EnvironmentInjector (provider di modulo o standalone)
- Risalire al root EnvironmentInjector
- Raggiungere NullInjector e lanciare errore se
@Optional()non è stato usato
Modificatori di Risoluzione: @Self, @SkipSelf, @Optional, @Host
I modificatori di risoluzione alterano il modo in cui Angular cerca nella gerarchia degli iniettori. Questi decorator funzionano sia con l'iniezione via costruttore che con la funzione inject().
import { Component, Optional, SkipSelf, Self, inject } from '@angular/core';
import { PanelService } from './panel.service';
@Component({
selector: 'app-panel',
standalone: true,
providers: [PanelService],
template: `<ng-content />`
})
export class PanelComponent {
// @Self: cercare solo nell'iniettore di questo componente, altrimenti fallire
private readonly localService = inject(PanelService, { self: true });
// @SkipSelf: saltare questo componente, iniziare la ricerca dal genitore
private readonly parentService = inject(PanelService, {
skipSelf: true,
optional: true
});
// @Optional: restituire null invece di lanciare errore se non trovato
private readonly optionalService = inject(PanelService, { optional: true });
constructor() {
// localService è sempre l'istanza propria del componente
// parentService è l'istanza del genitore o null
console.log('Local:', this.localService);
console.log('Parent:', this.parentService);
}
}Il modificatore @Host() limita la risoluzione all'iniettore dell'elemento host e si ferma ai confini del componente. Questo è utile quando una direttiva deve accedere a un servizio fornito dal suo componente host ma non deve cercare più in alto nell'albero.
import { Directive, inject, Host, Optional } from '@angular/core';
import { HighlightConfig } from './highlight.config';
@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
// Cercare solo nei provider del componente host
private readonly config = inject(HighlightConfig, {
host: true,
optional: true
});
constructor() {
// config è null se il componente host non fornisce HighlightConfig
const color = this.config?.color ?? 'yellow';
// Applicare evidenziazione...
}
}Multi-Provider per Sistemi Estensibili
I multi-provider permettono di registrare più valori sotto un singolo token. Angular restituisce tutti i valori registrati come array, abilitando architetture a plugin e pattern di estensibilità.
import { InjectionToken } from '@angular/core';
export interface Validator {
validate(value: string): string | null;
}
export const VALIDATORS = new InjectionToken<Validator[]>('validators');import { ApplicationConfig } from '@angular/core';
import { VALIDATORS } from './validators.tokens';
const requiredValidator = {
validate: (value: string) => value ? null : 'Field is required'
};
const minLengthValidator = {
validate: (value: string) => value.length >= 3 ? null : 'Minimum 3 characters'
};
const emailValidator = {
validate: (value: string) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? null : 'Invalid email format'
};
export const appConfig: ApplicationConfig = {
providers: [
{ provide: VALIDATORS, useValue: requiredValidator, multi: true },
{ provide: VALIDATORS, useValue: minLengthValidator, multi: true },
{ provide: VALIDATORS, useValue: emailValidator, multi: true }
]
};import { Injectable, inject } from '@angular/core';
import { VALIDATORS, Validator } from './validators.tokens';
@Injectable({ providedIn: 'root' })
export class ValidationService {
private readonly validators = inject(VALIDATORS);
validate(value: string): string[] {
// validators è un array di tutti i validatori registrati
return this.validators
.map(v => v.validate(value))
.filter((error): error is string => error !== null);
}
}Il flag multi: true indica ad Angular di raccogliere tutti i provider per questo token in un array. Senza di esso, i provider successivi sovrascriverebbero quelli precedenti.
Domande da Colloquio sulla Dependency Injection di Angular
I colloqui tecnici spesso esplorano la comprensione del sistema DI di Angular. Ecco domande che distinguono i candidati con esperienza in produzione.
D: Cosa succede quando lo stesso servizio viene fornito sia a livello di modulo che di componente?
Il provider a livello di componente crea un'istanza separata con scope nel sottoalbero di quel componente. I servizi iniettati in quel sottoalbero ricevono l'istanza del componente, non il singleton a livello di modulo. Questo permette l'isolamento dello stato, ad esempio quando ogni tab necessita del proprio stato del form.
D: Perché usare InjectionToken invece di una stringa letterale?
I token stringa rischiano collisioni tra librerie o parti diverse di un'applicazione. InjectionToken crea un riferimento runtime univoco che non può collidere. Il parametro di tipo generico fornisce anche type safety in fase di compilazione che manca ai token stringa.
D: Quando inject() lancia un errore vs restituisce undefined?
Per default, inject() lancia un errore quando la dipendenza non viene trovata. Passando { optional: true } si cambia il tipo di ritorno in T | null e si restituisce null invece di lanciare l'errore. Questo corrisponde al comportamento del decorator @Optional().
D: Spiegare la differenza tra providedIn: 'root' e fornire nell'array providers di un modulo.
Entrambi creano singleton, ma providedIn: 'root' abilita il tree-shaking. Il servizio viene incluso nel bundle solo se effettivamente iniettato da qualche parte. I provider a livello di modulo sono sempre inclusi indipendentemente dall'uso.
D: Come il lazy loading influenza lo scope dei servizi?
I moduli caricati in lazy loading ottengono il proprio EnvironmentInjector figlio. I servizi forniti in un modulo lazy hanno scope limitato a quel modulo e ai suoi figli. Un servizio con providedIn: 'root' rimane un vero singleton attraverso tutti i moduli, lazy o meno.
Per domande di pratica sui servizi Angular e pattern DI, consultare Domande da colloquio Angular su servizi e dependency injection.
Pattern Pratici: Configurazione e Feature Flag
Le applicazioni reali combinano questi concetti DI per la gestione della configurazione. Questo pattern utilizza InjectionToken, factory provider e consapevolezza dell'ambiente.
import { InjectionToken, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
export interface FeatureFlags {
newCheckout: boolean;
darkMode: boolean;
betaFeatures: boolean;
}
export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>('feature.flags', {
providedIn: 'root',
factory: () => {
const platformId = inject(PLATFORM_ID);
if (!isPlatformBrowser(platformId)) {
// SSR: restituire valori di default sicuri
return { newCheckout: false, darkMode: false, betaFeatures: false };
}
// Browser: controllare localStorage o config remota
const stored = localStorage.getItem('featureFlags');
if (stored) {
return JSON.parse(stored);
}
return { newCheckout: true, darkMode: false, betaFeatures: false };
}
});import { Directive, Input, TemplateRef, ViewContainerRef, inject } from '@angular/core';
import { FEATURE_FLAGS } from './feature-flags.config';
@Directive({
selector: '[appFeatureFlag]',
standalone: true
})
export class FeatureFlagDirective {
private readonly flags = inject(FEATURE_FLAGS);
private readonly templateRef = inject(TemplateRef<unknown>);
private readonly viewContainer = inject(ViewContainerRef);
@Input() set appFeatureFlag(flag: keyof typeof this.flags) {
if (this.flags[flag]) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}Questa direttiva renderizza contenuto condizionalmente basandosi sui feature flag, con la configurazione dei flag centralizzata in un token iniettabile.
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Best Practice Angular DI per la Produzione
- Usare
providedIn: 'root'per singleton application-wide che beneficiano del tree-shaking - In Angular 20+ preferire
inject()all'iniezione via costruttore per sintassi più pulita e compatibilità funzionale - Limitare i servizi stateful ai componenti quando serve isolamento, non a livello di modulo
- Creare
InjectionTokenper dipendenze non-classe per garantire type safety ed evitare collisioni - Applicare
@Optional()quando una dipendenza potrebbe non esistere, specialmente per plugin o feature opzionali - Testare componenti con provider sovrascritti usando
TestBed.overrideComponent()per l'isolamento - Usare multi-provider per pattern di estensibilità come validatori, interceptor e handler
Sapresti trovare il bug in Angular?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 13 settembre 2026
Tag
Condividi
Articoli correlati

Angular Signals e Computed nel 2026: Reattività Fine-Grained e Domande per Colloqui
Una guida approfondita ad Angular Signals, Computed Signals e al modello reattivo in Angular 20+. Include best practice, interoperabilità con RxJS e domande frequenti nei colloqui tecnici.

Angular Forms nel 2026: Reactive Forms, Validazione e Domande per Colloqui Tecnici
Una guida completa su Angular Reactive Forms con FormBuilder tipizzato, validatori personalizzati, validazione asincrona e FormArray. Include domande frequenti per colloqui e anteprima di Signal Forms.

Domande di colloquio Angular 19: Signals, SSR e concetti imprescindibili
Le domande di colloquio Angular 19 più frequenti: Signals, idratazione incrementale, change detection senza Zone.js e nuove API reattive con esempi di codice e risposte attese.