top of page

Ein Liebesbrief an Angular

  • Autorenbild: Sam Scholefield
    Sam Scholefield
  • vor 22 Stunden
  • 7 Min. Lesezeit

Oder: Wie ich lernte, mir keine Sorgen mehr zu machen und das Signal zu lieben


Betrachten wir das Schiff. Angular ist natürlich kein Schiff. Aber es hat Unternehmensanwendungen fast ein Jahrzehnt lang mit bemerkenswerter Eleganz durch die Tiefen der Produktionsumgebung getragen. Umfangreich, eigenwillig, mitunter nervtötend und hartnäckig gegen das Sinken resistent. Es war nicht immer beliebt. Im Laufe seiner langen und ereignisreichen Geschichte wurde es immer wieder aktiv verachtet, manchmal sogar von genau den Menschen, die am meisten darauf angewiesen waren – was, wenn man genauer darüber nachdenkt, die menschlichste Reaktion überhaupt ist.


Ich war auch schon mal so. Ich saß da und überwachte einen komplexen Deployment-Prozess an einem Freitagnachmittag, starrte auf einen ChangeDetectorRef.detectChanges() Aufruf, der wie ein Stützkeil in eine Komponente gezwängt war, und dachte: So sollte es nicht sein. Ich habe Zone.js, dieses Prometheus-ähnliche Modul, beobachtet, wie es sich in jedes Browser-Event einklinkte, und dachte: Und trotzdem, genau so ist es nun mal.


Aber dies ist ein Liebesbrief. Und Liebesbriefe, zumindest die guten, werden nicht an das Perfekte und Makellose geschrieben. Sie werden an das geschrieben, was einen fast in den Wahnsinn getrieben hat und einem dann, an einem ganz gewöhnlichen Dienstagmorgen, etwas so Schönes gezeigt hat, dass der ganze Groll einfach... verflogen ist.


Dieser unscheinbare Dienstagmorgen war für mich ein Zeichen.

Abstraktes Bild: Links chaotische Zahnräder und Kabel in Rot und Blau; rechts ein geordnetes Netzwerk in Blau und Rosa. In der Mitte ein Brückenmotiv.

Die Alte Welt, kurz betrauert

Um zu verstehen, warum sich die signalbasierte Reaktivität von Angular weniger wie eine schrittweise Verbesserung anfühlt, sondern eher wie der Moment, in dem eine Zivilisation zum zweiten Mal das Feuer entdeckt (diesmal aber mit Sicherheitsvorkehrungen), muss man die Vorgeschichte kennen. Und die Vorgeschichte war, gelinde gesagt, sehr umfangreich.


Reactive Forms präsentierte sich uns mit der grimmigen Entschlossenheit eines erfahrenen Ingenieurs, der insgeheim vermutete, dass das Projekt nur notdürftig mit Hoffnung und Kabelbindern zusammengehalten wurde. Man definierte seine FormGroup. Darin definierte man die FormControl Instanzen, jede einzelne eine winzige, unabhängige Zustandsmaschine mit eigenem Lebenszyklus und stillem Unmut darüber, nicht korrekt typisiert zu sein. Anschließend synchronisierte man diese Steuerelemente manuell mit dem Datenmodell – mithilfe von Subscriptions, valueChanges Pipes, patchValue Befehlen und dem gelegentlichen rituellen Einsatz eines console.log, um herauszufinden, was schiefgelaufen war.


Die Typen waren – und ich sage das mit der Zuneigung, die man einem geliebten, aber unzuverlässigen Hund entgegenbringt – eine Lüge. Und ControlValueAccessor, diese furchteinflößende Schnittstelle für benutzerdefinierte Formularsteuerelemente, verlangte die Implementierung von vier Methoden und das Verständnis eines Registrierungsprozesses, der sich anfühlte, als sei er von einem Gremium entworfen worden, das gerade erst die Dependency Injection entdeckt hatte und überaus zufrieden mit sich selbst war.


Es hat funktioniert. Technisch funktioniert alles, bis zu dem Moment, in dem man darauf angewiesen ist, dass es einwandfrei funktioniert.


Das Signal erscheint

Angular führte in Version 16 Signale ein, und die anfängliche Reaktion der Community war eine Mischung aus vorsichtigem Optimismus und der üblichen Skepsis, die man schon oft gehört hatte: „Das ändert alles.“ Signale waren auf den ersten Blick einfach: ein reaktives Element. Eine Art Box, die einen Wert speichert und die Beteiligten benachrichtigt, wenn sich dieser Wert ändert. Mehr nicht.


Aber genau das haben die Leute auch über die allgemeine Relativitätstheorie gesagt, und schau, wohin das geführt hat.


Die Signale stellten in Wirklichkeit eine philosophische Neuausrichtung des gesamten Frameworks dar. Angular hatte Änderungen stets durch die Überprüfung aller Komponenten erkannt. Ein Klick auf einen Button, ein Timer-Tick, eine einzelne Eigenschaftsänderung in einem entfernten Service: Der gesamte Baum wurde überprüft. Immer der gesamte Baum. Im großen Maßstab war dies in etwa so, als würde man jedes Zimmer in einem Hotel durchsuchen, weil jemand im dritten Stock geniest hat.


Die Leistungseinbußen waren genau wie erwartet: Anwendungen mit Hunderten von Komponenten überprüften pflichtbewusst in jedem Zyklus jede einzelne Bindung erneut und verbrauchten dabei CPU-Zeit für Vergleiche, die fast immer zu dem Ergebnis führten, dass sich nichts geändert hatte. Man konnte dies zwar mit OnPush Strategien und einer sorgfältigen Architektur abmildern, doch das grundlegende Problem blieb bestehen: Das Framework hatte keine Möglichkeit festzustellen, was sich geändert hatte, und war daher, wie ein etwas paranoider Detektiv, gezwungen, alles zu verdächtigen.


Signale ersetzten dies durch fein abgestufte Reaktivität. Das Signal kennt seine abhängigen Elemente. Die abhängigen Elemente kennen das Signal. Ändert sich ein Wert, werden nur die Elemente benachrichtigt, die diesen Wert tatsächlich benötigen. Kein umständliches Durchsuchen von Knoten. Kein Zone.js. Kein Monkey-Patching von Browser-Primitiven. Nur eine saubere, direkte, fast schon absurd elegante Kette von Ursache und Wirkung.


Und dann machten sie weiter.


computed() lieferte uns abgeleiteten Zustand, der verzögert neu berechnet wird und seine Ergebnisse zwischenspeichert. Simpel und genau richtig. effect() ermöglichte es uns, die reaktive Welt mit der imperativen zu verbinden, mit dem entsprechenden Hinweis, dass es sparsam eingesetzt werden sollte, ähnlich wie man einem Kleinkind einen Permanentmarker gibt: technisch möglich, aber wahrscheinlich am besten unter Aufsicht. linkedSignal() löste das schwierige Dilemma zwischen schreibgeschützter Ableitung und beschreibbarem Zustand. Die Anzahl der Workarounds, die diese drei Primitiven aus Codebasen weltweit verbannt haben, ist vermutlich unermesslich.


Und dann, mit Version 19.2, kam httpResource.


httpResource oder: Der reaktive Graph stellt eine Internetverbindung her

Es gibt eine bestimmte Art von Boilerplate-Code, die Angular-Services immer wieder plagt. Sie kennen ihn sicher: das Dreigestirn aus Laden, Fehler und Erfolg. Sie führen einen HTTP-Aufruf durch. Sie benötigen eine Eigenschaft für die Daten, eine weitere für den Ladestatus und noch eine für den Fehler. Sie schreiben dieses Muster ein-, zweimal, hundertmal, jedes Mal etwas anders, jedes Mal mit der nagenden Gewissheit, dass es einen besseren Weg geben muss.


httpResource ist die bessere Lösung. Es handelt sich um einen reaktiven Wrapper für HttpClient, der Ihnen mit einer einzigen Deklaration eine Ressource bereitstellt, deren Wert, Status, Fehler und Ladezustand allesamt Signale sind. Sie verweisen auf eine URL oder, noch besser, auf eine Funktion, die eine URL aus anderen Signalen berechnet, und die Ressource wird abgerufen. Ändern sich diese Quellsignale, wird die Ressource erneut abgerufen. Ist bereits eine Anfrage im Gange, wird diese abgebrochen. All dies geschieht mit der stillen Kompetenz eines Mitarbeiters, der eine Aufgabe übernommen hat, die etwas unter seinen Fähigkeiten liegt und der sich daher leicht langweilt.


Zwei Codezeilen. Reaktives Abrufen von Daten. Automatischer Abbruch. Typsichere Antworten. Es macht genau das. Und es macht es perfekt. Es versucht nicht, HttpClient für Mutationen zu ersetzen, und das sagt es ganz klar in der Dokumentation – genau diese Art von ehrlicher Selbsteinschätzung stärkt das Vertrauen in eine API.


Signalformen oder: Die wiederaufgebaute Kathedrale

httpResource bewies, dass Angulars Versprechen der Reaktionsfähigkeit Realität war. Signal Forms, das experimentell mit Angular 21 ausgeliefert wurde, zeigte, dass es ambitionierte Ziele verfolgte.


Das Formular ist das Signal. Das Signal ist das Datenmodell. Es gibt keine FormGroup. Es gibt kein FormControl. Es gibt kein paralleles Zustandsuniversum, das Sie mithilfe von Abonnementmechanismen und guten Absichten mit Ihren eigentlichen Domänenobjekten synchronisieren müssen.


Stattdessen nimmt eine form() Funktion Ihr Signal entgegen und gibt eine Struktur zurück, in der jede Eigenschaft ihren eigenen Wert, Änderungsstatus, Änderungsstatus, Gültigkeit und Fehler enthält – alles als Signale, alles reaktiv, alles korrekt typisiert, da die Typen direkt aus Ihren Daten abgeleitet werden.


Die Validierung ist eine Schemafunktion, die einen typisierten Pfadbaum entgegennimmt. Sie deklarieren die Anforderungen für jedes Feld. Benutzerdefinierte Validatoren lassen sich nach demselben Muster einfügen. Feldübergreifende Validierung und bedingte Validierung funktionieren. Die Schnittstelle ControlValueAccessor, die vier Methoden umfasste und unzählige verwirrende Fragen auf Stack Overflow auslöste, wurde durch eine deutlich einfachere Lösung ersetzt.


Aber das Detail, das mich wirklich staunen ließ, das diese „nette Verbesserung“ zu „jemand hat das Problem von Grund auf neu durchdacht“ erhoben hat, ist die bidirektionale Datenbindung. Das Signal, das Sie an form() übergeben, verwaltet den Zustand. Wenn der Benutzer etwas in ein Eingabefeld eingibt, wird das Signal aktualisiert. Wenn Sie das Signal programmatisch aktualisieren, wird auch das Eingabefeld aktualisiert. Eine einzige, verlässliche Datenquelle – und genau das, was Sie ohnehin erstellen wollten. Das Formular verwaltet nicht Ihren Zustand, sondern spiegelt ihn wider.


Für alle, die bereits mit Reactive Forms arbeiten – und das betrifft praktisch jeden – gibt es compatForm, eine Migrationsbrücke, mit der sich Signal Forms schrittweise einführen lassen, ohne den gesamten Code neu schreiben zu müssen. Dieses Verhalten zeugt von einem Framework, das – vielleicht nach einigen schwierigen Diskussionen – gelernt hat, dass eine sanfte Migration der einzig praktikable Weg ist.


Zoneless, oder: Der entfesselte Affe

Jede Reise braucht ein Ende, und diese endet dort, wo sie logischerweise enden muss: mit dem Wegfall von Zone.js. Angular 21 hat die zonenlose Änderungsermittlung zum Standard gemacht. Zone.js ist nicht mehr notwendig. Die Signale selbst sind der Mechanismus zur Änderungsermittlung. Wenn ein Signal, das eine Vorlage liest, aktualisiert wird, wird diese Vorlage zum erneuten Rendern eingeplant. Sonst nichts. Kein Durchlaufen des Codebaums. Kein Monkey-Patching. Keine dreißig Kilobyte Laufzeit-Overhead, die nur dazu dienten, die Frage „Ist etwas passiert?“ zu beantworten.


Das Ergebnis sind kleinere Bundles, schnelleres Rendering, sauberere Stacktraces und die beruhigende Gewissheit, dass Ihr Framework window.Promise nicht länger heimlich hinter Ihrem Rücken verändert.


Der Liebesbrief, endlich

Ich sagte ja schon eingangs, dass Liebesbriefe Bedingungen haben. Angular hat sie erfüllt. Das hat mich wahnsinnig gemacht. Viele von uns waren wahnsinnig. Wütend über die Zeremonie, den Boilerplate-Code, die Dekoratoren, die eigentlich nur Metadaten waren, die Module, die im Grunde nur Organisationseinheiten mit Nebenwirkungen durch Dependency Injection waren.


Doch Angular tat etwas, das in dieser Branche leider selten ist: Es nahm die jahrelangen Beschwerden auf, manche höflich, viele weniger höflich formuliert, und hörte zu. Nicht oberflächlich, indem es einfach eine neue API auf alte Systeme aufsetzte, sondern tiefgreifend, indem es die grundlegenden Annahmen überdenken und von Grund auf neu entwickeln ließ. Signale sind keine Funktion. Sie sind ein neues Fundament.


Und jede Ebene des Frameworks spricht nun dieselbe reaktive Sprache. Das Datenmodell ist ein Signal. Der abgeleitete Zustand ist ein berechnetes Signal. Der HTTP-Fetch ist eine Ressource, die auf Signalen basiert. Das Formular spiegelt ein Signal wider. Die Änderungsermittlung wird durch Signale ausgelöst. Es ist – zum ersten Mal in der Geschichte von Angular – eine durchgängig kohärente reaktive Architektur, die sich von der Netzwerkschnittstelle bis hinunter zum DOM erstreckt.


Das allein hätte schon genügt.


Die meisten Frameworks häufen Funktionen wie Sedimente Schicht für Schicht an, jede für sich genommen sinnvoll, doch zusammen bilden sie eine komplexe Struktur, deren Ausgrabung Spezialwissen erfordert. Angular ging einen anderen Weg: Es legte den Grundstein frei, ohne die Millionen von Anwendungen zu beeinträchtigen, die bereits darauf aufgebaut waren. Signale sind keine neue Funktion, die auf bestehenden aufsetzt. Sie machen die alten Funktionen überflüssig.


Egal welches Framework man verwendet, dessen Ambitionen verdienen Respekt. Die schiere Geduld und Beharrlichkeit, die nötig sind, um ein ganzes Ökosystem zu migrieren, ohne jemanden zurückzulassen. Die Ehrlichkeit, Dinge als experimentell zu kennzeichnen, wenn sie es sind. Die Würde, zu sagen: „Diese API ist dafür nicht ausgelegt“, anstatt sie stillschweigend scheitern zu lassen.


Also: An Angular und an die Menschen, die es entwickeln. Dies ist mein Liebesbrief. Ihr habt mich verrückt gemacht. Ihr habt mir etwas Wunderschönes gezeigt.


Entgegen aller bisherigen Annahmen und Erwartungen bin ich wirklich gespannt darauf, was Sie als Nächstes tun werden.

bottom of page