# 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. - Published: 2026-09-13 - Updated: 2026-09-13 - Author: Anthony Fillion-Maillet - Tags: angular, dependency injection, providers, injection token, interview - Reading time: 5 min --- 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. > **Concetto Chiave** > > 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`. ```typescript // app.config.ts 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. ```typescript // tokens/config.tokens.ts 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('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('max.upload.size', { providedIn: 'root', factory: () => 10 * 1024 * 1024 // 10MB }); ``` Il parametro di tipo generico su `InjectionToken` 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. ```typescript // user.service.ts 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. ```typescript // auth.guard.ts 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. ## 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. ```typescript // data.service.ts 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(); 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(); set(key: string, value: unknown) { this.data.set(key, value); } get(key: string) { return this.data.get(key); } } ``` ```typescript // dashboard.component.ts 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: ` ` }) 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: 1. Controllare l'ElementInjector del componente richiedente 2. Risalire l'albero ElementInjector verso gli antenati 3. Controllare l'EnvironmentInjector (provider di modulo o standalone) 4. Risalire al root EnvironmentInjector 5. 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()`. ```typescript // panel.component.ts import { Component, Optional, SkipSelf, Self, inject } from '@angular/core'; import { PanelService } from './panel.service'; @Component({ selector: 'app-panel', standalone: true, providers: [PanelService], template: `` }) 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. ```typescript // highlight.directive.ts 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à. ```typescript // validators.tokens.ts import { InjectionToken } from '@angular/core'; export interface Validator { validate(value: string): string | null; } export const VALIDATORS = new InjectionToken('validators'); ``` ```typescript // app.config.ts 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 } ] }; ``` ```typescript // validation.service.ts 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](/technologies/angular/interview-questions/services-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. ```typescript // feature-flags.config.ts 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('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 }; } }); ``` ```typescript // feature-flag.directive.ts 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); 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. ## 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 `InjectionToken` per 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 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/angular/angular-dependency-injection-advanced-providers-tokens-interview