Exploring The Hidden Corners of Angular Dependency Injection
Cu câteva luni în urmă, Kapunahele Wong și cu mine făceam un brainstorming de idei pentru propuneri de discuții pe care le-am putea face împreună. A fost cam în perioada în care am decis să explorez Ivy, așa că m-am gândit că ar putea fi o idee bună pentru o discuție. Kapunahele, însă, a avut o idee diferită:
Aceasta a fost prima dată când am auzit despre Injector Trees, așa că am fost intrigat. Kapunahele mi-a împărtășit o parte din cercetările sale preliminare și am aflat că algoritmul de rezolvare a dependențelor din Angular Injector nu era atât de simplu pe cât credeam înainte.
De fapt, de când NgModules a fost introdus în cea de-a 5-a versiune candidată a Angular 2.0.0, ofeream servicii doar la nivel de modul, folosind cunoscuta proprietate providers:
Sau, începând cu Angular 6, folosind proprietatea providedIn a decoratorului Injectable:
În orice caz, mi-am declarat întotdeauna serviciile la nivel de modul și nu am acordat prea multă atenție celorlalte posibilități.
Intrați în Injector Trees 🌴
Kapunahele și cu mine am decis să trimitem discuția noastră despre Injector Trees la Angular Connect. Câteva luni mai târziu, următorul mesaj a aterizat în inbox-ul meu:
Eram entuziasmați, discuția noastră a fost acceptată pentru conferință! 😊
Ne-am petrecut următoarele câteva săptămâni explorând toate colțurile ascunse ale modului în care funcționează injecția de dependență în Angular. În această postare pe blog, am de gând să vă împărtășesc câteva dintre ele.
Începând simplu
Vom începe cu o aplicație Angular simplă. Această aplicație are un serviciu „Kingdom” și o singură componentă care injectează Kingdom și afișează numele regatului:
Am decis să aleg un Dragon pentru exemplu, deoarece îmi place să le folosesc în loc de punct și virgulă în codul meu.
Pentru a face lucrurile un pic mai interesante, haideți să ne condimentăm aplicația cu o componentă Unicorn. Această componentă imprimă numele regatului în care trăiește:
Acum avem o componentă de aplicație și un unicorn în interiorul ei. Grozav!
Acum, ce se întâmplă când schimbăm definiția AppComponent
pentru a oferi o valoare diferită pentru KingdomService
?
Potem face acest lucru adăugând următoarea linie la declarația componentei:
providers:
Cum va afecta acest lucru aplicația noastră? Să încercăm și să vedem:
După cum puteți vedea, valoarea pe care am definit-o pentru KingdomService
în AppComponent
a avut prioritate față de serviciul definit în AppModule (nu a fost definit direct acolo, ci mai degrabă folosind providedIn
, dar rezultatul este același).
Element Tree, Module Tree
Motivul pentru care vedem zombi este modul în care funcționează rezolvarea dependențelor în Angular. Acesta caută mai întâi în arborele de componente și abia apoi în arborele de module. Să luăm în considerare UnicornComponent
. Acesta injectează o instanță de KingdomService
în interiorul constructorului său:
constructor(public kingdom: KingdomService) {}
Când Angular creează această componentă, se uită mai întâi dacă există furnizori definiți pe același element ca și componenta. Acești furnizori ar fi putut fi înregistrați pe componenta însăși sau folosind o directivă. În acest caz, nu am furnizat nicio valoare pentru KingdomService
în interiorul UnicornComponent
, și nici nu avem vreo directivă pe elementul <app-unicorn>
.
Cercetarea continuă apoi în susul arborelui de elemente, mergând până la AppComponent
. Aici Angular constată că avem o valoare pentru KingdomService
, așa că injectează această valoare și oprește căutarea acolo. Deci, în acest caz, Angular nici măcar nu s-a uitat la arborele de module.
Angular este leneș!
La fel ca noi, programatorii, Angular este, de asemenea, un procrastinator. Nu creează instanțe de servicii decât dacă este cu adevărat necesar. Puteți confirma acest lucru adăugând o instrucțiune console.log
la constructorul lui KingdomService
(puteți adăuga și un alert('I love marquees')
dacă vă simțiți nostalgici astăzi).
Veți vedea că instrucțiunea console.log
nu este niciodată executată – deoarece Angular nu creează serviciul. Dacă eliminați declarația providers:
din AppComponent
(sau mutați-o în UnicornComponent
, astfel încât să se aplice doar unicornului și elementelor sale copil), ar trebui să începeți să vedeți mesajul de jurnal în consolă.
Acum Angular nu are de ales – nu găsește KingdomService
atunci când caută în Element Tree. Deci, mai întâi se duce la Module Injector Tree, apoi vede că am furnizat serviciul acolo și, în cele din urmă, creează o instanță a acestuia. Prin urmare, codul din interiorul constructorului rulează și veți putea vedea imprimarea de depanare pe care ați pus-o acolo.
Invazia directivelor!
Am menționat că directivele pot furniza, de asemenea, valori pentru injectarea dependențelor. Haideți să experimentăm cu asta. Vom defini o nouă directivă appInvader
, care va schimba valoarea regnului în 👾.
De ce? Pentru că au fost atât de drăguțe în discuția VR + Angular pe care Alex Castillo și cu mine am ținut-o în ng-conf.
Apoi, vom adăuga un alt element <app-unicorn>
și îi vom aplica noua directivă appInvader
:
Cum era de așteptat, noul unicorn trăiește în regatul lui 👾. Acest lucru se datorează faptului că directiva a furnizat o valoare pentru KingdomService
. Și, așa cum s-a explicat mai sus, Angular începe căutarea de la elementul curent, uitându-se la Component și la toate Directivele, și numai dacă nu găsește acolo valoarea solicitată, continuă să urce în arborele de elemente (și apoi în module).
Să ne uităm la ceva puțin mai complicat:
Adaugarea unei păduri care proiectează conținut în aplicație!
Vom adăuga o nouă componentă Forest la aplicația noastră și vom pune o parte din unicorni în interiorul acestei păduri, pentru că acolo trăiesc unicornii (un tip oarecare a spus asta pe Quora, deci trebuie să fie adevărat).
Componenta Forest este pur și simplu un container, care folosește Content Projection pentru a-și afișa copiii deasupra unui fundal verde „forestier”:
Așa că vedem elementele componentei AppForest
pe un fundal de iarbă, iar apoi, tot conținutul proiectat deasupra unui fundal verde strălucitor. Și din moment ce am furnizat o valoare pentru KingdomService
în interiorul componentei noastre de aplicație, tot ce se află înăuntru o moștenește (cu excepția unicornului cu directiva appInvader
).
Dar ce se întâmplă dacă furnizăm o nouă valoare pentru KingdomService
în interiorul ForestComponent
? Conținutul proiectat (care a fost definit în șablonul pentru AppComponent
) va primi, de asemenea, această nouă valoare pentru regat? Sau va fi în continuare în regnul 🧟? Poți să ghicești?
Vrăjitorul Pădurii
Vom adăuga o singură linie la exemplul nostru anterior, furnizând un regat 🧙 pentru ForestComponent
:
providers:
Și acesta este rezultatul:
Acum acest lucru este interesant – vedem un amestec de regate în interiorul pădurii! Elementul de pădure în sine trăiește în regnul 🧙, dar conținutul proiectat pare să aibă personalitate divizată: unicornii aparțin, de asemenea, regnului 🧙, dar textul de deasupra lor arată regnul 🧟?
Am definit atât acești unicorni, cât și textul în același loc, liniile 12-15 din șablonul app.component.html
. Cu toate acestea, ceea ce contează este locul în care componenta însăși a fost creată în DOM. Textul din linia 12 este, de fapt, același cu ceea ce facem în linia 4 – citim proprietatea kingdom
a aceleiași instanțe AppComponent
. Elementul DOM pentru această componentă este, de fapt, un strămoș al elementului DOM <app-forest>
. Deci, atunci când această instanță AppComponent
a fost creată, a fost injectată cu regnul 🧟.
Cele două elemente <app-unicorn>
sunt, totuși, în interiorul elementelor DOM <app-forest>
, așa că atunci când instanțele lor de UnicornComponents
sunt create, angular urcă de fapt în DOM și vede valoarea pe care am furnizat-o pentru KingdomService
în interiorul ForestComponent
, și astfel acești unicorni sunt injectați cu regnul 🧙.
Puteți obține un comportament diferit dacă schimbați providers
în viewProviders
atunci când definiți ForestComponent
. Puteți afla mai multe despre View Providers aici și, de asemenea, puteți verifica acest exemplu de cod, în care am schimbat ForestComponent
pentru a utiliza View Providers, astfel încât acum chiar și unicornii din interiorul pădurii sunt injectați cu regatul 🧟. Îi mulțumesc lui Lars Gyrup Brink Nielsen pentru că mi-a semnalat acest lucru!
Continuă explorarea!
Sper că tocmai ați învățat ceva nou despre sistemul de Injecție de dependență al Angular. Acesta este doar unul dintre lucrurile pe care Kapunahele și cu mine le-am explorat atunci când am pregătit conferința noastră pentru AngularConnect. Există mult mai multe – sunteți invitați să explorați mai departe, iar noi vom împărtăși, de asemenea, slide-urile și linkul către video după discuție. Oh, și va fi și ceva live coding. Va fi foarte distractiv!
Dacă doriți să aflați mai multe despre ins și out of the Angular Injector, iată câteva articole pe care le-am găsit foarte utile:
- Ce ați vrut întotdeauna să știți despre Angular Dependency Injection
- Un caz curios al decoratorului @Host și al Element Injectors în Angular
- Hierarchical Dependency Injectors (Angular Docs)
Și dacă participați la AngularConnect, sunteți invitați să veniți să ne salutați!