Ansätze zur Verwaltung von Modulen in Angular (und nicht nur)

Das Verständnis der Organisation der Entitäten, mit denen Sie arbeiten, kommt nicht sofort von einem Entwickler, der seine ersten Projekte in Angular schreibt.


Eines der Probleme, auf die Sie stoßen können, ist die ineffiziente Verwendung von Angular-Modulen, insbesondere des überlasteten App-Moduls: Sie haben eine neue Komponente erstellt, sie hineingeworfen, und der Dienst wurde auch dort ausgeführt. Und alles scheint großartig zu sein, alles funktioniert. Mit der Zeit wird es jedoch schwierig sein, ein solches Projekt zu warten und zu optimieren.


Glücklicherweise bietet Angular Entwicklern die Möglichkeit, eigene Module zu erstellen, und nennt sie auch Funktionsmodule.




Domain Feature Module


Das überladene App-Modul muss aufgeteilt werden. Daher müssen Sie zunächst große Teile in der Anwendung auswählen und in separaten Modulen platzieren.


Ein beliebter Ansatz besteht darin, die Anwendung in Domänenfeature-Module aufzuteilen. Sie dienen dazu, die Schnittstelle basierend auf einer Schlüsselaufgabe (Domäne) zu unterteilen, die jeder Teil davon ausführt. Beispiele für Domain-Feature-Module sind eine Profilbearbeitungsseite, eine Produktseite usw. Einfach ausgedrückt, alles, was unter dem Menüpunkt sein könnte.


Bild

Alle Anzeigen in den blauen Rahmen sowie der Inhalt anderer Menüelemente verdienen ihre eigenen Domain-Funktionsmodule.


Domänenfeature-Module können eine unbegrenzte Anzahl deklarierbarer Elemente (Komponenten, Direktiven, Pipes) verwenden, exportieren jedoch nur die Komponente, die die Benutzeroberfläche dieses Moduls darstellt. Domänenfeature-Module werden normalerweise in ein größeres Modul importiert.


Domain Feature-Module deklarieren normalerweise keine Dienste in sich. Wenn sie jedoch bekannt geben, sollte die Lebensdauer dieser Dienste auf die Lebensdauer des Moduls beschränkt sein. Dies kann durch verzögertes Laden oder Werbedienste in der externen Komponente des Moduls erreicht werden. Diese Methoden werden später in diesem Artikel erläutert.


Faules Laden


Durch Aufteilen der Anwendung in Domain Feature-Module können Sie das verzögerte Laden verwenden . So können Sie aus dem Originalpaket entfernen, was der Benutzer beim ersten Öffnen der Anwendung nicht benötigt: Benutzerprofil, Produktseite, Fotoseite usw. All dies kann bei Bedarf geladen werden.


Dienstleistungen und Injektoren


Die Anwendung ist in große Teile unterteilt - Module, und einige dieser Module werden bei Bedarf geladen. Frage: Wo sollen globale Dienste angekündigt werden? Und was ist, wenn wir den Leistungsumfang einschränken möchten?


Injektoren von träge geladenen Modulen


Im Gegensatz zu deklarierbaren Elementen, deren Existenz in jedem Modul deklariert werden muss, in dem sie verwendet werden, sind Singletones von Diensten, die einmal in einem der Module deklariert wurden, in der gesamten Anwendung verfügbar.


Es stellt sich heraus, dass Dienste in jedem Modul deklariert werden können und keine Sorge? Nicht wirklich so.


Das Vorstehende ist wahr, wenn die Anwendung nur einen globalen Injektor verwendet, aber oft ist alles etwas interessanter. Faul geladene Module haben einen eigenen Injektor (Komponenten auch, aber dazu später mehr). Warum erstellen träge geladene Module überhaupt einen eigenen Injektor? Der Grund liegt darin, wie die Abhängigkeitsinjektion in Angular funktioniert.


Der Injektor kann mit neuen Anbietern aufgefüllt werden, bis er in Betrieb genommen wird. Sobald der Injektor den ersten Dienst erstellt, wird er geschlossen , um neue Anbieter hinzuzufügen.


Wenn die Anwendung gestartet wird, richtet Angular zuerst den Root-Injektor ein und legt darin die Anbieter fest, die im App-Modul und in den darin importierten Modulen deklariert wurden. Dies gilt bereits vor dem Erstellen der ersten Komponenten und vor dem Bereitstellen von Abhängigkeiten.


In einer Situation, in der das Modul träge geladen ist, wurde der globale Injektor für eine lange Zeit konfiguriert und ist in Betrieb gegangen. Das geladene Modul hat keine andere Wahl, als einen eigenen Injektor zu erstellen. Dieser Injektor ist ein Kind des Injektors, der in dem Modul verwendet wird, das die Last initiiert hat. Dies führt zu einem Verhalten, das Javascript-Entwicklern in der Prototypenkette vertraut ist: Wenn der Dienst nicht im Injektor eines träge geladenen Moduls gefunden wurde, sucht das DI-Framework im übergeordneten Injektor usw. danach.


Mit träge geladenen Modulen können Sie also Dienste deklarieren, die nur im Rahmen dieses Moduls verfügbar sind. Anbieter können wie bei Javascript-Prototypen auch hier neu definiert werden.


Zurück zu den Domain-Feature-Modulen: Das beschriebene Verhalten ist eine Möglichkeit, die Lebensdauer der darin beworbenen Anbieter zu begrenzen.


Kernmodul


Wo sollten globale Dienste wie Autorisierungsdienste, API-Dienste, Benutzerdienste usw. angekündigt werden? Die einfache Antwort finden Sie im App-Modul. Um jedoch die Reihenfolge im App-Modul wiederherzustellen (dies tun wir), sollten Sie globale Services in einem separaten Modul, dem Core-Modul, deklarieren und NUR in das App-Modul importieren. Das Ergebnis ist das gleiche, als ob die Dienste direkt im App-Modul deklariert worden wären.


Ab Version 6 gab es im Winkel die Möglichkeit, globale Dienste zu deklarieren, ohne sie irgendwo zu importieren. Alles, was Sie tun müssen, ist, die Option "In" zu Injectable hinzuzufügen und den Wert "root" darin anzugeben. Auf diese Weise deklarierte Dienste werden für die gesamte Anwendung verfügbar, sodass sie nicht im Modul deklariert werden müssen.


Neben der Tatsache, dass dieser Ansatz in die glänzende Zukunft des Winkels ohne Module blickt, hilft er auch, unnötigen Code abzuschneiden.


Singleton-Test


Aber was ist, wenn jemand im Projekt das Core-Modul woanders importieren möchte? Ist es möglich, sich davor zu schützen? Du kannst.


Fügen Sie dem Core-Modul einen Konstruktor hinzu, der Sie auffordert, das Core-Modul in das Modul einzufügen (das ist richtig, Sie selbst), und markieren Sie diese Deklaration mit den Dekoratoren Optional und SkipSelf. Wenn der Injektor eine Abhängigkeit in eine Variable einfügt, versucht jemand, das Core-Modul erneut zu deklarieren.


Verwenden des Ansatzes in BrowserModule

Verwenden Sie den in BrowserModule beschriebenen Ansatz.


Dieser Ansatz kann sowohl für Module als auch für Dienste verwendet werden.


Serviceankündigung in der Komponente


Wir haben bereits über eine Möglichkeit nachgedacht, den Umfang der Anbieter durch verzögertes Laden einzuschränken, aber hier ist eine andere.


Jede Komponenteninstanz verfügt über einen eigenen Injektor. Um ihn zu konfigurieren, verfügt der Komponentendekorator genau wie der NgModule-Dekorator über die Eigenschaft des Anbieters. Und auch - eine zusätzliche Eigenschaft von viewProviders. Beide dienen zur Konfiguration der Injektorkomponenten. Die in jeder der Methoden deklarierten Anbieter haben jedoch unterschiedliche Bereiche.


Um den Unterschied zu verstehen, benötigen Sie einen kurzen Hintergrund.


Eine Komponente besteht aus Ansicht und Inhalt.


Ich verdrehe Komponenten

Komponenten anzeigen


Ich Inhalt Komponenten

Inhaltskomponenten


Alles, was in der HTML-Datei der Komponente enthalten ist, ist ihre Ansicht, während zwischen den öffnenden und schließenden Komponenten-Tags der Inhalt übergeben wird.


Das erhaltene Ergebnis:


Das Ergebnis

Das Ergebnis


Die zu Anbietern hinzugefügten Anbieter sind also sowohl in der Ansicht der Komponente verfügbar, in der sie deklariert sind, als auch für den Inhalt, der an die Komponente übergeben wird. Während viewProviders, wie der Name schon sagt, Dienste nur zum Anzeigen sichtbar macht und sie für Inhalte schließt.


Trotz der Tatsache, dass es empfehlenswert ist, Dienste im Root-Injektor zu deklarieren, gibt es Szenarien, in denen die Injektor-Komponente verwendet wird:


Die erste ist, wenn jede neue Komponenteninstanz eine eigene Dienstinstanz haben muss. Zum Beispiel ein Dienst, der Daten speichert, die für jede neue Instanz einer Komponente spezifisch sind.


Für ein anderes Szenario müssen wir uns daran erinnern, dass, obwohl die Domain-Funktionsmodule einige Anbieter deklarieren können, die sie nur benötigen, es wünschenswert ist, dass diese Anbieter mit diesen Modulen sterben. In diesem Fall deklarieren wir den Anbieter in der externesten Komponente, die aus dem Modul exportiert wird.


Zum Beispiel das Domain-Feature-Modul, das für das Benutzerprofil verantwortlich ist. Wir werden den für diesen Teil der Anwendung erforderlichen Dienst nur bei Anbietern der externesten Komponente, UserProfileComponent, deklarieren. Jetzt erhalten alle deklarierbaren Elemente, die im Markup dieser Komponente deklariert und im Inhalt an sie übergeben werden, dieselbe Dienstinstanz.


Wiederverwendbare Komponenten


Was tun mit den Komponenten, die wir wiederverwenden möchten? Es gibt auch keine eindeutige Antwort auf diese Frage, aber es gibt bewährte Ansätze.


Gemeinsames Modul


Alle im Projekt verwendeten Komponenten können in einem Modul gespeichert, exportiert und in die Module des Projekts importiert werden, in denen diese Komponenten möglicherweise benötigt werden.


In einem solchen Modul können Sie die Komponenten einer Schaltfläche, eine Dropdown-Liste, einen stilisierten Textblock usw. sowie benutzerdefinierte Anweisungen und Pipes einfügen.


Ein solches Modul wird normalerweise SharedModule genannt.


Es ist wichtig zu beachten, dass SharedModule keine Dienste deklarieren sollte. Oder deklarieren Sie den forRoot-Ansatz. Wir werden etwas später über ihn sprechen.


Obwohl der SharedModules-Ansatz funktioniert, gibt es einige Punkte:


  1. Wir haben die Anwendungsstruktur nicht sauberer gemacht, sondern einfach das Chaos von einem Ort zum anderen verschoben.
  2. Dieser Ansatz befasst sich nicht mit der glänzenden Zukunft von Angular, in der es keine Module geben wird.

Ein Ansatz, der diese Mängel nicht aufweist, besteht darin, für jede Komponente ein Modul zu erstellen.


Modul pro Komponente oder SCAM (Einzelkomponenten-Winkelmodul)


Wenn Sie eine neue Komponente erstellen, sollten Sie sie in Ihr eigenes Modul einfügen. Sie müssen Komponentenabhängigkeiten in dasselbe Modul importieren.



Bild

Jedes Mal, wenn eine bestimmte Komponente an einer beliebigen Stelle in der Anwendung benötigt wird, muss lediglich das Modul dieser Komponente importiert werden.


Im Englischen wird dieser Ansatz als Modul pro Komponente oder SCAM - Einzelkomponenten-Winkelmodul bezeichnet. Obwohl der Name die Wortkomponente enthält, gilt dieser Ansatz auch für Pipes und Direktiven (SPAM, SDAM).


Der wahrscheinlich bedeutendste Vorteil dieses Ansatzes ist die Erleichterung des Komponententests. Da das für die Komponente erstellte Modul es exportiert und auch bereits alle erforderlichen Abhängigkeiten enthält, um TestBed zu konfigurieren, setzen Sie dieses Modul einfach in den Import ein.


Dieser Ansatz trägt zur Reihenfolge und Struktur im Projektcode bei und bereitet uns auch auf die Zukunft ohne Module vor. Wenn Sie eine Komponente im Layout einer anderen verwenden, müssen Sie nur Abhängigkeiten in der Komponentenanweisung deklarieren. In diesem Artikel können Sie ein wenig in die Zukunft schauen.


ModuleWithProviders-Schnittstelle


Wenn im Projekt ein Modul mit einer Deklaration von XYZ-Diensten gestartet wird und dieses Modul im Laufe der Zeit überall verwendet wird, versucht jeder Import dieses Moduls, dem entsprechenden Injektor XYZ-Dienste hinzuzufügen, was unweigerlich zu Kollisionen führt. Angular hat eine Reihe von Regeln für diesen Fall, die möglicherweise nicht den Erwartungen des Entwicklers entsprechen. Dies gilt insbesondere für das träge geladene Injektormodul.


Um Kollisionsprobleme zu vermeiden, bietet Angular die Schnittstelle ModuleWithProviders , mit der Sie Anbieter an das Modul anhängen können, während die Anbieter des Moduls selbst unberührt bleiben. Und genau das wird im oben beschriebenen Fall benötigt.


Strategien für Root (), forChild ()


Damit die Dienste im globalen Injektor genau festgelegt werden, wird das Modul mit den Anbietern nur in das AppModule importiert. Auf der Seite des importierten Moduls müssen Sie nur eine statische Methode erstellen, die ModuleWithProviders zurückgibt, die in der Vergangenheit den Namen forRoot erhalten hat.


Bild


Methoden, die ModuleWithProviders zurückgeben, können eine beliebige Zahl sein und nach Ihren Wünschen benannt werden. forRoot ist eine bequemere Konvention als eine Anforderung.


Beispielsweise verfügt RouterModule über eine statische forChild- Methode, mit der das Routing in träge geladenen Modulen konfiguriert wird.


Fazit:


  1. Trennen Sie die Benutzeroberfläche nach Schlüsselaufgaben und erstellen Sie für jedes ausgewählte Teil ein eigenes Modul: Zusätzlich zum besseren Verständnis der Struktur des Projektcodes können Sie Teile der Benutzeroberfläche träge laden
  2. Verwenden Sie Injektoren von träge geladenen Modulen und Komponenten, wenn die Anwendungsarchitektur dies erfordert
  3. Veröffentlichen Sie globale Dienstankündigungen in einem separaten Modul, dem Kernmodul, und importieren Sie sie nur in das App-Modul. Dies hilft beim Reinigen des App-Moduls.
  4. Verwenden Sie besser die Option requiredIn mit dem Stammwert des Injectable Decorators.
  5. Verwenden Sie Hack mit optionalen und SkipSelf-Dekoratoren, um den erneuten Import von Modulen und Diensten zu verhindern
  6. Speichern Sie wiederverwendbare Komponenten, Anweisungen und Rohre im freigegebenen Modul
  7. Der beste Ansatz, der auch in die Zukunft blickt und das Testen erleichtert, besteht darin, für jede Komponente ein Modul zu erstellen (auch Direktiven und Pipes).
  8. Verwenden Sie die ModuleWithProviders-Oberfläche, wenn Sie Anbieterkonflikte vermeiden möchten. Ein beliebter Ansatz ist die Implementierung der forRoot-Methode, um dem Root-Modul Anbieter hinzuzufügen

Source: https://habr.com/ru/post/de467525/


All Articles