Transclusion, Injection og Procrastination

For et par måneder siden var Kapunahele Wong og jeg i gang med at brainstorme idéer til foredrag, som vi kunne holde sammen. Det var omkring det tidspunkt, hvor jeg besluttede mig for at udforske Ivy, så jeg tænkte, at det kunne være et godt bud på et foredrag. Kapunahele havde dog en anden idé:

Twitter, where the fun stuff is happening!

Det var første gang, jeg hørte om Injector Trees, så jeg var fascineret. Kapunahele delte nogle af sine foreløbige undersøgelser med mig, og jeg lærte, at algoritmen til opløsning af afhængigheder i Angular Injector ikke var så enkel, som jeg troede før.

Faktisk set, siden NgModules blev introduceret i den 5. release candidate af Angular 2.0.0, leverede jeg kun tjenester på modulniveau ved hjælp af den velkendte providers-egenskab:

Og siden Angular 6 ved hjælp af providedIn-egenskaben i Injectable-dekoratoren:

Hver som helst har jeg altid erklæret mine tjenester på modulniveau og har aldrig været for opmærksom på de andre muligheder.

Enter Injector Trees 🌴

Kapunahele og jeg besluttede os for at indsende vores Injector Trees-foredrag til Angular Connect. Flere måneder senere landede følgende besked i min indbakke:

Vi var pumpede, vores foredrag blev accepteret til konferencen! 😊

Vi brugte de næste par uger på at udforske alle de skjulte hjørner af, hvordan dependency injection fungerer i Angular. I dette blogindlæg vil jeg dele nogle af dem med dig.

Starting Simple

Vi vil starte med en simpel Angular-app. Denne app har en “Kingdom”-tjeneste og en enkelt komponent, der injicerer Kingdom og viser navnet på Kingdom:

Jeg besluttede at gå med en drage til eksemplet, da jeg elsker at bruge dem i stedet for semikolon i min kode.

For at gøre tingene lidt mere interessante, skal vi krydre vores app med en Unicorn-komponent. Denne komponent udskriver navnet på det kongerige, hvor den bor:

Så vi har en app-komponent og en enhjørning inde i den. Fint!

Hvad sker der nu, når vi ændrer definitionen af vores AppComponent for at give en anden værdi for KingdomService?

Vi kan gøre dette ved at tilføje følgende linje til komponentdeklarationen:

providers: 

Hvordan vil dette påvirke vores app? Lad os prøve det og se:

Som du kan se, fik den værdi, vi definerede for KingdomService i vores AppComponent, forrang frem for den tjeneste, der er defineret i vores AppModule (den blev ikke defineret direkte der, men ved hjælp af providedIn, men resultatet er det samme).

Elementtræ, modultræ

Grunden til, at vi ser zombier, er den måde, som afhængighedsopløsning fungerer på i Angular. Den søger først i træet af komponenter og først derefter i træet af moduler. Lad os se på UnicornComponent. Den injicerer en instans af KingdomService inde i dens konstruktør:

constructor(public kingdom: KingdomService) {}

Når Angular opretter denne komponent, kigger den først, om der er nogen udbydere defineret på det samme element som komponenten. Disse udbydere kan være blevet registreret på selve komponenten eller ved hjælp af et direktiv. I dette tilfælde har vi ikke angivet nogen værdi for KingdomService inde i UnicornComponent, og vi har heller ikke nogen direktiver på <app-unicorn>-elementet.

Søgningen fortsætter derefter opad i elementtræet og går til AppComponent. Her finder Angular, at vi har en værdi for KingdomService, så den injicerer denne værdi og stopper søgningen der. Så i dette tilfælde kiggede Angular ikke engang på træet af moduler.

Angular er doven!

Som os programmører er Angular også en procrastinator. Den opretter ikke instanser af tjenester, medmindre den virkelig har brug for det. Du kan bekræfte dette ved at tilføje en console.log-anvisning til konstruktøren af KingdomService (du kan også tilføje en alert('I love marquees'), hvis du føler dig nostalgisk i dag).

Du vil se, at console.log-anvisningen aldrig bliver udført – da Angular ikke opretter tjenesten. Hvis du fjerner providers:-deklarationen fra AppComponent (eller flytter den til UnicornComponent, så den kun gælder for enhjørningen og dens underordnede elementer), bør du begynde at se logmeddelelsen i din konsol.

Nu har Angular intet valg – den finder ikke KingdomService, når den kigger i elementtræet. Så den går først til Module Injector-træet, ser derefter, at vi har leveret tjenesten der, og opretter til sidst en instans af den. Derfor kører koden inde i konstruktøren, og du vil kunne se det debugprint, du har lagt ind der.

Directive Invasion!

Jeg nævnte, at direktiver også kan levere værdier til dependency injection. Lad os eksperimentere med det. Vi vil definere et nyt appInvader-direktiv, som vil ændre værdien af kongeriget til 👾.
Hvorfor? Fordi de var så dejlige i det VR + Angular-foredrag, som Alex Castillo og jeg holdt i ng-conf.

Så vil vi tilføje et andet <app-unicorn>-element og anvende det nye appInvader-direktiv på det:

Som forventet bor den nye enhjørning i 👾’s kingdom. Dette skyldes, at direktivet gav en værdi for KingdomService. Og som forklaret ovenfor starter Angular søgningen fra det aktuelle element og kigger på Component og alle direktiverne, og kun hvis den ikke kan finde den ønskede værdi der, fortsætter den med at gå opad i elementtræet (og derefter modulerne).

Lad os se på noget lidt mere kompliceret:

At tilføje en Content-Projecting Forest til app’en!

Vi vil tilføje en ny Forest-komponent til vores app og placere nogle af enhjørningerne inde i denne skov, fordi det er der, enhjørningerne bor (en tilfældig fyr sagde det på quora, så det må være sandt).

Skov-komponenten er simpelthen en container, som bruger Content Projection til at vise sine børn oven på en grøn “skovagtig” baggrund:

Så vi ser elementerne i AppForest-komponenten på en græsbaggrund, og derefter alt det projicerede indhold oven på en lysegrøn baggrund. Og da vi har givet en værdi for KingdomService inde i vores app-komponent, arver alt indeni den (bortset fra den ene enhjørning med appInvader-direktivet).

Men hvad sker der, hvis vi giver en ny værdi for KingdomService inde i ForestComponent? Vil det projekterede indhold (som blev defineret i skabelonen for AppComponent) også få denne nye værdi for kongeriget? Eller vil det stadig være i 🧟 riget? Kan du gætte?

Man plejede at kalde det Transclusion. Nu hedder det “Content Projection” (indholdsprojektion). Foto af ng-conf

Troldmanden i skoven

Vi tilføjer en enkelt linje til vores tidligere eksempel og giver et 🧙 kongerige til ForestComponent:

providers: 

Og dette er resultatet:

Nu er det interessant – vi ser en blanding af kongeriger inde i skoven! Selve skovelementet bor i 🧙-riget, men det projekterede indhold synes at have delt personlighed: enhjørningerne hører også til 🧙-riget, men teksten over dem viser 🧟-riget?

Vi definerede både disse enhjørninger og teksten det samme sted, nemlig linje 12-15 i app.component.html-skabelonen. Det afgørende er imidlertid det sted, hvor selve komponenten blev oprettet i DOM’en. Teksten i linje 12 er faktisk det samme som det, vi gør i linje 4 – vi læser kingdom-egenskaben i den samme AppComponent-instans. DOM-elementet for denne komponent er faktisk en forfader til <app-forest> DOM-elementet. Så da denne AppComponent-instans blev oprettet, blev den injiceret med 🧟 kingdom.

De to <app-unicorn>-elementer er dog inde i <app-forest> DOM-elementerne, så når deres instanser af UnicornComponents oprettes, går angular faktisk op i DOM’en og ser den værdi, vi har angivet for KingdomService inde i ForestComponent, og dermed injiceres disse enhjørninger med 🧙 kingdom.

Du kan opnå en anden opførsel, hvis du ændrer providers til viewProviders, når du definerer ForestComponent. Du kan lære mere om View Providers her, og du kan også se dette kodeeksempel, hvor jeg ændrede ForestComponent til at bruge View Providers, så nu injiceres selv enhjørningerne inde i skoven med 🧟 kingdom. Tak Lars Gyrup Brink Nielsen for at gøre mig opmærksom på dette!

Jeg transcluderede Chrome T-Rex i dette blogindlæg

Keep Exploring!

Jeg håber, at du lige har lært noget nyt om Angular’s Dependency Injection-system. Dette er blot en af de ting Kapunahele og jeg udforskede, da vi forberedte vores foredrag til AngularConnect. Der er meget mere – du er velkommen til at udforske yderligere, og vi vil også dele slides og linket til videoen efter foredraget. Åh, og der vil også være noget live kodning. Det skal nok blive rigtig sjovt!

Hvis du ønsker at lære mere om ins og outs af Angular Injector, er her et par artikler, som jeg fandt meget nyttige:

  • What you always wanted to know about Angular Dependency Injection
  • A curious case of the @Host decorator and Element Injectors in Angular
  • Hierarchical Dependency Injectors (Angular Docs)

Og hvis du deltager i AngularConnect, er du velkommen til at komme og sige hej!

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.