Transclusion, Injection and Procrastination

Een paar maanden geleden waren Kapunahele Wong en ik aan het brainstormen over ideeën voor praatvoorstellen die we samen konden doen. Het was rond de tijd dat ik besloot om Ivy te onderzoeken, dus ik dacht dat dat wel een goed idee zou zijn voor een lezing. Kapunahele had echter een ander idee:

Twitter, where the fun stuff is happening!

Dit was de eerste keer dat ik hoorde over Injector Trees, dus ik was geïntrigeerd. Kapunahele deelde met mij wat van haar vooronderzoek, en ik leerde dat het afhankelijkheidsoplossingsalgoritme van de Angular Injector niet zo eenvoudig was als ik eerder dacht.

In feite, sinds NgModules werden geïntroduceerd in de 5e release candidate van Angular 2.0.0, leverde ik alleen services op moduleniveau, met behulp van de bekende eigenschap providers:

Of sinds Angular 6, met behulp van de eigenschap providedIn van de decorator Injectable:

Hoe dan ook, ik declareerde mijn services altijd op moduleniveau, en besteedde nooit te veel aandacht aan de andere mogelijkheden.

Enter Injector Trees 🌴

Kapunahele en ik besloten om onze Injector Trees-praatje in te dienen bij Angular Connect. Enkele maanden later belandde het volgende bericht in mijn inbox:

We waren helemaal opgepompt, onze talk was geaccepteerd voor de conferentie! 😊

We hebben de volgende weken alle verborgen hoeken van de werking van dependency injection in Angular verkend. In deze blog post ga ik er een aantal met jullie delen.

Start Simple

We beginnen met een eenvoudige Angular app. Deze app heeft een “Koninkrijk”-service en een enkel component dat Koninkrijk injecteert en de naam van het Koninkrijk weergeeft:

Ik heb besloten om voor het voorbeeld een Draak te gebruiken, omdat ik die graag gebruik in plaats van puntkomma’s in mijn code.

Om de zaken wat interessanter te maken, laten we onze app kruiden met een Unicorn-component. Deze component drukt de naam af van het koninkrijk waar hij woont:

Dus we hebben een app-component, en een eenhoorn erin. Geweldig!

Nu, wat gebeurt er als we de definitie van onze AppComponent wijzigen om een andere waarde voor de KingdomService op te geven?

Dit kunnen we doen door de volgende regel aan de componentdeclaratie toe te voegen:

providers: 

Hoe zal dit onze app beïnvloeden? Laten we het eens proberen:

Zoals u kunt zien, heeft de waarde die we voor KingdomService in onze AppComponent hebben gedefinieerd voorrang boven de service die in onze AppModule is gedefinieerd (deze was daar niet rechtstreeks gedefinieerd, maar met behulp van providedIn, maar het resultaat is hetzelfde).

Elementenboom, moduleboom

De reden dat we zombies zien, is de manier waarop dependency resolution in Angular werkt. Het doorzoekt eerst de boom van componenten, en dan pas de boom van modules. Laten we UnicornComponent eens bekijken. Het injecteert een instantie van KingdomService in zijn constructor:

constructor(public kingdom: KingdomService) {}

Wanneer Angular dit component maakt, kijkt het eerst of er providers zijn gedefinieerd op hetzelfde element als het component. Deze providers kunnen zijn geregistreerd op de component zelf, of met behulp van een directive. In dit geval hebben we geen waarde opgegeven voor KingdomService binnen de UnicornComponent, en hebben we ook geen directives op het <app-unicorn> element.

Het zoeken gaat dan verder omhoog in de element tree, naar AppComponent. Hier vindt Angular dat we een waarde hebben voor KingdomService, dus injecteert het deze waarde en stopt daar met zoeken. Dus in dit geval heeft Angular niet eens naar de boom van modules gekeken.

Angular is lui!

Net als wij programmeurs, is Angular ook een uitsteller. Het maakt geen instanties van diensten, tenzij het echt nodig is. Je kunt dit bevestigen door een console.log statement toe te voegen aan de constructor van KingdomService (je kunt ook een alert('I love marquees') toevoegen als je je nostalgisch voelt vandaag).

Je zult zien dat het console.log statement nooit wordt uitgevoerd – omdat Angular de service niet aanmaakt. Als je de providers: declaratie uit de AppComponent verwijdert (of verplaatst naar de UnicornComponent, zodat het alleen van toepassing is op de eenhoorn en zijn kind-elementen), zou je de log-melding in je console moeten gaan zien.

Nu heeft Angular geen keus – het vindt de KingdomService niet wanneer het in de Element Tree kijkt. Dus, eerst gaat het naar de Module Injector Tree, ziet dan dat we de service daar hebben geleverd, en maakt er tenslotte een instantie van. De code in de constructor wordt uitgevoerd, en je kunt de debug print zien die je daar hebt gezet.

Directive Invasion!

Ik zei dat directives ook waarden kunnen geven voor dependency injection. Laten we daar eens mee experimenteren. We gaan een nieuwe appInvader directive definiëren, die de waarde van het koninkrijk zal veranderen in 👾.
Waarom? Omdat ze zo mooi waren in de VR + Angular-talk die Alex Castillo en ik gaven in ng-conf.

Daarna voegen we nog een <app-unicorn>-element toe, en passen de nieuwe appInvader-richtlijn erop toe:

Zoals verwacht, woont de nieuwe eenhoorn in het koninkrijk 👾. Dit komt omdat de directive een waarde voor KingdomService heeft opgegeven. En zoals hierboven is uitgelegd, begint Angular met zoeken vanaf het huidige element, waarbij het kijkt naar de Component en alle Directives, en alleen als het daar de gevraagde waarde niet kan vinden, gaat het verder omhoog in de elementenboom (en dan de modules).

Laten we eens kijken naar iets wat iets ingewikkelder is:

Een Content-Projecting Forest toevoegen aan de App!

We voegen een nieuw Bos-component aan onze app toe, en zetten een aantal eenhoorns in dit bos, want daar leven eenhoorns (een of andere willekeurige vent zei dat op quora, dus het zal wel waar zijn).

De bos-component is gewoon een container, die Content Projection gebruikt om zijn kinderen bovenop een groene “bosachtige” achtergrond weer te geven:

Dus we zien de elementen van de AppForest-component op een grasachtergrond, en vervolgens alle geprojecteerde inhoud bovenop een heldergroene achtergrond. En omdat we een waarde hebben opgegeven voor KingdomService in onze app-component, erft alles daarbinnen deze waarde (behalve die ene eenhoorn met de appInvader-richtlijn).

Maar wat gebeurt er als we een nieuwe waarde opgeven voor KingdomService in de ForestComponent? Krijgt de geprojecteerde inhoud (die in het sjabloon voor AppComponent was gedefinieerd) ook deze nieuwe waarde voor het koninkrijk? Of zal het nog steeds in het 🧟 koninkrijk staan? Kun je het raden?

Vroeger heette het Transclusion. Nu heet het “Content Projection”. Foto door ng-conf

The Wizard of The Forest

We voegen een enkele regel toe aan ons vorige voorbeeld, en geven een 🧙 koninkrijk voor de ForestComponent:

providers: 

En dit is het resultaat:

Nu is dit interessant – we zien een mengelmoes van koninkrijken binnen het bos! Het boselement zelf bevindt zich in het 🧙 koninkrijk, maar de geprojecteerde inhoud lijkt een gespleten persoonlijkheid te hebben: de eenhoorns behoren ook tot het 🧙 koninkrijk, maar de tekst erboven toont 🧟 koninkrijk?

We hebben zowel deze eenhoorns als de tekst op dezelfde plaats gedefinieerd, regels 12-15 van het app.component.html sjabloon. Wat echter van belang is, is de plaats waar de component zelf in het DOM is gemaakt. De tekst in regel 12 is eigenlijk hetzelfde als wat we doen in regel 4 – we lezen de kingdom eigenschap van dezelfde AppComponent instantie. Het DOM element voor deze component is eigenlijk een voorouder van het <app-forest> DOM element. Dus toen deze AppComponent instantie werd gemaakt, werd het geïnjecteerd met het 🧟 koninkrijk.

De twee <app-unicorn> elementen, zijn echter binnen de <app-forest> DOM-elementen, dus wanneer hun instanties van UnicornComponents worden gemaakt, angular gaat eigenlijk de DOM en ziet de waarde die we voor de KingdomService binnen de ForestComponent, en dus deze eenhoorns worden geïnjecteerd met het 🧙 koninkrijk.

U kunt een ander gedrag bereiken als u providers verandert in viewProviders bij het definiëren van de ForestComponent. Je kunt hier meer leren over View Providers, en bekijk ook dit code voorbeeld, waar ik ForestComponent heb veranderd om View Providers te gebruiken, zodat nu zelfs de eenhoorns in het bos worden geïnjecteerd met het 🧟 koninkrijk. Met dank aan Lars Gyrup Brink Nielsen die me hierop wees!

Ik heb de Chrome T-Rex in deze blogpost

Keep Exploring!

Ik hoop dat u zojuist iets nieuws heeft geleerd over Angular’s Dependency Injection-systeem. Dit is slechts een van de dingen die Kapunahele en ik hebben onderzocht toen we onze toespraak voor AngularConnect voorbereidden. Er is nog veel meer – je bent uitgenodigd om verder te verkennen, en we zullen ook de slides en de link naar de video delen na de talk. Oh, en er zal ook wat live coding zijn. Het wordt heel leuk!

Als je meer wilt weten over de ins en outs van de Angular Injector, zijn hier een paar artikelen die ik erg nuttig vond:

  • Wat je altijd al wilde weten over Angular Dependency Injection
  • Een merkwaardig geval van de @Host decorator en Element Injectors in Angular
  • Hierarchische Dependency Injectors (Angular Docs)

En als je AngularConnect bijwoont, ben je van harte uitgenodigd om hallo te komen zeggen!

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.