Transclusione, Iniezione e Procrastinazione

Qualche mese fa, Kapunahele Wong ed io stavamo facendo un brainstorming di idee per proposte di talk da fare insieme. Era il periodo in cui ho deciso di esplorare Ivy, così ho pensato che potesse essere un buon posto per un discorso. Kapunahele, tuttavia, aveva un’idea diversa:

Twitter, dove avviene il divertimento! Kapunahele ha condiviso con me alcune delle sue ricerche preliminari, e ho imparato che l’algoritmo di risoluzione delle dipendenze di Angular Injector non era così semplice come pensavo prima.

In effetti, da quando gli NgModules sono stati introdotti nella quinta release candidate di Angular 2.0.0, fornivo solo servizi a livello di modulo, usando la nota proprietà providers:

O da Angular 6, usando la proprietà providedIn del decoratore Injectable:

In entrambi i casi, ho sempre dichiarato i miei servizi a livello di modulo e non ho mai prestato molta attenzione alle altre possibilità.

Entrare Injector Trees 🌴

Kapunahele e io abbiamo deciso di presentare il nostro discorso Injector Trees ad Angular Connect. Diversi mesi dopo, il seguente messaggio è arrivato nella mia casella di posta:

Eravamo entusiasti, il nostro intervento è stato accettato per la conferenza! 😊

Abbiamo passato le settimane successive ad esplorare tutti gli angoli nascosti di come funziona l’iniezione delle dipendenze in Angular. In questo post del blog, ne condividerò alcuni con voi.

Inizio semplice

Inizieremo con una semplice app Angular. Questa app ha un servizio “Kingdom” e un singolo componente che inietta Kingdom e visualizza il nome del Regno:

Ho deciso di usare un Dragone per l’esempio, perché amo usarlo al posto del punto e virgola nel mio codice.

Per rendere le cose un po’ più interessanti, condiamo la nostra app con un componente Unicorn. Questo componente stampa il nome del Regno in cui vive:

Così abbiamo un componente app e un unicorno al suo interno. Fantastico!

Ora, cosa succede se cambiamo la definizione del nostro AppComponent per fornire un valore diverso per il KingdomService?

Lo possiamo fare aggiungendo la seguente linea alla dichiarazione del componente:

providers: 

Come influenzerà la nostra app? Proviamo e vediamo:

Come potete vedere, il valore che abbiamo definito per KingdomService nel nostro AppComponent ha avuto la precedenza sul servizio definito nel nostro AppModule (non è stato definito direttamente lì, ma usando providedIn, ma il risultato è lo stesso).

Albero degli elementi, albero dei moduli

La ragione per cui vediamo gli zombie è il modo in cui la risoluzione delle dipendenze funziona in Angular. Cerca prima l’albero dei componenti, e solo dopo l’albero dei moduli. Consideriamo UnicornComponent. Inietta un’istanza di KingdomService all’interno del suo costruttore:

constructor(public kingdom: KingdomService) {}

Quando Angular crea questo componente, guarda prima se ci sono dei provider definiti sullo stesso elemento del componente. Questi provider potrebbero essere stati registrati sul componente stesso, o usando una direttiva. In questo caso, non abbiamo fornito alcun valore per KingdomService all’interno del UnicornComponent, né abbiamo alcuna direttiva sull’elemento <app-unicorn>.

La ricerca continua poi su per l’albero degli elementi, andando a AppComponent. Qui Angular trova che abbiamo un valore per KingdomService, quindi inietta questo valore e ferma la ricerca lì. Quindi in questo caso, Angular non ha nemmeno guardato l’albero dei moduli.

Angular è pigro!

Proprio come noi programmatori, Angular è anche un procrastinatore. Non crea istanze di servizi a meno che non ne abbia davvero bisogno. Puoi confermarlo aggiungendo una dichiarazione console.log al costruttore di KingdomService (puoi anche aggiungere un alert('I love marquees') se ti senti nostalgico oggi).

Vedrai che la dichiarazione console.log non viene mai eseguita – poiché Angular non crea il servizio. Se rimuovi la dichiarazione providers: dal AppComponent (o la sposti nel UnicornComponent, così si applica solo all’unicorno e ai suoi elementi figli), dovresti iniziare a vedere il messaggio di log nella tua console.

Ora Angular non ha scelta – non trova il KingdomService quando cerca nell’albero degli elementi. Quindi, prima va al Module Injector Tree, poi vede che abbiamo fornito il servizio lì, e infine crea un’istanza di esso. Quindi, il codice all’interno del costruttore viene eseguito, e sarete in grado di vedere la stampa di debug che avete messo lì.

Invasione di direttive!

Ho detto che le direttive possono anche fornire valori per l’iniezione di dipendenza. Sperimentiamo con questo. Stiamo per definire una nuova direttiva appInvader, che cambierà il valore del regno in 👾.
Perché? Perché erano così adorabili nel discorso VR + Angular che io e Alex Castillo abbiamo tenuto in ng-conf.

Poi, aggiungeremo un altro elemento <app-unicorn>, e applicheremo la nuova direttiva appInvader ad esso:

Come previsto, il nuovo unicorno vive nel regno di 👾. Questo perché la direttiva ha fornito un valore per KingdomService. E come spiegato sopra, Angular inizia la ricerca dall’elemento corrente, guardando il componente e tutte le direttive, e solo se non riesce a trovare lì il valore richiesto, continua a risalire l’albero degli elementi (e poi dei moduli).

Guardiamo qualcosa di un po’ più complicato:

Aggiungi una foresta che proietta contenuti all’app!

Aggiungeremo un nuovo componente Forest alla nostra app, e metteremo alcuni degli unicorni dentro questa foresta, perché è lì che vivono gli unicorni (l’ha detto un tizio a caso su quora, quindi deve essere vero).

Il componente Forest è semplicemente un contenitore, che usa Content Projection per mostrare i suoi figli su uno sfondo verde “forestale”:

Così vediamo gli elementi del componente AppForest su uno sfondo di erba, e poi, tutto il contenuto proiettato su uno sfondo verde brillante. E poiché abbiamo fornito un valore per KingdomService all’interno del nostro componente app, tutto all’interno lo eredita (tranne l’unicorno con la direttiva appInvader).

Ma cosa succede se forniamo un nuovo valore per KingdomService all’interno del ForestComponent? Il contenuto proiettato (che è stato definito nel template per AppComponent) riceverà anche questo nuovo valore per il regno? O sarà ancora nel regno 🧟? Puoi indovinare?

Lo chiamavano Transclusione. Ora si chiama “Proiezione del contenuto”. Photo by ng-conf

Il mago della foresta

Aggiungiamo una singola linea al nostro esempio precedente, fornendo un regno 🧙 per il ForestComponent:

providers: 

E questo è il risultato:

Ora questo è interessante – vediamo un misto di regni all’interno della foresta! L’elemento foresta in sé vive nel regno 🧙 , ma il contenuto proiettato sembra avere una doppia personalità: anche gli unicorni appartengono al regno 🧙 , ma il testo sopra di loro mostra il regno 🧟?

Abbiamo definito sia questi unicorni che il testo nello stesso posto, linee 12-15 del template app.component.html. Tuttavia, ciò che conta è il luogo in cui il componente stesso è stato creato nel DOM. Il testo nella riga 12 è in realtà lo stesso che facciamo nella riga 4 – leggiamo la proprietà kingdom della stessa istanza AppComponent. L’elemento DOM per questo componente è in realtà un antenato dell’elemento DOM <app-forest>. Quindi, quando questa istanza AppComponent è stata creata, è stata iniettata con il regno 🧟.

I due elementi <app-unicorn>, sono però all’interno degli elementi DOM <app-forest>, quindi quando le loro istanze di UnicornComponents vengono create, angular risale effettivamente il DOM e vede il valore che abbiamo fornito per il KingdomService dentro il ForestComponent, e quindi questi unicorni vengono iniettati con il regno 🧙.

Puoi ottenere un comportamento diverso se cambi providers in viewProviders quando definisci il ForestComponent. Puoi saperne di più sui View Provider qui, e guarda anche questo esempio di codice, dove ho cambiato ForestComponent per usare i View Provider, così ora anche gli unicorni dentro la foresta sono iniettati con il regno 🧟. Grazie Lars Gyrup Brink Nielsen per avermelo fatto notare!

Ho trascritto il Chrome T-Rex in questo post del blog

Continua a esplorare!

Spero che abbiate appena imparato qualcosa di nuovo sul sistema di Dependency Injection di Angular. Questa è solo una delle cose che io e Kapunahele abbiamo esplorato quando abbiamo preparato il nostro talk per AngularConnect. C’è molto di più – siete invitati ad esplorare ulteriormente, e condivideremo anche le slide e il link al video dopo il discorso. Oh, e ci sarà anche un po’ di codifica dal vivo. Sarà molto divertente!

Se volete saperne di più sui pro e i contro di Angular Injector, ecco alcuni articoli che ho trovato molto utili:

  • Quello che hai sempre voluto sapere su Angular Dependency Injection
  • Un caso curioso del decoratore @Host e degli Element Injectors in Angular
  • Hierarchical Dependency Injectors (Angular Docs)

E se sei presente ad AngularConnect, sei invitato a venire a salutare!

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.