Transclusion, Injection and Procrastination

För några månader sedan funderade Kapunahele Wong och jag på idéer för föredrag som vi skulle kunna göra tillsammans. Det var ungefär samtidigt som jag bestämde mig för att utforska Ivy, så jag tänkte att det skulle passa bra för ett föredrag. Kapunahele hade dock en annan idé:

Twitter, där de roliga sakerna händer!

Det här var första gången som jag hörde talas om Injector Trees, så jag blev nyfiken. Kapunahele delade med sig av en del av sin preliminära forskning, och jag lärde mig att algoritmen för beroendeupplösning i Angular Injector inte var så enkel som jag trodde tidigare.

Förresten, sedan NgModules introducerades i den femte release-kandidaten av Angular 2.0.0, tillhandahöll jag bara tjänster på modulnivå med hjälp av den välkända providers-egenskapen:

Och sedan Angular 6 med hjälp av providedIn-egenskapen i Injectable-dekoratorn:

Hursomhelst har jag alltid deklarerat mina tjänster på modulnivå och har aldrig brytt mig om de andra möjligheterna.

Enter Injector Trees 🌴

Kapunahele och jag bestämde oss för att skicka in vårt föredrag om Injector Trees till Angular Connect. Flera månader senare landade följande meddelande i min inkorg:

Vi var pumpade, vår föreläsning hade accepterats till konferensen! 😊

Vi tillbringade de kommande veckorna med att utforska alla dolda hörn av hur injektion av beroenden fungerar i Angular. I det här blogginlägget kommer jag att dela några av dem med dig.

Starting Simple

Vi kommer att börja med en enkel Angular-app. Den här appen har en ”Kingdom”-tjänst och en enda komponent som injicerar Kingdom och visar namnet på Kingdom:

Jag bestämde mig för att använda en drake i exemplet, eftersom jag älskar att använda dem i stället för semikolon i min kod.

För att göra saker och ting lite intressantare, låt oss krydda vår app med en Unicorn-komponent. Den här komponenten skriver ut namnet på kungariket där den bor:

Så vi har en appkomponent och en enhörning inuti den. Vad händer nu när vi ändrar definitionen av vår AppComponent för att ge ett annat värde för KingdomService?

Vi kan göra detta genom att lägga till följande rad i komponentdeklarationen:

providers: 

Hur kommer detta att påverka vår app? Låt oss prova det och se:

Som du kan se fick värdet som vi definierade för KingdomService i vår AppComponent företräde framför tjänsten som definierades i vår AppModule (det definierades inte direkt där, utan snarare med hjälp av providedIn, men resultatet är detsamma).

Elementträdet, modulträdet

Rörelsen till att vi ser zombier är det sätt som beroendeupplösning fungerar i Angular. Den söker först i komponentträdet och först därefter i modulträdet. Låt oss se på UnicornComponent. Den injicerar en instans av KingdomService i sin konstruktör:

constructor(public kingdom: KingdomService) {}

När Angular skapar den här komponenten tittar den först om det finns några providers definierade på samma element som komponenten. Dessa providers kan ha registrerats på själva komponenten eller med hjälp av ett direktiv. I det här fallet har vi inte angett något värde för KingdomService inne i UnicornComponent, och vi har inte heller några direktiv på elementet <app-unicorn>.

Sökningen fortsätter sedan uppåt i elementträdet och går till AppComponent. Här upptäcker Angular att vi har ett värde för KingdomService, så det injicerar detta värde och slutar söka där. I det här fallet tittade Angular alltså inte ens på modulträdet.

Angular är lat!

Som vi programmerare är Angular också en prokrastinator. Den skapar inte instanser av tjänster om den inte verkligen behöver det. Du kan bekräfta detta genom att lägga till ett console.log-uttalande till konstruktören av KingdomService (du kan också lägga till ett alert('I love marquees') om du känner dig nostalgisk idag).

Du kommer att se att console.log-uttalandet aldrig utförs – eftersom Angular inte skapar tjänsten. Om du tar bort providers:-deklarationen från AppComponent (eller flyttar den till UnicornComponent, så att den bara gäller för enhörningen och dess underordnade element) bör du börja se loggmeddelandet i konsolen.

Nu har Angular inget val – den hittar inte KingdomService när den tittar i elementträdet. Så den går först till Module Injector Tree, ser sedan att vi tillhandahöll tjänsten där och skapar slutligen en instans av den. Därför körs koden i konstruktören och du kommer att kunna se felsökningsutskriften som du lade in där.

Directive Invasion!

Jag nämnde att direktiv också kan tillhandahålla värden för injektion av beroenden. Låt oss experimentera med det. Vi ska definiera ett nytt appInvader-direktiv, som kommer att ändra värdet för riket till 👾.
Varför? För att de var så vackra i VR + Angular-föreläsningen som Alex Castillo och jag höll i ng-conf.

Sedan lägger vi till ett annat <app-unicorn>-element och tillämpar det nya appInvader-direktivet på det:

Som väntat bor den nya enhörningen i 👾:s kingdom. Detta beror på att direktivet gav ett värde för KingdomService. Och som förklarat ovan börjar Angular sökningen från det aktuella elementet, tittar på Component och alla direktiv, och bara om den inte kan hitta det begärda värdet där fortsätter den att gå uppåt i elementträdet (och sedan modulerna).

Låt oss titta på något som är lite mer komplicerat:

Att lägga till en Content-Projecting Forest i appen!

Vi kommer att lägga till en ny Forest-komponent till vår app och placera några av enhörningarna i denna skog, eftersom det är där enhörningar bor (någon slumpmässig kille sa det på quora, så det måste vara sant).

Skogskomponenten är helt enkelt en behållare som använder Content Projection för att visa sina barn ovanpå en grön ”skoglig” bakgrund:

Så vi ser elementen i AppForest-komponenten på en gräsbakgrund, och sedan allt projicerat innehåll ovanpå en ljusgrön bakgrund. Och eftersom vi har angett ett värde för KingdomService inne i vår appkomponent ärver allt inuti det (utom den ena enhörningen med appInvader-direktivet).

Men vad händer om vi anger ett nytt värde för KingdomService inne i ForestComponent? Kommer det projicerade innehållet (som definierades i mallen för AppComponent) också att få detta nya värde för riket? Eller kommer det fortfarande att vara i 🧟-riket? Kan du gissa?

Förr brukade man kalla det för Transclusion. Nu kallas det ”Content Projection”. Photo by ng-conf

Trollkarlen i skogen

Vi lägger till en enda rad till vårt tidigare exempel, vilket ger ett 🧙 rike för ForestComponent:

providers: 

Och det här är resultatet:

Nu är det här intressant – vi ser en blandning av riken inuti skogen! Själva skogselementet bor i riket 🧙, men det projicerade innehållet verkar ha delad personlighet: enhörningarna tillhör också riket 🧙, men texten ovanför dem visar 🧟 riket?

Vi definierade både dessa enhörningar och texten på samma ställe, raderna 12-15 i mallen app.component.html. Det som spelar roll är dock platsen där själva komponenten skapades i DOM. Texten på rad 12 är faktiskt samma sak som det vi gör på rad 4 – vi läser kingdom-egenskapen för samma AppComponent-instans. DOM-elementet för den här komponenten är faktiskt en föregångare till DOM-elementet <app-forest>. Så när den här AppComponent-instansen skapades injicerades den med 🧟 kingdom.

De två <app-unicorn>-elementen finns dock inuti <app-forest> DOM-elementen, så när deras UnicornComponents-instanser skapas går angular faktiskt upp i DOM och ser det värde som vi angett för KingdomService inuti ForestComponent, och därmed injiceras dessa enhörningar med 🧙 kingdom.

Du kan uppnå ett annat beteende om du ändrar providers till viewProviders när du definierar ForestComponent. Du kan lära dig mer om View Providers här, och även kolla in det här kodexemplet, där jag ändrade ForestComponent för att använda View Providers, så nu injiceras även enhörningarna i skogen med 🧟 kingdom. Tack Lars Gyrup Brink Nielsen för att han påpekade detta för mig!

Jag transkluderade Chrome T-Rex i det här blogginlägget

Keep Exploring!

Jag hoppas att du just har lärt dig något nytt om Angular’s Dependency Injection-system. Detta är bara en av de saker Kapunahele och jag utforskade när vi förberedde vårt föredrag för AngularConnect. Det finns mycket mer – du är inbjuden att utforska vidare, och vi kommer också att dela med oss av bildspelet och länken till videon efter föredraget. Åh, och det kommer att bli lite livekodning också. Det kommer att bli väldigt roligt!

Om du vill lära dig mer om hur Angular Injector fungerar, finns här några artiklar som jag fann mycket hjälpsamma:

  • Vad du alltid har velat veta om Angular Dependency Injection
  • A curious case of the @Host decorator and Element Injectors in Angular
  • Hierarchical Dependency Injectors (Angular Docs)

Och om du deltar i AngularConnect är du välkommen att komma och säga hej!

Lämna ett svar

Din e-postadress kommer inte publiceras.