Come modificare il meccanismo di dependency injection presente in Angular.
Stefano Marchisio - sviluppatore freelance: angular | asp.net core mvc c#
Introduzione
L’obiettivo del presente articolo e spiegare la differenza tra l’injectors “providers” e l’injectors “viewProviders”. Anche se non è trattato pienamente nel presente articolo, facciamo prima un piccolo recall del meccanismo di dependency injection presente in Angular.
In Angular (come in altri framework) è presente una funzionalità per creare e iniettare un oggetto nel costruttore di una classe. L’oggetto iniettato potrà poi essere di 2 tipi: 1) Singleton ovvero verrà creata un'unica istanza condivisa che verrà iniettata tutte le volte che viene richiesta 2) Verrà iniettata una nuova istanza tutte le volte che viene richiesta.
Decidere di attivare una modalità piuttosto che l’altra dipende dove viene registrato il servizio!
1) Se un servizio viene registrato all’interno di un modulo tramite l’array providers avremo un servizio singleton (dalla versione di Angula 6 abbiamo anche l’attributo providedIn del decoratore @Injectable, vedi sotto).
2) Se un servizio viene registrato all’interno di un componente tramite l’array providers avremo un nuovo servizio che verrà creato tutte le volte che un componente viene istanziato, tale servizio sarà poi visibile sia dal componente che lo ha registrato e sia dagli eventuali figli presenti nel componente che ne fanno richiesta.
Entrando nel dettaglio, quando all’interno del costruttore di un componente viene richiesto un servizio, il motore di dependency injection presente in Angular per prima cosa guarda se nel componente è stato registrato un servizio, in caso contrario risale l’albero dei componenti per guardare nei componenti di livello superiore, se giunto al nodo radice non ha trovato niente guarda allora nel modulo. Ciò implica che in Angular esistono 2 tipi di injector: root-injector e child-injector.
Fin qui nulla di strano, e per maggiori informazioni sulla dependency injection si rimanda alla documentazione ufficiale.
Arriviamo ora allo scopo del presente articolo, la cosa che forse molti non conoscono è che all’interno di un componente è possibile registrare un servizio oltre che con l’array “providers” anche con l’array “viewProviders”. Infatti nel decoratore “@Component” usato per definire un componente, oltre alle proprietà base: selector, templateUrl, styleUrls; troviamo le proprietà: providers e viewProviders (che servono appunto per registrare un servizio).
Non si riscontrano differenze tra “providers” e “viewProviders” finchè all’interno del template HTML non è presente un tag <ng-content>; ovvero esiste del contenuto proiettato. Ciò vuol dire che in un componente “Padre” i figli vengono iniettati nel seguente modo: <parent><child></child></parent>.
In questo caso (se esiste del contenuto proiettato) se un componente “child” richiede un servizio (che è registrato nel “parent”) il motore di dependency injection presente in Angular NON fornirà il servizio come avverrebbe di default, ma va subito in cima al component tree. Questo avviene per prevenire che librerie di 3 parti possano usare dei nostri servizi. Vediamo ora 3 brevi esempi.
I 2 templete HTML sovrastanti sono utilizzati nell’esempio 1 e nell’esempio 2. Come si può vedere vengono definiti 2 componenti “parent” affiancati, all’interno di ogni componente parent è poi presente un componte “child”.
1) Il servizio è registrato solo all’interno del modulo
In questo primo esempio sono presenti 2 componenti “parent” affiancati, sia il componente “parent” che il componente “child” (definito all’interno del componente parent) richiedono lo stesso servizio. Essendo il servizio registrato nel modulo otterranno tutti la stessa istanza (parent e child). Per questo motivo qualsiasi cosa che venga digitata in una delle 2 textbox del componente “parent” verrà replicata anche nell’altra textbox, così come nella lables dei controlli “child”.
2) Il servizio è registrato all’interno del componente parent tramite “providers”
In questo secondo esempio sono presenti 2 componenti “parent” affiancati, sia il componente “parent” che il componente “child” (definito all’interno del componente parent) richiedono lo stesso servizio. Essendo il servizio registrato nel componente “parent” verranno create 2 istanze diverse per ciascun componente, i componenti “child” otterranno poi l’istanza del componente “parent” corrispondente. Per questo motivo le 2 textbox saranno disgiunte, quello che viene digitato in una textbox non verrà replicato anche nell’altra textbox, verrà solo replicato nel componente “child” corrispondente.
3) Il servizio è registrato all’interno del componente parent tramite “viewProviders”
Quello visto fino ad ora è il funzionamento standard del motore di dependency injection presente in Angular. Vedremo ora che in presenza di contenuto proiettato, se un servizio è registrato tramite “viewProviders”, le cose cambiano.
Sotto si può vedere il template HTML del componente contenitore
Sotto si può vedere il template HTML del componente “parent”
Come si può vedere nei template HTML sovrastanti all’interno del componente contenitore sono presenti 2 componenti “parent” affiancati: <parentc><child></child></parentc>. Il componente “child” in questo caso viene proiettato a differenza dei 2 esempi precedenti in cui era dichiarato nel componente “parent” corrispondente. Nel componente “parent” troviamo solo il tag <ng-content>. Cosa importante, nel componente “parent” il servizio è registrato attaverso “viewProviders”.
In questo terzo esempio sono presenti 2 componenti “parent” affiancati, sia il componente “parent” che il componente “child” richiedono lo stesso servizio, in questo caso però il componente “child” viene proiettato. Essendo il servizio registrato nel componente “parent” verranno create 2 istanze diverse per ciascun componente. Per questo motivo le 2 textbox saranno disgiunte, quello che viene digitato in una textbox non verrà replicato anche nell’altra textbox. A differenza degli altri 2 esempi, ciò che viene digitato in una textbox NON verrà replicato nel componente “child” corrispondente, questo perché il componente “child” non otterrà l’istanza del servizio dal componente “parent” perché registrato con “viewProviders”. Questo avviene per prevenire che librerie di 3 parti possano usare dei nostri servizi.
Servizi tree-shakable
Il tree shaking è l’operazione che implica la rimozione del codice non utilizzato all'interno di un'applicazione Angular in modo che non sia presente nel bundle filale.
Con Angular Ivy il nuovo set di istruzioni è stato progettato per raggiungere gli obiettivi sopra menzionati. Infatti è stato progettato per essere completamente tree-shakeable. Ciò significa che se non viene utilizzata una particolare funzione di Angular, le istruzioni corrispondenti a quella funzione non saranno inserite nel bundle finale, al contrario il vecchio motore di render ViewEngine non era completamente tree-shakeable. Per quanto riguarda i servizi, un servizio è tree-shakable se non viene incluso nel bundle finale se non è mai stato fatto riferimento ad esso nell’applicazione.
Detto questo, i servizi definiti nell’array provider all’interno di un modulo (non di componente) non sono tree-shakable. Ma se si registra un provider direttamente all'interno del decoratore @Injectable() utilizzando l'attributo providedIn, se non viene utilizzato nell'applicazione non verrà inserito nel bundle.
Questa nuova modalità è stata introdotta a partire da Angular 6, ma tutto ciò che è stato detto poco sopra in merito a “providers” e “viewProviders” è sempre valido.
Conclusioni
Anche se poco utilizzato abbiamo visto che esiste anche l’array “viewProviders”, che potrà venirci in aiuto per modificare il meccanismo di default del motore di dependency injection presente in Angular. Pur non essendo oggetto del presente articolo esistono anche altri metodi per cambiare tale comportamento. Infatti se si fa precedere il nome del servizio che deve essere iniettato nel costruttore con un decoratore di parametro: @Optional, @Self, @SkipSelf, @Inject, @host, etc; il risultato sarà un comportamento diverso.
Se volete contattarmi il mio profilo Linkedin è il seguente:
Stefano Marchisio - sviluppatore freelance: angular | asp.net core mvc c#
Top comments (0)