Transclusion, Injection and Procrastination

Před několika měsíci jsme s Kapunahele Wongem přemýšleli o nápadech na přednášky, které bychom mohli udělat společně. Bylo to v době, kdy jsem se rozhodl prozkoumat Ivy, takže mě napadlo, že by se to mohlo hodit na přednášku. Kapunahele měl ale jiný nápad:

Twitter, kde se dějí zábavné věci!“

O Injector Trees jsem slyšel poprvé, takže mě to zaujalo. Kapunahele se se mnou podělila o část svého předběžného výzkumu a já jsem se dozvěděl, že algoritmus řešení závislostí v Angular Injectoru není tak jednoduchý, jak jsem si předtím myslel.

V podstatě od doby, kdy byly NgModules představeny v 5. kandidátské verzi Angularu 2.0, jsem si myslel, že je to tak jednoduché.0 jsem služby poskytoval pouze na úrovni modulu pomocí známé vlastnosti providers:

Nebo od verze Angular 6 pomocí vlastnosti providedIn dekorátoru Injectable:

Tak či onak jsem služby vždy deklaroval na úrovni modulu a jiným možnostem jsem nikdy nevěnoval příliš pozornosti.

Vstup do Injector Trees 🌴

Kapunahele a já jsme se rozhodli poslat naši přednášku o Injector Trees na Angular Connect. O několik měsíců později mi ve schránce přistála následující zpráva:

Byli jsme nadšení, naše přednáška byla na konferenci přijata! 😊

Následujících několik týdnů jsme strávili zkoumáním všech skrytých zákoutí fungování injection závislostí v Angularu. V tomto příspěvku na blogu se s vámi o některé z nich podělím.

Začneme jednoduše

Začneme jednoduchou aplikací Angular. Tato aplikace má službu „Království“ a jednu komponentu, která injektuje Království a zobrazuje jeho název:

Pro příklad jsem se rozhodl použít draka, protože je rád používám v kódu místo středníků.

Aby to bylo trochu zajímavější, okořeníme naši aplikaci komponentou Jednorožec. Tato komponenta vypíše název království, ve kterém žije:

Máme tedy komponentu aplikace a v ní jednorožce. Skvělé!“

Nyní, co se stane, když změníme definici naší komponenty AppComponent a poskytneme jinou hodnotu pro KingdomService?

To můžeme udělat přidáním následujícího řádku do deklarace komponenty:

providers: 

Jak to ovlivní naši aplikaci? Vyzkoušíme to a uvidíme:

Jak vidíte, hodnota, kterou jsme definovali pro KingdomService v našem AppComponent, dostala přednost před službou definovanou v našem AppModulu (nebyla tam definována přímo, spíše pomocí providedIn, ale výsledek je stejný).

Strom prvků, strom modulů

Důvodem, proč vidíme zombie, je způsob, jakým v Angularu funguje řešení závislostí. Nejprve prohledává strom komponent a teprve potom strom modulů. Podívejme se na příklad UnicornComponent. Injektuje instanci KingdomService uvnitř svého konstruktoru:

constructor(public kingdom: KingdomService) {}

Když Angular vytvoří tuto komponentu, nejprve se podívá, zda jsou na stejném elementu jako komponenta definováni nějací poskytovatelé. Tito zprostředkovatelé mohli být zaregistrováni na samotné komponentě nebo pomocí směrnice. V tomto případě jsme neposkytli žádnou hodnotu pro KingdomService uvnitř prvku UnicornComponent, ani nemáme žádnou direktivu na prvku <app-unicorn>.

Prohledávání pak pokračuje směrem nahoru po stromu prvků až k prvku AppComponent. Zde Angular zjistí, že máme hodnotu pro KingdomService, takže tuto hodnotu vloží a hledání zde ukončí. V tomto případě se tedy Angular ani nepodíval do stromu modulů.

Angular je líný!“

Stejně jako my programátoři je i Angular prokrastinátor. Nevytváří instance služeb, pokud to není nezbytně nutné. Můžete to potvrdit přidáním příkazu console.log do konstruktoru KingdomService (můžete také přidat alert('I love marquees'), pokud dnes cítíte nostalgii).

Uvidíte, že příkaz console.log se nikdy neprovede – protože Angular službu nevytváří. Pokud z AppComponent odstraníte deklaraci providers: (nebo ji přesunete do UnicornComponent, takže se bude týkat pouze jednorožce a jeho podřízených prvků), měli byste v konzoli začít vidět hlášení protokolu.

Nyní Angular nemá na výběr – při hledání ve stromu prvků nenajde KingdomService. Nejprve tedy přejde do stromu Module Injector, pak uvidí, že jsme službu poskytli tam, a nakonec vytvoří její instanci. Proto se kód uvnitř konstruktoru spustí a vy budete moci vidět ladicí výpis, který jste tam vložili.

Invaze direktiv!“

Zmínil jsem se, že direktivy mohou také poskytovat hodnoty pro vstřikování závislostí. Pojďme si s tím zaexperimentovat. Budeme definovat novou direktivu appInvader, která změní hodnotu království na 👾.
Proč? Protože byly tak krásné v přednášce VR + Angular, kterou jsme s Alexem Castillem vedli na ng-conf.

Poté přidáme další element <app-unicorn> a použijeme na něj novou direktivu appInvader:

Podle očekávání bude nový jednorožec žít v království 👾. Je to proto, že směrnice poskytla hodnotu KingdomService. A jak bylo vysvětleno výše, Angular začne hledání od aktuálního prvku, podívá se na komponentu a všechny směrnice, a teprve když tam požadovanou hodnotu nenajde, pokračuje dál po stromu prvků (a pak modulů).

Podívejme se na něco trochu složitějšího:

Přidání lesa promítajícího obsah do aplikace!

Přidáme do naší aplikace novou komponentu Les a do tohoto lesa umístíme některé jednorožce, protože tam jednorožci žijí (to řekl nějaký náhodný člověk na Quoře, takže to musí být pravda).

Komponenta Les je prostě kontejner, který pomocí promítání obsahu zobrazuje své děti na zeleném „lesním“ pozadí:

Takže vidíme prvky komponenty AppForest na pozadí trávy a pak, veškerý promítaný obsah na jasně zeleném pozadí. A protože jsme uvnitř naší komponenty aplikace zadali hodnotu pro KingdomService, vše uvnitř ji zdědí (kromě jednoho jednorožce se směrnicí appInvader).

Ale co se stane, když uvnitř komponenty ForestComponent zadáme novou hodnotu pro KingdomService? Dostane promítaný obsah (který byl definován v šabloně pro AppComponent) také tuto novou hodnotu pro království? Nebo bude stále v království 🧟? Dokážete to odhadnout?

Dříve se tomu říkalo Transclusion. Nyní se tomu říká „Projekce obsahu“. Foto: ng-conf

Čaroděj z lesa

Přidáme k našemu předchozímu příkladu jeden řádek, který poskytne 🧙 království pro ForestComponent:

providers: 

A toto je výsledek:

Teď je to zajímavé – vidíme směs království uvnitř lesa! Samotný prvek lesa žije v království 🧙, ale promítaný obsah jako by měl rozdvojenou osobnost: jednorožci také patří do království 🧙, ale text nad nimi ukazuje království 🧟?“

Tyto jednorožce i text jsme definovali na stejném místě, na řádcích 12-15 šablony app.component.html. Důležité je však místo, kde byla v DOM vytvořena samotná komponenta. Text na řádku 12 je vlastně totéž, co děláme na řádku 4 – čteme vlastnost kingdom téže instance AppComponent. Element DOM pro tuto komponentu je ve skutečnosti předkem elementu DOM <app-forest>. Takže když byla vytvořena tato instance AppComponent, byla injektována s královstvím 🧟.

Dva prvky <app-unicorn> jsou však uvnitř prvků DOM <app-forest>, takže když jsou vytvořeny jejich instance UnicornComponents, angular vlastně prochází DOM a vidí hodnotu, kterou jsme poskytli pro KingdomService uvnitř ForestComponent, a tak jsou tyto jednorožce injektovány s královstvím 🧙.

Jiného chování dosáhnete, pokud při definování ForestComponent změníte providers na viewProviders. Více o zprostředkovatelích zobrazení se dozvíte zde a podívejte se také na tento příklad kódu, kde jsem změnil ForestComponent tak, aby používal zprostředkovatele zobrazení, takže nyní jsou i jednorožci uvnitř lesa injektováni s královstvím 🧟. Děkuji Larsu Gyrupu Brink Nielsenovi, že mě na to upozornil!!!

Překládal jsem Chrome T-Rex v tomto příspěvku na blogu

Další zkoumání!

Doufám, že jste se právě dozvěděli něco nového o systému Angular Dependency Injection. Je to jen jedna z věcí, které jsme s Kapunahelem prozkoumali, když jsme připravovali naši přednášku pro AngularConnect. Je toho mnohem víc – zveme vás k dalšímu zkoumání a po přednášce se s vámi podělíme také o slajdy a odkaz na video. Jo, a taky tam bude nějaké živé kódování. Bude to velká zábava!

Pokud se chcete dozvědět více o úskalích Angular Injectoru, zde je několik článků, které mi přišly velmi užitečné:

  • Co jste vždycky chtěli vědět o Angular Dependency Injection
  • Kuriózní případ dekorátoru @Host a Element Injectors v Angularu
  • Hierarchické Dependency Injectors (Angular Docs)

A pokud se účastníte konference AngularConnect, jste zváni, abyste je přišli pozdravit!

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.