Das Arbeiten mit Observables, Subscriptions und BehaviorSubjects in Angular bietet eine leistungsstarke Möglichkeit, um asynchrone Daten zu verwalten und darauf zu reagieren. Jedoch birgt diese Technik auch das Risiko von Speicherlecks, wenn Subscriptions nicht ordnungsgemäß verwaltet werden. In dieser Sektion wird erläutert, wie Speicherlecks entstehen können, wie sie verhindert werden und wie man mit der Lebensdauer von Services und Komponenten in Angular umgeht, um eine effiziente Speicherverwaltung sicherzustellen.
Die korrekte Verwaltung von Subscriptions ist entscheidend für die Leistung einer Anwendung. Wenn eine Subscription aktiv bleibt, obwohl der zugehörige Komponentenlebenszyklus abgeschlossen ist, kann dies dazu führen, dass der zugehörige Speicher nicht freigegeben wird. Dies führt zu einer stetigen Erhöhung des RAM-Verbrauchs und kann dazu führen, dass der Browser unresponsiv wird oder, im schlimmsten Fall, Daten verloren gehen. Das Ergebnis ist eine schlechte Benutzererfahrung, die vermieden werden sollte.
Ein typisches Beispiel für eine Subscription ist die Beobachtung von currentWeather$, einem BehaviorSubject in einem Wetter-Service, wie im folgenden Code-Ausschnitt gezeigt:
Wenn wir in der Komponente CurrentWeatherComponent auf dieses BehaviorSubject abonnieren, um auf Änderungen der Wetterdaten zu reagieren, müssen wir darauf achten, dass die Subscription beim Zerstören der Komponente aufgehoben wird, um Speicherlecks zu vermeiden. Dies könnte so aussehen:
Der entscheidende Punkt hierbei ist die Verwendung von ngOnDestroy. Wenn eine Komponente in Angular zerstört wird, wird die Methode ngOnDestroy aufgerufen. Dies ist der richtige Moment, um alle aktiven Subscriptions zu verwalten und zu kündigen, bevor die Komponente aus dem Speicher entfernt wird.
Die korrekte Handhabung von Services in Angular ist ebenfalls ein wichtiger Aspekt. Angular verwendet standardmäßig Singleton-Services, die beim Start der Anwendung im Speicher gehalten werden, solange die App läuft. Wenn jedoch eine Komponente eine eigene Instanz des Services benötigt, kann dies zu Problemen führen. Um sicherzustellen, dass eine Service-Instanz nur innerhalb einer Komponente lebt, kann man den Service direkt in der Komponente bereitstellen:
In diesem Fall wird der Service zerstört, wenn die Komponente zerstört wird, jedoch schützt dies nicht automatisch vor Speicherlecks, da es weiterhin auf eine fehlerhafte Verwaltung der Subscriptions ankommt. Wenn man also eine Subscription nicht kündigt, wird die Instanz des Services im Speicher gehalten, auch wenn die Komponente nicht mehr benötigt wird.
Speicherlecks entstehen, wenn Objekte, die nicht mehr verwendet werden, immer noch Referenzen auf andere, verwendete Objekte enthalten. Der JavaScript-Garbage Collector (GC) ist dafür verantwortlich, nicht mehr benötigte Objekte zu entfernen, aber er kann keine Referenzen zwischen Objekten erkennen, die zu einer unerwünschten Bindung führen. Dies wird in der folgenden Analogie von Brendon Caulkins verdeutlicht: Man kann sich den Speicher des Browsers als Parkplatz vorstellen. Jedes Mal, wenn wir eine Subscription erstellen oder einen Wert zuweisen, "parken" wir ein Auto auf diesem Parkplatz. Wenn wir das Auto nicht entfernen, bleibt der Parkplatz besetzt und kann nicht wieder genutzt werden. Dies führt zu einem überfüllten Parkplatz – also einem überlasteten Speicher.
Um diesen Problemen vorzubeugen, ist es entscheidend, von Subscriptions wieder zu "entparken". Dies bedeutet, dass wir immer daran denken müssen, uns von allen Subscriptions zu "befreien", wenn eine Komponente zerstört wird. Im Falle der CurrentWeatherComponent könnte dies durch die Implementierung von ngOnDestroy erfolgen, in dem die unsubscribe-Methode aufgerufen wird:
Diese Methode garantiert, dass nach der Zerstörung der Komponente alle Ressourcen, die durch die Subscription belegt wurden, wieder freigegeben werden. Dennoch muss man sich bewusst sein, dass die Verwaltung von Subscriptions in größeren Anwendungen schnell komplex werden kann, insbesondere wenn mehrere Subscriptions in einer Komponente existieren.
In Angular gibt es auch eine Möglichkeit, Abonnements ohne manuelles Kündigen zu verwalten, indem man Operatoren wie first oder takeUntilDestroyed verwendet. Diese Operatoren bieten eine elegantere Lösung für die Verwaltung von Subscriptions und können helfen, das Risiko von Speicherlecks weiter zu minimieren.
Es ist auch wichtig zu beachten, dass das Abonnieren von FormControl-Ereignissen wie valueChanges in Angular nicht zwangsläufig zu einem Speicherleck führt. Da FormControl ein Kindobjekt der Komponente ist, wird es zusammen mit der Elternkomponente vom Garbage Collector entfernt, sobald diese nicht mehr benötigt wird. Trotzdem kann das direkte Abonnieren von Observable-Streams als ein Imperativmuster betrachtet werden, das man tunlichst vermeiden sollte, um den reaktiven Programmieransatz nicht zu verwässern.
Wenn Subscriptions nicht richtig verwaltet werden, kann dies zu schwerwiegenden Performance-Problemen führen. Daher ist es unerlässlich, in einer Angular-Anwendung auf eine saubere Verwaltung der Ressourcen und den Lebenszyklus der Komponenten und Services zu achten, um Speicherlecks zu verhindern und die App performant zu halten.
Wie können wir die Unterstützung für internationale Postleitzahlen in unserer App integrieren?
In der heutigen Welt, in der Webanwendungen zunehmend komplexe Anforderungen erfüllen müssen, kann eine vermeintlich einfache Funktion wie die Handhabung von Postleitzahlen in einem internationalen Kontext zu unerwarteten Schwierigkeiten führen. Ein solches Beispiel zeigt sich in der Notwendigkeit, Postleitzahlen aus verschiedenen Ländern zu verarbeiten. Während eine US-amerikanische Postleitzahl wie 22201 leicht von einem Stadtnamen zu unterscheiden ist, stellt die britische Postleitzahl EC2R 6AB eine viel größere Herausforderung dar. Hier muss der Entwickler auf die Unterstützung von Drittanbieterdiensten zurückgreifen, um eine umfassende Lösung zu bieten.
Im Beispiel einer Wetter-App, die ursprünglich nur US-amerikanische Postleitzahlen unterstützte, wurde eine Erweiterung vorgenommen, um internationale Postleitzahlen zu integrieren. Dies erforderte nicht nur Änderungen an der Benutzeroberfläche und den API-Aufrufen, sondern auch eine sorgfältige Überlegung, wie man mit der Validierung und Verarbeitung von Postleitzahlen auf globaler Ebene umgehen kann.
Ein entscheidender Schritt bestand darin, eine Verbindung zu einem Drittanbieter-Service wie Geonames.org herzustellen, der in der Lage ist, Postleitzahlen weltweit zu validieren. Über diesen Dienst können wir prüfen, ob der vom Benutzer eingegebene Wert eine gültige Postleitzahl oder vielmehr der Name einer Stadt ist. Dies wird durch einen API-Aufruf realisiert, der als Teil des Suchprozesses in die Logik der App integriert wird.
Ein wichtiger Punkt bei der Implementierung solcher Funktionen ist das Verständnis von Interfaces und deren Bedeutung im Designprozess. Das Erstellen eines klar definierten Interfaces für den Postleitzahl-Service ermöglicht es dem Entwicklerteam, sich auf die Interaktionsmodelle zu konzentrieren, ohne sich sofort in den Details der Implementierung zu verlieren. Diese Methode fördert nicht nur eine saubere Trennung der Verantwortlichkeiten, sondern ist auch von zentraler Bedeutung für die Anwendung von Test-Driven Development (TDD), da man so frühzeitig mit der Erstellung von Unit-Tests beginnen kann.
Der Code für den Postleitzahl-Service selbst nutzt die Angular-HTTP-Client-Bibliothek, um eine Anfrage an die Geonames-API zu senden. Der Rückgabewert dieser Anfrage wird dann überprüft, und der resultierende Datensatz wird aufbereitet, um sicherzustellen, dass nur gültige Postleitzahlen verarbeitet werden. Sollte keine gültige Antwort erhalten werden, wird der Wert auf null gesetzt, was eine einfache Handhabung von Fehlern ermöglicht.
Ein weiteres zentrales Konzept bei der Arbeit mit asynchronem Code ist das Verständnis von Observables und deren Sequenzierung. Der Einsatz von switchMap ermöglicht es, mehrere asynchrone API-Aufrufe miteinander zu verketten, sodass der Entwickler die Postleitzahl validieren kann, bevor er mit der Wetterabfrage fortfährt. Diese Art der Kettenbildung stellt sicher, dass die Anwendung korrekt auf Benutzeranfragen reagiert, selbst wenn diese fehlerhafte oder unvollständige Daten eingeben.
Durch diese Refaktorisierungen wird die App nicht nur flexibler, sondern auch robuster gegenüber den vielfältigen Anforderungen der internationalen Benutzung. Ein weiterer Vorteil ist, dass die Komplexität der Logik in kleinere, wiederverwendbare und testbare Komponenten aufgeteilt wird, was die Wartbarkeit der App langfristig verbessert.
Es ist jedoch entscheidend, dass Entwickler bei der Arbeit mit APIs und externen Diensten stets auch die Performance im Auge behalten. Der ständige Aufruf externer APIs kann zu Verlangsamungen führen, insbesondere wenn mehrere API-Anfragen hintereinander ausgeführt werden müssen. Daher sollten Entwickler bei der Arbeit mit solchen Kettenaufrufen auch Caching-Mechanismen und Fehlerbehandlung einplanen, um eine optimale Benutzererfahrung sicherzustellen.
Die Integration internationaler Postleitzahlen stellt nicht nur eine technische Herausforderung dar, sondern erfordert auch ein tiefes Verständnis der geografischen und kulturellen Unterschiede, die das Verhalten der Benutzer beeinflussen können. Ein solches Feature zeigt auf, wie wichtig es ist, den Kontext der Anwendung und die unterschiedlichen Bedürfnisse der Benutzer in verschiedenen Regionen zu berücksichtigen. Entwickler müssen sicherstellen, dass ihre Anwendungen nicht nur in einer Region gut funktionieren, sondern auch global skalierbar sind.
Ein weiterer Aspekt, der in Betracht gezogen werden sollte, ist die Validierung von Eingabedaten auf der Client-Seite. Während die Nutzung von API-Diensten zur Validierung eine gute Praxis ist, können auch einfache Prüfungen wie die Formatierung der Eingabewerte auf der Front-End-Seite hilfreich sein, um sicherzustellen, dass nur gültige Werte an die Server-API gesendet werden. Auf diese Weise kann die Last auf den Server reduziert und die Benutzererfahrung verbessert werden.
Wie die Bootstrap-Phasen einer Angular-Anwendung die Leistung beeinflussen und wie man diese optimiert
Die Leistung von modernen Webanwendungen ist oft ein kritisches Thema, insbesondere bei der Entwicklung von Single Page Applications (SPAs) wie denen, die mit Frameworks wie Angular, React oder Vue gebaut werden. Bei der Untersuchung von Leistungsproblemen ist es wichtig, die verschiedenen Phasen des Bootstraps einer Anwendung zu verstehen, da diese direkt die Ladezeiten und die allgemeine Nutzererfahrung beeinflussen können.
Während des Bootstrap-Prozesses einer Angular-Anwendung, oder beim Laden einer neuen Seite, werden mehrere Schritte ausgeführt, die in ihrer Gesamtheit für Verzögerungen und Performance-Probleme verantwortlich sein können. Dies umfasst den Download von JavaScript, die Ausführung von JavaScript, die Hydratisierung der Anwendungslogik und die Erkennung von Änderungen. Wenn die Anwendung oder die API-Codebasis keine größeren Probleme aufweist, sind diese Schritte die Hauptursache für Performance-Engpässe.
Der erste Schritt, der Download von JavaScript, umfasst die Codebasis der Anwendung, des Frameworks und der verwendeten Drittanbieter-Bibliotheken. Je größer der Umfang dieser Dateien, desto langsamer wird der Download-Prozess. Dies betrifft besonders mobile Geräte oder langsame Internetverbindungen, wo die verfügbare Bandbreite begrenzt ist.
Im zweiten Schritt muss der heruntergeladene JavaScript-Code in den Arbeitsspeicher geladen und durch den JavaScript-Interpreter des Browsers für die Just-in-Time-Ausführung verarbeitet werden. Diese Phase kann ebenfalls zu Verzögerungen führen, wenn der Code zu umfangreich oder schlecht optimiert ist. Hinzu kommt die Notwendigkeit, die DOM-Elemente zu erstellen und die Framework-Hooks zu initialisieren, was zusätzliche Zeit in Anspruch nehmen kann.
Die dritte Phase, die Hydratisierung, ist besonders für SPAs von Bedeutung. Hierbei muss das Framework den Zustand der Anwendung berechnen, sowohl visuell als auch in Bezug auf die Daten, die Ereignislistener an den DOM binden und das Rendering durchführen. Auch dieser Schritt ist rechenintensiv und kann die Leistung erheblich beeinträchtigen, besonders bei großen und komplexen Anwendungen.
Schließlich wird in der vierten Phase die Änderungsdetektion durchgeführt. Das Framework muss den Komponentenbaum durchlaufen, um zu prüfen, ob die Benutzeroberfläche aufgrund von Zustandsänderungen aktualisiert werden muss. Diese kontinuierliche Überprüfung kann zu erheblichen Verzögerungen führen, wenn sie nicht effizient implementiert ist.
Ein weiteres großes Problem stellt die Notwendigkeit dar, zu viele interaktive Komponenten gleichzeitig auf dem Bildschirm darzustellen. Hierbei kann es zu einer dramatischen Leistungseinbuße kommen, da jede dieser Komponenten ihren eigenen Lifecycle durchlaufen muss, was bei einer hohen Anzahl von Komponenten sehr teuer werden kann.
Es gibt jedoch auch minimalistische Ansätze zur Erstellung performanter Webanwendungen, wie die Verwendung von JavaScript-basierte Lösungen, die ohne große Frameworks auskommen. Mit modernen Sprachfunktionen von ECMAScript 2022, die von allen wichtigen Browsern unterstützt werden, lassen sich performante und reaktive Webanwendungen mit nur 1-2 kB "Framework"-Code erstellen. Ein solches Beispiel ist ArrowJS, eine Frontend-Bibliothek, die auf den grundlegenden Prinzipien von JavaScript basiert, um einfache, schnelle und reaktive Webseiten zu ermöglichen.
Das Konzept von ArrowJS basiert auf einfachen JavaScript-Primitiven wie WeakMap, Proxy, Set und Tagged Template Literals, die zusammen eine performante und reaktive Anwendungsstruktur ermöglichen. Der Code in ArrowJS erfordert keine Kompilierung oder zusätzliche Verarbeitung, was ihn äußerst leichtgewichtig und schnell macht. Dies steht im krassen Gegensatz zu den umfangreicheren Frameworks, die in traditionellen SPAs verwendet werden.
Ein weiteres Beispiel für eine moderne Lösung zur Performance-Optimierung ist Qwik.js. Entwickelt von Miško Hevery, dem "Vater" von Angular, und seinen Mitstreitern, wurde Qwik.js speziell entwickelt, um die grundlegenden Probleme von traditionellen SPAs zu adressieren. Die Besonderheit von Qwik.js liegt in der Verwendung von Resumability statt Hydration. Bei Qwik.js wird die Anwendung als vollständig gerenderter HTML-Payload mit eingebettetem Zustand ausgeliefert, was die Komplexität erheblich reduziert und zu deutlich schnelleren Ladezeiten führt. Im Gegensatz zu herkömmlichen SPAs, die nach dem ersten Laden aufwändig hydratisiert werden müssen, wird bei Qwik.js nur der notwendige JavaScript-Code nachgeladen, um Interaktivität zu ermöglichen.
Die Vorteile von Qwik.js sind nicht nur eine drastische Verbesserung der Ladegeschwindigkeit (unter 1 Sekunde für die erste vollständige Seitenladung), sondern auch eine signifikante Reduzierung der Gesamtgröße des Frameworks, das nur etwa 1 KB umfasst. Dies führt zu einer 5- bis 10-fachen Leistungssteigerung im Vergleich zu herkömmlichen SPA-Frameworks. Qwik.js geht noch einen Schritt weiter, indem es eine aggressive Lazy-Loading-Strategie verfolgt und nur die Code-Elemente nachlädt, mit denen der Benutzer tatsächlich interagiert.
Für Angular-Entwickler bietet Server-Side Rendering (SSR) eine Möglichkeit, die Initialisierung und Ladezeiten ihrer Anwendungen erheblich zu verkürzen. Bei SSR wird die SPA auf dem Server gerendert und als fertiger HTML- und JavaScript-Code an den Client geschickt. Diese Methode hat den Vorteil, dass der Browser nicht viel Zeit mit der Interpretation von JavaScript-Code verbringen muss, wodurch die Ladezeiten erheblich reduziert werden. Besonders für mobile Geräte oder Geräte mit eingeschränkter Rechenleistung und Bandbreite kann diese Technik von großem Nutzen sein.
Server-Side Rendering ermöglicht nicht nur eine schnellere Darstellung von Inhalten, sondern hat auch den Vorteil, dass Webcrawler die Seite leichter indexieren können, was für die Suchmaschinenoptimierung (SEO) von entscheidender Bedeutung ist. Dies ist ein großer Vorteil im Vergleich zu traditionellen SPAs, bei denen JavaScript-basierte Inhalte für Webcrawler möglicherweise schwer zugänglich sind.
Es ist auch wichtig zu verstehen, dass die Leistung einer Webanwendung nicht nur von der Geschwindigkeit des Initial-Ladens abhängt, sondern auch von der Fähigkeit, kontinuierlich flüssige und schnelle Interaktionen zu gewährleisten. Dies erfordert eine gut durchdachte Architektur, effiziente Datenstrukturen und ein geschicktes Management der Reaktivität, um sicherzustellen, dass die Anwendung auf verschiedenen Geräten und unter verschiedenen Bedingungen immer eine optimale Leistung bietet.
Wie man ein Angular-Projekt mit Lazy Loading und Feature-Modulen konfiguriert
In modernen Angular-Anwendungen ist es entscheidend, die Benutzererfahrung durch effizientes Laden von Modulen und Komponenten zu optimieren. Eine der leistungsfähigsten Techniken, um die Ladezeiten zu verbessern, ist das sogenannte Lazy Loading. Diese Methode ermöglicht es, nur die Module zu laden, die der Benutzer tatsächlich benötigt, anstatt die gesamte Anwendung auf einmal zu laden. In diesem Abschnitt betrachten wir, wie man Lazy Loading in Angular mit Feature-Modulen und Routen konfiguriert.
Stellen wir uns eine Anwendung vor, die mehrere Routen enthält, darunter a, b und c. Die Route a wird als eine reguläre, sofort geladene Route konfiguriert, während die Route b mit einer gestrichelten Linie verbunden ist, was darauf hinweist, dass sie Lazy Loaded ist. In der Praxis bedeutet dies, dass die Route b ein Modul, das sogenannte BModule, dynamisch lädt, das wiederum den childRouter enthält. Dieser kann beliebig viele Komponenten definieren, selbst solche, deren Routenbezeichner an anderer Stelle in der Anwendung bereits verwendet wurden. Zum Beispiel könnte die Route /b/a und /b/b innerhalb des childRouter definiert werden.
Ein wichtiger Aspekt bei der Konfiguration von Routen ist, dass die Routen im rootRouter nicht alle Unterrouten der childRouter beinhalten müssen. Dies wird am Beispiel der Routen-Definition für rootRouter deutlich:
In diesem Beispiel sehen wir, dass die Unterrouten wie /b/a oder /b/b nicht explizit im rootRouter definiert sind. Dies ist der Fall, weil sie durch den childRouter selbst verwaltet werden. Der childRouter sieht folgendermaßen aus:
Durch diese Trennung von Routen und deren Verwaltung können wir sicherstellen, dass nur die tatsächlich benötigten Module geladen werden. Beispielsweise wird das zweite Modul, das die Komponenten BA und BB enthält, erst dann heruntergeladen, wenn der Benutzer eine Route mit dem Präfix /b aufruft. Dies ist das Wesen des Lazy Loadings, das in modernen Webanwendungen unverzichtbar geworden ist.
Ein weiterer wichtiger Aspekt des Lazy Loadings ist das sogenannte "Chunking". Beim ersten Laden der Anwendung wird der Code in verschiedene Chunks (Teile) unterteilt. Der erste Chunk enthält alle sofort geladenen Komponenten und deren Abhängigkeiten, wie zum Beispiel die Komponenten AComponent, MasterComponent, DetailComponent und PageNotFoundComponent. Wenn der Benutzer jedoch zu einer Route navigiert, die ein Lazy Loaded Modul erfordert, wird der zugehörige Chunk nur dann heruntergeladen, wenn er benötigt wird. In unserem Beispiel wird der zweite Chunk mit den Komponenten BAComponent und BBComponent erst dann heruntergeladen, wenn der Benutzer zu einer Route wie /b/a navigiert.
Für eine noch granularere Steuerung kann das Chunking auch auf Komponentenebene erfolgen. In Kapitel 8, "Rezepte – Wiederverwendbarkeit, Formulare und Caching", wird gezeigt, wie man stand-alone Komponenten für Lazy Loading konfiguriert. Hierbei handelt es sich um isolierte Komponenten, die unabhängig vom restlichen Modul geladen werden können. Diese Technik wird besonders nützlich, wenn Komponenten in mehreren Modulen wiederverwendet werden müssen.
Die Konfiguration eines Feature-Moduls mit Lazy Loading in Angular erfordert nicht nur das Laden von Routen, sondern auch eine sorgfältige Strukturierung der Ordner und Module. Zum Beispiel könnte man ein ManagerModule erstellen, das eine Landing-Page für das Management-Panel enthält. Um das Lazy Loading für das ManagerModule zu implementieren, muss man in der app.routes.ts Datei den entsprechenden Pfad mit dem loadChildren-Attribut konfigurieren:
Dieser Trick ermöglicht es, das ManagerModule nur dann zu laden, wenn der Benutzer den Pfad manager aufruft. Die Route selbst wird durch die Konfiguration des manager-routing.module.ts Moduls mit einer einfachen Weiterleitung zum ManagerHomeComponent weiterverarbeitet:
Die Implementierung des Lazy Loadings erfordert auch, dass das Angular CLI beim Build-Prozess automatisch separate JavaScript-Chunks erstellt, die nur bei Bedarf heruntergeladen werden. Dies kann durch die Beobachtung der CLI-Ausgabe überprüft werden, die nach dem Starten des Servers den Abschnitt „Lazy Chunk Files“ anzeigt.
Wichtig zu beachten ist, dass Lazy Loading nur dann sinnvoll ist, wenn Module in sich abgeschlossen sind. Dies bedeutet, dass jedes Modul seine eigenen Routen und Abhängigkeiten verwaltet, ohne auf andere Teile der Anwendung zugreifen zu müssen. In einer Standalone-Konfiguration könnten Lazy Loaded Module sehr granular sein und nur die Komponenten enthalten, die in einem bestimmten Modul benötigt werden.
Ein weiterer wichtiger Punkt ist die Navigation und das Routing innerhalb der Anwendung. Die Verwendung von routerLink in Komponenten wie HomeComponent ermöglicht es, die verschiedenen Module und deren Routen miteinander zu verknüpfen, sodass Benutzer von einer Seite zur nächsten navigieren können, ohne dass sofort der gesamte Code geladen werden muss.
Endtext
Wie kann der Kompetenzansatz die Architektur im Zeitalter der KI transformieren?
Wie CCD-Datenkorrekturen die Bildqualität verbessern: Ein Überblick
Wie kann die Exklusion von Migranten gerechtfertigt werden?
Warum das atomare Zeitalter seine Hoffnungen enttäuschte: Von den Radium-Mädchen bis zur Ära der Atomkraftwerke

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский