Exploring The Hidden Corners of Angular Dependency Injection
Vor ein paar Monaten haben Kapunahele Wong und ich Ideen für Vorträge gesammelt, die wir gemeinsam halten könnten. Das war ungefähr zu der Zeit, als ich beschloss, Ivy zu erforschen, also dachte ich, das könnte gut für einen Vortrag geeignet sein. Kapunahele hatte jedoch eine andere Idee:
Das war das erste Mal, dass ich von Injector Trees hörte, also war ich fasziniert. Kapunahele teilte mit mir einige ihrer Voruntersuchungen, und ich lernte, dass der Algorithmus zur Auflösung von Abhängigkeiten des Angular Injectors nicht so einfach war, wie ich zuvor dachte.
In der Tat, seit NgModules im 5. Release Candidate von Angular 2.0 eingeführt wurden.0 eingeführt wurden, habe ich nur Dienste auf Modulebene bereitgestellt, indem ich die bekannte Provider-Eigenschaft verwendet habe:
Oder seit Angular 6, indem ich die providedIn-Eigenschaft des Injectable-Dekorators verwendet habe:
So oder so, ich habe meine Dienste immer auf Modulebene deklariert und den anderen Möglichkeiten nie allzu viel Aufmerksamkeit geschenkt.
Enter Injector Trees 🌴
Kapunahele und ich beschlossen, unseren Injector Trees-Vortrag bei Angular Connect einzureichen. Einige Monate später landete die folgende Nachricht in meinem Posteingang:
Wir waren begeistert, unser Vortrag wurde für die Konferenz angenommen! 😊
Wir verbrachten die nächsten Wochen damit, alle versteckten Ecken zu erkunden, wie Dependency Injection in Angular funktioniert. In diesem Blogpost werde ich einige davon mit euch teilen.
Einfach anfangen
Wir werden mit einer einfachen Angular-App beginnen. Diese App hat einen „Kingdom“-Dienst und eine einzelne Komponente, die Kingdom injiziert und den Namen des Königreichs anzeigt:
Ich habe mich für das Beispiel für einen Drachen entschieden, da ich sie gerne anstelle von Semikolons in meinem Code verwende.
Um die Dinge etwas interessanter zu machen, wollen wir unsere App mit einer Einhorn-Komponente aufpeppen. Diese Komponente gibt den Namen des Königreichs aus, in dem sie lebt:
So haben wir eine App-Komponente und ein Einhorn darin. Großartig!
Was passiert nun, wenn wir die Definition unseres AppComponent
ändern, um einen anderen Wert für das KingdomService
bereitzustellen?
Wir können dies tun, indem wir die folgende Zeile zur Komponentendeklaration hinzufügen:
providers:
Wie wird sich dies auf unsere App auswirken? Probieren wir es aus:
Wie Sie sehen, hat der Wert, den wir für KingdomService
in unserem AppComponent
definiert haben, Vorrang vor dem Dienst, der in unserem AppModule definiert ist (er wurde dort nicht direkt definiert, sondern mit providedIn
, aber das Ergebnis ist dasselbe).
Elementbaum, Modulbaum
Der Grund, warum wir Zombies sehen, ist die Art und Weise, wie die Abhängigkeitsauflösung in Angular funktioniert. Sie durchsucht zuerst den Baum der Komponenten und erst dann den Baum der Module. Betrachten wir UnicornComponent
. Es injiziert eine Instanz von KingdomService
innerhalb seines Konstruktors:
constructor(public kingdom: KingdomService) {}
Wenn Angular diese Komponente erstellt, schaut es zuerst, ob es irgendwelche Provider gibt, die auf dem gleichen Element wie die Komponente definiert sind. Diese Anbieter können in der Komponente selbst oder über eine Direktive registriert worden sein. In diesem Fall haben wir keinen Wert für KingdomService
innerhalb von UnicornComponent
angegeben, und wir haben auch keine Direktive für das <app-unicorn>
-Element.
Die Suche geht dann im Elementbaum weiter nach oben bis zu AppComponent
. Hier stellt Angular fest, dass wir einen Wert für KingdomService
haben, also injiziert es diesen Wert und beendet die Suche dort. In diesem Fall hat Angular also nicht einmal in den Modulbaum geschaut.
Angular ist faul!
Genauso wie wir Programmierer ist auch Angular ein Zauderer. Es erstellt keine Instanzen von Diensten, wenn es nicht wirklich nötig ist. Sie können dies bestätigen, indem Sie eine console.log
-Anweisung zum Konstruktor von KingdomService
hinzufügen (Sie können auch eine alert('I love marquees')
hinzufügen, wenn Sie sich heute nostalgisch fühlen).
Sie werden sehen, dass die console.log
-Anweisung nie ausgeführt wird – da Angular den Dienst nicht erstellt. Wenn Sie die providers:
-Deklaration aus dem AppComponent
entfernen (oder in das UnicornComponent
verschieben, so dass sie nur für das Einhorn und seine untergeordneten Elemente gilt), sollten Sie die Log-Meldung in Ihrer Konsole sehen.
Jetzt hat Angular keine Wahl – es findet das KingdomService
nicht, wenn es im Elementbaum sucht. Also geht es zuerst zum Module Injector Tree, sieht dann, dass wir den Dienst dort bereitgestellt haben, und erstellt schließlich eine Instanz davon. Daher wird der Code im Konstruktor ausgeführt, und Sie können den Debug-Ausdruck sehen, den Sie dort eingefügt haben.
Direktive Invasion!
Ich habe erwähnt, dass Direktiven auch Werte für Dependency Injection bereitstellen können. Lassen Sie uns damit experimentieren. Wir werden eine neue appInvader
-Direktive definieren, die den Wert des Königreichs auf 👾 ändert.
Warum? Weil sie in dem VR + Angular-Vortrag, den Alex Castillo und ich in der ng-conf gehalten haben, so schön waren.
Dann fügen wir ein weiteres <app-unicorn>
-Element hinzu und wenden die neue appInvader
-Direktive darauf an:
Wie erwartet, lebt das neue Einhorn im Königreich 👾. Das liegt daran, dass die Direktive einen Wert für KingdomService
liefert. Und wie oben erklärt, beginnt Angular die Suche mit dem aktuellen Element, schaut sich die Komponente und alle Direktiven an, und nur wenn es den angeforderten Wert dort nicht findet, geht es weiter den Elementbaum hinauf (und dann die Module).
Lassen Sie uns etwas Komplizierteres betrachten:
Hinzufügen eines Content-Projecting Forest zur App!
Wir fügen eine neue Waldkomponente zu unserer App hinzu und platzieren einige der Einhörner in diesem Wald, denn dort leben Einhörner (das hat irgendein Typ auf Quora gesagt, also muss es wahr sein).
Die Komponente „Wald“ ist einfach ein Container, der die Inhaltsprojektion verwendet, um seine Kinder auf einem grünen „Wald“-Hintergrund anzuzeigen:
So sehen wir die Elemente der Komponente AppForest
auf einem Grashintergrund, und dann alle projizierten Inhalte auf einem hellgrünen Hintergrund. Und da wir in unserer App-Komponente einen Wert für KingdomService
angegeben haben, erbt alles, was darin enthalten ist, diesen Wert (mit Ausnahme des Einhorns mit der appInvader
-Direktive).
Aber was passiert, wenn wir einen neuen Wert für KingdomService
in ForestComponent
angeben? Wird der projizierte Inhalt (der in der Vorlage für AppComponent
definiert wurde) auch diesen neuen Wert für das Königreich erhalten? Oder wird er immer noch im 🧟-Reich sein? Können Sie es erraten?
Der Zauberer des Waldes
Wir fügen eine einzige Zeile zu unserem vorherigen Beispiel hinzu, indem wir ein 🧙 Königreich für das ForestComponent
:
providers:
Und das ist das Ergebnis:
Nun ist das interessant – wir sehen eine Mischung von Königreichen innerhalb des Waldes! Das Waldelement selbst lebt im 🧙-Reich, aber der projizierte Inhalt scheint eine gespaltene Persönlichkeit zu haben: die Einhörner gehören auch zum 🧙-Reich, aber der Text über ihnen zeigt das 🧟-Reich…
Wir haben sowohl die Einhörner als auch den Text an der gleichen Stelle definiert, in den Zeilen 12-15 der app.component.html
-Vorlage. Entscheidend ist jedoch die Stelle, an der die Komponente selbst im DOM erstellt wurde. Der Text in Zeile 12 ist eigentlich derselbe wie in Zeile 4 – wir lesen die Eigenschaft kingdom
derselben AppComponent
-Instanz. Das DOM-Element für diese Komponente ist eigentlich ein Vorfahre des DOM-Elements <app-forest>
. Als diese AppComponent
-Instanz erstellt wurde, wurde ihr also das 🧟-Königreich injiziert.
Die beiden <app-unicorn>
-Elemente befinden sich jedoch innerhalb der <app-forest>
-DOM-Elemente. Wenn also ihre UnicornComponents
-Instanzen erstellt werden, geht Angular das DOM hoch und sieht den Wert, den wir für KingdomService
innerhalb von ForestComponent
angegeben haben, und so werden diese Einhörner mit dem 🧙-Königreich injiziert.
Sie können ein anderes Verhalten erreichen, wenn Sie bei der Definition des ForestComponent
providers
in viewProviders
ändern. Du kannst hier mehr über View Provider erfahren und dir auch dieses Codebeispiel ansehen, in dem ich ForestComponent
geändert habe, um View Provider zu verwenden, so dass jetzt sogar die Einhörner im Wald mit dem 🧟 Königreich injiziert werden. Danke Lars Gyrup Brink Nielsen für den Hinweis!
Keep Exploring!
Ich hoffe, Sie haben gerade etwas Neues über das Dependency Injection System von Angular gelernt. Dies ist nur eines der Dinge, die Kapunahele und ich erforscht haben, als wir unseren Vortrag für die AngularConnect vorbereitet haben. Es gibt noch viel mehr – Sie sind eingeladen, weiter zu forschen, und wir werden auch die Folien und den Link zum Video nach dem Vortrag teilen. Oh, und es wird auch etwas Live-Coding geben. Es wird eine Menge Spaß machen!
Wenn Sie mehr über den Angular Injector erfahren möchten, finden Sie hier ein paar Artikel, die ich sehr hilfreich fand:
- Was Sie schon immer über Angular Dependency Injection wissen wollten
- Ein kurioser Fall des @Host-Dekorators und Element-Injectors in Angular
- Hierarchische Dependency Injectors (Angular Docs)
Und wenn Sie an der AngularConnect teilnehmen, sind Sie herzlich eingeladen, vorbeizukommen und Hallo zu sagen!