Im Angular-Framework kann das Schließen eines Router-Outlets, insbesondere wenn es um komplexe UI-Strukturen geht, manchmal problematisch sein. Die native Methode, ein Outlet zu schließen, stößt gelegentlich an Grenzen, etwa wenn der Router das Schließen nicht reibungslos ausführt. Hier bietet sich das OutletCloserService an, das aus dem Common-Ordner stammt und jedes Outlet unabhängig vom Kontext zuverlässig schließen kann. Dieses Service basiert auf einer ursprünglichen Implementierung von Andrew Scott, deren Referenz sich auf Stackblitz befindet. Das Besondere am Service ist, dass er den komplexen Prozess des Outlet-Schließens abstrahiert und so Fehler und unvorhergesehene Zustände vermeidet.

Im Zusammenhang mit der Steuerung von Daten in einer Material Data Table spielt der Lebenszyklus-Hook ngAfterViewInit eine zentrale Rolle. Dort werden zunächst Subscriptions für Sortier- und Paginierungsereignisse angelegt, damit die Tabelle korrekt auf Änderungen reagiert. Anschließend wird der RxJS-Operator merge innerhalb eines setTimeout-Aufrufs verwendet, um auf sämtliche relevanten Veränderungen zu lauschen – dazu zählen neben Paginierung und Sortierung auch Filtereinstellungen. Jede Änderung löst die komplette Datenpipeline aus, um die aktuell anzuzeigenden Daten zu aktualisieren.

Die Verwendung von setTimeout ist dabei kein Zufall, sondern eine essentielle Maßnahme, um den Angular-spezifischen Fehler NG0100 ExpressionChangedAfterItHasBeenCheckedError zu vermeiden. Da der Zugriff auf Paginator und Sort-Referenzen aus dem Template erfolgt, und der Datenquellenwert (dataSource) bereits im Initialisierungsprozess von Angular gesetzt wird, würde eine unmittelbare Neuzuweisung innerhalb desselben Change Detection-Zyklus zu diesem Fehler führen. Durch das Verschieben der Operation in den nächsten Zyklus wird dieses Problem elegant umgangen – eine Praxis, die sich auch bei anderen Angular-Routinen wie der Authentifizierung bewährt hat.

Innerhalb der Datenpipeline erfolgt ein Aufruf an userService.getUsers, um die Nutzerinformationen anhand der aktuellen Paginierungs-, Sortier- und Filterkriterien zu beziehen. Die resultierenden Daten werden in das Observable items$ geleitet, welches dann über die asynchrone Pipe des Material Data Table automatisch abonniert wird. Wichtig ist, dass keine eigene Subscription auf items$ vorgenommen wird, da dies zu doppelten Serveranfragen führen würde. Um Speicherlecks durch zu lange bestehende Subscriptions zu vermeiden, sollte der Operator takeUntilDestroyed am Ende der Pipe verwendet werden, damit die Abonnements bei der Zerstörung der Komponente ordnungsgemäß beendet werden.

Das User Table Component integriert dazu eine Vielzahl von Angular- und Material-Modulen, die Funktionalität wie Formulare, Buttons, Icons, Paginatoren und Sortiermechanismen bereitstellen. Das begleitende CSS sorgt dafür, dass visuelle Rückmeldungen wie ein Lade-Overlay (loading-shade) beim Datenabruf elegant dargestellt werden, ohne die gesamte Oberfläche zu blockieren. Besonders hervorzuheben ist die Gestaltung der Tabellenzeilen mit Hover- und Selektions-Effekten, die eine intuitive Benutzerinteraktion ermöglichen und den Material-typischen Ripple-Effekt unterstützen.

Die Template-Implementierung bindet die Observable items$ an die Datenquelle der Tabelle und nutzt dynamische RouterLinks, um eine Detailansicht für einzelne Nutzer in einem separaten Outlet anzuzeigen. Die Nutzung von relativen URLs und das Attribut skipLocationChange ermöglichen es, die URL im Browser stabil zu halten, während die Detailansicht geladen wird. Dies ist insbesondere für modulare Anwendungen wichtig, da dadurch eine Wiederverwendbarkeit der Komponente in verschiedenen Kontexten (etwa unter /manager/users, /owner/users oder /ceo/users) gewährleistet wird, ohne die Komponente an eine spezifische URL zu binden.

Neben der Implementie

Wie man mit NgRx und SignalState in Angular eine saubere und effiziente Ladeanzeige implementiert

Im modernen Web-Entwicklungsprozess ist eine saubere und benutzerfreundliche Ladeanzeige von entscheidender Bedeutung. Besonders bei Datenabrufen aus APIs oder beim Laden von Inhalten können Nutzer von einer klaren visuelle Rückmeldung profitieren, die ihnen mitteilt, dass der Prozess noch läuft. In diesem Abschnitt werden wir untersuchen, wie man mit NgRx und SignalState in Angular eine einfache aber robuste Lösung für das Anzeigen eines Lade-Indikators umsetzt.

Zu Beginn wird ein privater SignalState definiert, der die Eigenschaften count und isLoading enthält. Diese Variablen dienen dazu, den Status des Lade-Indikators zu steuern. Es ist wichtig, dass der Zustand immer innerhalb der Grenzen des jeweiligen Kontextes kapselt wird, um unkontrollierte Nebeneffekte zu vermeiden. Eine der besten Methoden zur Verwaltung solcher Nebeneffekte ist der Einsatz von SignalStore, der als robustere Lösung für die Verwaltung von Zustandsänderungen gilt.

Der Lade-Indikator sollte jedoch öffentlich zugänglich sein, damit eine Benutzeroberfläche (UI) ihn an die Sichtbarkeit eines Lade-Spinners binden kann. Hierzu wird ein berechnetes Signal implementiert, das als Selektor fungiert, um den aktuellen Wert von isLoading in einem nur-lesbaren Format bereitzustellen. Die Funktion patchState stellt eine sicherere Methode dar, um Zustandsänderungen auf immutabler Basis vorzunehmen. Diese wird verwendet, um die Werte von count und isLoading jedes Mal zu aktualisieren, wenn eine der Ladeanzeigen-Funktionen (show oder hide) aufgerufen wird.

Im nächsten Schritt wird der LoadingHttpInterceptor implementiert. Dieser Interceptor sorgt dafür, dass die Methoden show und hide des UiService beim Aufruf von APIs zum Tragen kommen. Das Ziel ist es, den Lade-Indikator zu zeigen, wenn eine API-Anfrage gestartet wird und ihn wieder zu verstecken, sobald die Anfrage abgeschlossen ist. Der finalize-Operator sorgt dafür, dass hideLoader aufgerufen wird, nachdem die Anfrage abgeschlossen ist, unabhängig davon, ob sie erfolgreich war oder nicht.

Ein wesentliches Detail bei der Entwicklung eines Lade-Indikators ist die Integration eines UI-Komponenten, die den Status von isLoading nutzt, um den Spinner anzuzeigen oder zu verstecken. Hierfür wird eine LoadingOverlayComponent erstellt, die über eine computed Signal vom UiService verfügt. Mit Hilfe der Direktive @if wird der Spinner dynamisch je nach Wert von isLoading angezeigt oder versteckt. Ein besonderes Augenmerk sollte auf die Verwendung von ViewEncapsulation.ShadowDom gelegt werden, da diese Methode eine robustere Kapselung der CSS-Stile ermöglicht, was insbesondere für dynamische CSS-Features von Bedeutung ist.

Damit die Ladeanzeige tatsächlich auf dem Bildschirm sichtbar wird, muss der Spinner im HTML-Template des Haupt-Components (app.component.ts) eingefügt werden. Sobald dies erledigt ist, sollte der Lade-Indikator korrekt erscheinen, wenn API-Anfragen gemacht werden. In der Praxis wird ein auffälliger Lemon-Swirl-Spinner angezeigt, sobald eine Anfrage im Hintergrund ausgeführt wird.

Zusätzlich zu dieser grundlegenden Implementierung gibt es noch weitere Überlegungen. Zum Beispiel könnten Nutzer, die eine schnellere Netzwerkverbindung haben, die Ladeanzeige kaum wahrnehmen, da die API-Aufrufe zu schnell abgeschlossen werden. In solchen Fällen kann es hilfreich sein, eine künstliche Verzögerung einzuführen, um den Spinner besser sichtbar zu machen. In der Serveranwendung könnte dies durch das Einfügen eines setTimeout-Befehls für die Antworten von APIs erreicht werden.

Für eine noch optimierte Benutzererfahrung, besonders bei langsamen Netzwerkverbindungen, könnte auch eine prä-angezeigte Lade-Oberfläche von Nutzen sein. Diese wird mit einfachem HTML und CSS implementiert und sorgt dafür, dass die Nutzer bereits beim Starten der Anwendung eine visuelle Rückmeldung erhalten, bevor die tatsächlichen Inhalte geladen werden. Dabei wird eine minimale Lade-Oberfläche im index.html-Dokument eingebunden, die sofort beim Starten der Anwendung angezeigt wird. Diese einfache Lösung trägt dazu bei, die wahrgenommene Ladezeit zu reduzieren, insbesondere wenn die Anwendung auf langsamen Verbindungen läuft.

Ein weiterer Schritt in der Optimierung von Ladezeiten ist die Vermeidung unnötiger API-Aufrufe. Dies kann durch eine geschickte Verwaltung des Ladezustands und eine präzise Steuerung der API-Anfragen erreicht werden. Dabei kann der Einsatz von NgRx/SignalState oder auch NgRx/SignalStore von großem Vorteil sein, da er die Verwaltung von Nebenwirkungen und Zustandseffekten auf eine saubere und wartbare Weise ermöglicht. Gerade bei komplexeren Anwendungen, bei denen eine Vielzahl an API-Aufrufen und Zustandsänderungen notwendig ist, sollte auf die Implementierung von SignalStore geachtet werden, da es eine nahezu Observable- und RxJS-freie Lösung bietet.

Abschließend lässt sich sagen, dass die richtige Implementierung eines Lade-Indikators mit NgRx und SignalState nicht nur die Benutzererfahrung verbessert, sondern auch eine wichtige Rolle bei der Optimierung der Performance von Webanwendungen spielt. Durch die Verwendung von Shadow DOM, berechneten Signalen und interaktiven Interceptoren lässt sich eine saubere und effiziente Lösung erzielen, die dem Nutzer eine schnelle visuelle Rückmeldung über den Status der laufenden Prozesse gibt.