In der Softwareentwicklung ist es entscheidend, Prinzipien zu befolgen, die den Code wartbar und erweiterbar machen. Die SOLID-Prinzipien, ein Akronym aus den fünf Prinzipien der objektorientierten Programmierung, bieten eine hervorragende Grundlage für die Gestaltung von robustem und skalierbarem Code. Ein Beispiel aus der Praxis ist die Umstrukturierung einer Wetter-API, um den Code gemäß diesen Prinzipien zu verbessern. Im folgenden Text wird ein konkretes Beispiel vorgestellt, das eine Refaktorisierung einer Wetterdienst-API beschreibt, um den Code sauberer und effizienter zu gestalten.
Zunächst wird das ursprüngliche Design einer Funktion beschrieben, die aktuelle Wetterdaten abruft. Ursprünglich wurde der Stadtname direkt als Parameter verwendet, jedoch stellte sich heraus, dass es flexibler war, den Parameter in „search“ umzubenennen, da er sowohl für Städtenamen als auch für Postleitzahlen genutzt werden konnte. Der Parameter konnte nun als Zeichenkette oder Zahl übergeben werden. Abhängig vom Datentyp wurde entweder der Parameter „q“ (für Stadtname) oder „zip“ (für Postleitzahl) verwendet. Auch der Ländercode wurde optional gestaltet, um flexibler auf die Eingabe des Nutzers reagieren zu können.
Die Funktion „getCurrentWeather“ wurde daraufhin in zwei separate Funktionen unterteilt. Zunächst wurde der HTTP-Aufruf zur Abfrage des Wetters in eine Hilfsfunktion ausgelagert, „getCurrentWeatherHelper“. Dies verbessert die Lesbarkeit des Codes und macht die Funktion einfacher zu testen. Die Trennung von Logik und HTTP-Aufruf folgt dem Prinzip der „Einzelverantwortlichkeit“ (Single Responsibility Principle) und bietet eine einfache Möglichkeit zur Erweiterung des Codes. Diese Art der Umstrukturierung erleichtert außerdem die Unit-Tests, da die HTTP-Anfrage von der Geschäftslogik getrennt ist und damit leicht isoliert getestet werden kann.
Ein weiteres Prinzip, das hier beachtet wird, ist das „Offen/geschlossen-Prinzip“ (Open/Closed Principle). Die Funktion „getCurrentWeatherHelper“ bleibt offen für Erweiterungen, da sie beliebige Parameter wie „uriParams“ akzeptieren kann, ohne dass der Code selbst geändert werden muss. Dies sorgt dafür, dass neue Anforderungen, wie etwa das Abrufen von Wetterdaten anhand von geografischen Koordinaten, einfach implementiert werden können, ohne die bestehende Logik anzupassen. Ein neues Beispiel zeigt, wie „getCurrentWeatherByCoords“ die Koordinaten des Nutzers akzeptiert und über dieselbe „getCurrentWeatherHelper“-Funktion das Wetter abruft, was eine elegante Lösung darstellt.
Durch die Implementierung dieser Prinzipien wird der Code nicht nur modularer und einfacher zu testen, sondern auch robuster gegenüber Fehlern und Wartungsaufwand. Ein weiterer Schritt in der Entwicklung des Wetterdienstes war die Erweiterung des „WeatherService“ Interfaces, um sowohl die Methode „getCurrentWeather“ als auch „getCurrentWeatherByCoords“ zu unterstützen.
Ein weiterer wichtiger Aspekt bei der Entwicklung von Webanwendungen ist die Benutzerinteraktion. In diesem Beispiel wird ein Eingabefeld für die Stadtsuche implementiert, das den Nutzer in die Lage versetzt, entweder eine Stadt oder eine Postleitzahl einzugeben. Das Eingabefeld ist so konzipiert, dass es sowohl eine Stadt als auch einen Ländercode akzeptiert. Durch die Verwendung von String-Methoden wird die Eingabe aufgeteilt und bereinigt, sodass auch Eingaben wie „Paris, US“ korrekt verarbeitet werden können.
Die Handhabung von Benutzeranfragen muss jedoch auch in Bezug auf Performance optimiert werden. Eine einfache Benutzerinteraktion wie die Eingabe von Text kann schnell zu einer Vielzahl an Serveranfragen führen, was nicht nur die Benutzererfahrung beeinträchtigt, sondern auch die Systemressourcen belastet. Um dies zu verhindern, wird die Eingabe der Benutzer durch die Techniken des „Debouncing“ oder „Throttling“ limitiert. Diese Techniken verhindern, dass mit jeder Tasteingabe eine Anfrage an den Server gesendet wird, sondern nur nach einer gewissen Verzögerung oder nach einer festgelegten Anzahl an Eingaben. Dies verbessert nicht nur die Benutzererfahrung, sondern reduziert auch die Serverlast und verhindert unnötige Netzwerkaufrufe.
Die Implementierung des „debounceTime“-Operators aus der RxJS-Bibliothek stellt sicher, dass die Eingabe des Nutzers nur dann verarbeitet wird, wenn der Nutzer aufgehört hat zu tippen, wodurch unnötige Serveranfragen vermieden werden. Dies ist besonders wichtig für Anwendungen, die auf Benutzerinteraktionen reagieren und dynamische Inhalte anzeigen, wie dies bei einer Wetter-App der Fall ist.
Es ist jedoch wichtig zu betonen, dass Debouncing und Throttling unterschiedliche Verhaltensweisen aufweisen. Während Debouncing dafür sorgt, dass immer nur der letzte Eingabewert verarbeitet wird, stellt Throttling sicher, dass nur eine Anfrage in einem festgelegten Intervall an den Server geschickt wird. Je nach den Anforderungen der Anwendung kann die eine oder die andere Technik besser geeignet sein.
Die Integration dieser Funktionalitäten in eine Anwendung führt zu einer besseren Benutzererfahrung und optimierten Performance. Sie hilft dabei, unnötige Belastungen für Server und Clients zu vermeiden und ermöglicht es, die Anwendung auch unter hoher Last effizient zu betreiben. Das Prinzip der „Verzögerung“ bei der Eingabeoptimierung sollte daher stets berücksichtigt werden, um eine benutzerfreundliche und leistungsfähige Anwendung zu entwickeln.
Wie man eine rollenbasierte Navigation implementiert und die Benutzererfahrung optimiert
Die Implementierung einer rollenbasierten Navigation ist entscheidend, um eine benutzerfreundliche und frustrationfreie Erfahrung zu gewährleisten. Durch gezielte Steuerung des Zugriffs auf bestimmte Elemente und Funktionen der Anwendung können Benutzer sicher und selbstbewusst durch die App navigieren. Eine der ersten und wichtigsten Aufgaben dabei ist, die Sichtbarkeit bestimmter Komponenten je nach Authentifizierungsstatus des Benutzers zu steuern.
Zunächst betrachten wir das Login-Formular und wie man es bei erfolgreicher Anmeldung automatisch ausblendet. Dazu muss der HomeComponent so angepasst werden, dass der Authentifizierungsstatus überprüft wird, bevor das Login angezeigt wird. Dies kann durch Injection des AuthService in den HomeComponent und die Verwendung des async Pipes erreicht werden, der den Authentifizierungsstatus in Echtzeit überwacht. Das Beispiel zeigt eine einfache Implementierung, bei der das Login nur angezeigt wird, wenn der Benutzer nicht eingeloggt ist:
Im Template des HomeComponent wird nun die Sichtbarkeit des Logins gesteuert, basierend auf dem Authentifizierungsstatus des Benutzers. Sobald der Benutzer eingeloggt ist, wird die Login-Komponente ausgeblendet und stattdessen die Hauptanwendung angezeigt:
Dieser Ansatz gewährleistet, dass nur authentifizierte Benutzer Zugang zu bestimmten Bereichen der Anwendung haben, während nicht authentifizierte Benutzer zum Login weitergeleitet werden. Eine zusätzliche Optimierung erfolgt durch die Verwendung des async Pipes, um zu vermeiden, dass Fehler wie ExpressionChangedAfterItHasBeenCheckedError auftreten. Dies sorgt für eine reaktive und effiziente Handhabung des Authentifizierungsstatus.
Ein weiteres zentrales Element einer rollenbasierten Navigation ist die dynamische Anpassung der App-Navigation basierend auf dem Benutzerstatus. Im AppComponent wird ebenfalls der AuthService eingebunden, um den Status des Benutzers zu überwachen und die Toolbar entsprechend anzupassen. Wenn der Benutzer nicht eingeloggt ist, wird die Toolbar ohne Buttons angezeigt, was zu einer klaren und aufgeräumten Benutzeroberfläche führt. Sobald der Benutzer eingeloggt ist, können personalisierte Elemente wie das Profilbild und die Logout-Schaltfläche angezeigt werden:
Neben der Anpassung der Benutzeroberfläche muss auch die Ladeperformance der App berücksichtigt werden. Der Einsatz der NgOptimizedImage-Direktive hilft dabei, Bilder effizient zu laden, um das First Contentful Paint (FCP) zu optimieren und Layout-Verschiebungen zu verhindern. Diese Best Practices für die Bildoptimierung sind entscheidend für eine schnelle und reaktive Benutzeroberfläche.
Die Handhabung von Formvalidierungen stellt eine weitere Herausforderung dar, die in der praktischen Entwicklung oft auftritt. Bei der Implementierung von Formularen, insbesondere bei Login- und Registrierungsformularen, ist es von Bedeutung, wiederkehrende Validierungslogiken in eine zentrale Datei auszulagern. Dies ermöglicht es, diese Validierungen wiederzuverwenden und zu testen. Ein Beispiel zeigt, wie man eine Datei validations.ts erstellt und darin die Validierungen für E-Mail und Passwort definiert:
Diese Methode der Validierung gewährleistet eine saubere und wartungsfreundliche Struktur im Code und trägt zur Wiederverwendbarkeit und Flexibilität der Applikation bei.
Die Kombination dieser Methoden – von der rollenbasierten Sichtbarkeit über die Authentifizierung bis hin zur Bildoptimierung und Validierung – führt zu einer insgesamt stabileren und benutzerfreundlicheren Anwendung. Sie sorgt dafür, dass sich die Benutzer nicht nur sicher, sondern auch effizient und schnell durch die Anwendung bewegen können. Ein funktionales, durchdachtes User Interface, das dynamisch auf den Authentifizierungsstatus reagiert, bildet das Rückgrat einer erfolgreichen Webanwendung.
Wie funktioniert rollenbasierte Navigation und Authentifizierung in Angular?
Die rollenbasierte Navigation stellt eine essentielle Komponente moderner Webanwendungen dar, um Nutzern eine personalisierte und sichere Benutzererfahrung zu ermöglichen. Im Kontext von Angular wird dieses Prinzip durch gezielten Einsatz von Routing, Lazy Loading und Authentifizierungs- sowie Autorisierungsmechanismen realisiert. Zentral ist die Gewährleistung, dass ein Nutzer nach der Anmeldung direkt zur für seine Rolle vorgesehenen Startseite weitergeleitet wird, ohne durch die Anwendung navigieren zu müssen, um die benötigten Funktionen zu finden.
Lazy Loading spielt hierbei eine wichtige Rolle: Es sorgt dafür, dass nur die minimal notwendigen Assets beim Anmeldevorgang geladen werden. Erst nach erfolgreicher Anmeldung wird der Nutzer in Abhängigkeit von seiner Rolle auf eine spezifische Seite geführt. Beispielsweise erhält ein Kassierer automatisch Zugriff auf die Kasse (Point of Sale, POS), während ein Lagerist oder Manager jeweils andere, für ihre Aufgaben relevante Bereiche sehen. Die Implementierung erfolgt im Login-Component, wo eine Funktion homeRoutePerRole definiert, welche anhand der Nutzerrolle die korrekte Route zurückgibt.
Um die Sicherheit der Anwendung zu erhöhen, sind sogenannte Route Guards unverzichtbar. Diese Guards kontrollieren sowohl den Zugang zu einzelnen Routen als auch das Laden von Modulen. In Angular existieren vier primäre Guard-Typen: canActivate, canActivateChild, canDeactivate und canLoad. Besonders relevant für die rollenbasierte Navigation sind canActivate und canLoad. Sie gewährleisten, dass nur authentifizierte Nutzer mit der passenden Rolle bestimmte Komponenten aufrufen oder Module laden dürfen.
Ein AuthGuard prüft dabei kontinuierlich den Authentifizierungsstatus sowie die Nutzerrolle. Sollte ein Nutzer nicht angemeldet sein oder nicht über die erforderlichen Berechtigungen verfügen, wird er auf die Login-Seite zurückgeleitet. Diese Prüfung findet sowohl beim Laden von Modulen (z.B. Manager-Modul) als auch beim Aktivieren einzelner Komponenten (z.B. Benutzerprofil) statt. Die Integration dieser Logik erfolgt häufig durch das Injizieren relevanter Dienste wie AuthService, Router und UI-Komponenten zur Anzeige von Fehlermeldungen.
Darüber hinaus ist es von großer Bedeutung, dass nicht nur auf der Client-Seite die Zugriffsrechte kontrolliert werden, sondern auch auf Serverseite eine strikte Zugriffskontrolle mittels Role-Based Access Control (RBAC) implementiert wird. Nur so kann ein umfassender Schutz gegen unautorisierte Zugriffe gewährleistet werden.
Die praktische Anwendung dieses Konzepts zeigt sich etwa darin, dass der Manager beim Login automatisch auf die Manager-Startseite geleitet wird, während ein Lagerist auf das Lagerverwaltungs-Interface gelangt. In der Routen-Konfiguration wird für geschützte Routen ein expectedRole-Attribut gesetzt, das im AuthGuard zur Überprüfung dient. Somit ist es ausgeschlossen, dass Nutzer auf Funktionen zugreifen, die nicht ihrer Rolle entsprechen.
Neben der Zugangskontrolle sorgen Guards auch für eine bessere Nutzererfahrung, indem sie unnötige Serveranfragen und das Laden unberechtigter Inhalte verhindern. Weiterführende Techniken wie Resolve ermöglichen zudem das Vorladen von Daten, bevor eine Komponente gerendert wird, was die Performance und Responsivität der Anwendung verbessert.
Das Zusammenspiel dieser Mechanismen schafft eine modulare, sichere und benutzerorientierte Anwendung. Nutzer werden zielgerichtet zu ihren Aufgaben geführt, ohne durch unübersichtliche Menüs navigieren zu müssen, und sensible Bereiche bleiben geschützt. Ein ganzheitliches Verständnis von Rollenmanagement, Routing und Authentifizierung ist somit unerlässlich für die Entwicklung anspruchsvoller Angular-Anwendungen.
Endtext
Wie man NgRx für asynchrone Datenoperationen in Angular verwendet
NgRx ist ein leistungsfähiges Tool, das in Angular-Anwendungen zur Verwaltung des Zustands und zur Handhabung von Nebenwirkungen verwendet wird. Es basiert auf der Redux-Architektur und bietet eine saubere und deklarative Möglichkeit, mit Zustandsänderungen umzugehen. Die Implementierung von NgRx kann jedoch komplex erscheinen, insbesondere wenn es darum geht, asynchrone Operationen zu verwalten und mit verschiedenen RxJS-Operatoren zu arbeiten.
Eine häufige Herausforderung besteht darin, wie man NgRx korrekt einrichtet und dabei asynchrone Effekte wie API-Aufrufe verwaltet, ohne die Anwendung unnötig zu verkomplizieren. Der folgende Abschnitt beschreibt, wie man NgRx zur Verwaltung von Wetterdaten implementiert und dabei einige häufig verwendete RxJS-Operatoren erklärt.
Zu Beginn müssen wir NgRx in das Projekt integrieren. Dies geschieht durch den Befehl:
Dies fügt das NgRx Store-Paket hinzu und erstellt einen Ordner „reducers“ mit einer index.ts-Datei. Um auch mit Effekten zu arbeiten, wird das NgRx Effects-Paket mit diesem Befehl hinzugefügt:
Der --minimal-Schalter stellt sicher, dass nur die nötigsten Dateien erzeugt werden, ohne unnötigen Boilerplate-Code. Zusätzlich installieren wir die NgRx-Schematics-Bibliothek, um Generatoren für das Erstellen von Boilerplate-Code zu verwenden:
Nun können wir die eigentliche Logik implementieren. In einem typischen Szenario wie der Wetterabfrage gibt es zwei wichtige Aktionen: eine zum Suchen von Wetterdaten und eine zum Laden dieser Daten.
Aktionen definieren
Zunächst definieren wir die Aktionen, die die Anwendung ausführen wird. Eine search-Aktion fordert Wetterdaten für eine angegebene Stadt oder Postleitzahl an, während die weatherLoaded-Aktion darauf hinweist, dass die aktuellen Wetterdaten erfolgreich abgerufen wurden.
Die search-Aktion wird folgendermaßen erstellt:
Der --group-Schalter sorgt dafür, dass die Aktionen in einem Ordner namens action organisiert werden, während --creators dafür sorgt, dass die Aktionen mithilfe von Creator-Funktionen implementiert werden, was den Code klarer und wartungsfreundlicher macht.
Die Implementierung der beiden Aktionen sieht dann so aus:
Effekte implementieren
Um auf die search-Aktion zu reagieren und die entsprechenden Wetterdaten zu laden, nutzen wir NgRx-Effekte. Effekte ermöglichen es, den Zustand zu ändern, ohne die ursprünglichen Ereignisdaten im Zustand zu speichern. Dies ist nützlich, wenn wir zum Beispiel nur die Wetterdaten im Zustand haben möchten und nicht den Suchtext.
Die Erstellung eines Effekts erfolgt folgendermaßen:
Der --root-Schalter registriert den Effekt in der app.module.ts-Datei. Im current-weather.effects.ts-File definieren wir nun eine Methode doSearch, die die Wetterdaten abruft:
Die Methode verwendet die getCurrentWeather-Funktion des Wetterdienstes und gibt bei Erfolg eine weatherLoaded-Aktion zurück, die die geladenen Daten enthält. Fehler werden hier mit der EMPTY-Funktion von RxJS abgefangen, sodass keine weiteren Fehlerbehandlungsmechanismen in diesem einfachen Beispiel erforderlich sind.
Der Effekt selbst wird dann wie folgt erstellt:
Der exhaustMap-Operator sorgt dafür, dass keine weiteren Anfragen ausgeführt werden, bevor die aktuelle Anfrage abgeschlossen ist. Dies ist nützlich, wenn mehrere Suchanfragen schnell hintereinander abgesendet werden, aber nur die erste ausgeführt werden soll.
RxJS-Operatoren im Detail
In der obigen Implementierung wurde der exhaustMap-Operator verwendet, aber in vielen Fällen könnte auch ein anderer RxJS-Operator wie mergeMap, concatMap oder switchMap sinnvoller sein. Diese Operatoren beeinflussen das Verhalten der API-Aufrufe und die Art und Weise, wie Ereignisse verarbeitet werden:
-
mergeMap: Dieser Operator verarbeitet mehrere Aktionen parallel. Er ist ideal, wenn jede Aktion unabhängig ist und nicht in einer bestimmten Reihenfolge ausgeführt werden muss.
-
concatMap: Dieser Operator sorgt dafür, dass Aktionen in der Reihenfolge verarbeitet werden, in der sie ausgelöst wurden. Er wartet, bis die vorherige Aktion abgeschlossen ist, bevor er die nächste startet.
-
switchMap: Dieser Operator verwirft die vorherige Anfrage, wenn eine neue eingegangen ist. Er ist nützlich, wenn nur die neueste Aktion von Interesse ist, wie beispielsweise bei einer Suchleiste, in der nur die letzte Eingabe relevant ist.
-
exhaustMap: Wie bereits erwähnt, ignoriert dieser Operator neue Anfragen, wenn bereits eine bearbeitet wird. Dies verhindert unnötige API-Aufrufe, etwa bei mehrfachen Klicks auf den gleichen Button.
Fazit
Die Implementierung von NgRx in Angular-Anwendungen bietet eine strukturierte und deklarative Möglichkeit, mit asynchronen Daten und Ereignissen umzugehen. Durch den Einsatz von RxJS-Operatoren wie exhaustMap, mergeMap, concatMap und switchMap können Entwickler das Verhalten der Anwendung an die spezifischen Anforderungen anpassen. Die Wahl des richtigen Operators kann dabei den Unterschied zwischen einer effizienten und einer ineffizienten Lösung ausmachen.
Wie die digitale Transformation die Unternehmenskultur im Fertigungssektor verändert und Herausforderungen überwindet
Wie man Hobbys günstig genießen kann
Wie beeinflussen persönliche Eigenschaften die Wahl der Behandlung?

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