Angular Dynamic Signal Forms

Angular Dynamic Signal Forms

Auteur: Danny Holstein

Op 19 november 2025 is Angular 21 uitgekomen met als hoogtepunt de langverwachte signal forms waarmee op een nieuwe manier formulieren gemaakt kunnen worden. De signal forms zijn compleet nieuw en van de grond af opgebouwd met TypeScript en heeft zowel type safety als makkelijkere validatie. Na het lezen van een ander technische blog, waarin gesteld werd dat dynamische formulieren meer binnen handbereik zijn, ben ik hiermee gaan experimenteren. Hierbij wil dynamisch zeggen dat de velden van het formulier in eerste instantie onbekend zijn - de configuratie met alle data is afkomstig uit een JSON bestand of een WebApi in plaats dat dit in de code van de applicatie is terug te vinden. Dit nieuwe blogbericht zal gaan over de ontwikkeling van Dynamic Signal Forms.

Het idee achter Dynamic Forms

In een grote applicatie bevinden zich vaak meerdere formulieren op verschillende plaatsen. Dit kan tot problemen leiden wanneer de eisen hieraan regelmatig veranderen en zaken hard-gecodeerd zijn (dit wil zeggen dat er vaste code is in de applicatie). Het idee achter Dynamic Forms is om door middel van JSON (uit een bestand of een WebApi) de configuratie van formulieren te verkrijgen en in te stellen. Het voordeel is dat er in de theorie geen tot weinig Angular componenten aangepast hoeven te worden.

Voor dit experiment is er gekozen om een C#/.NET WebApi te gebruiken die al standaard responsen in JSON geeft. Dit bevat niet alleen de configuratie, maar ook alle informatie over de velden (het type, validatie, eventuele opties en extra data) van het formulier. Op basis hiervan wordt de UI (User Interface) dynamisch weergegeven in de Angular applicatie. Het voordeel is in de theorie: minder onderhoud, de structuur van het formulier bevindt zich buiten de codebase, de updates zijn direct door de JSON aan te passen en de view te verversen. Hetzelfde Angular component kan dus meerdere Dynamic Forms weergeven.

PostgreSQL database structuur en C#/.NET WebApi

De database structuur is in het schema hieronder te zien (en moet van rechts naar links worden gelezen):

postgresql database structure

De pijlen tussen de verschillende blokken geven one-to-many relaties aan. Bijvoorbeeld een formulier (SignalForm) kan meerdere velden hebben (SignalFormField). Op zijn beurt kan een veld in het formulier meerdere validaties (SignalFormValidation), meerdere opties (SignalFormOption) en extra data (SignalFormAdditionalData) hebben. De WebApi die begrijpt al deze relaties die expliciet zijn aangelegd dankzij het EntityFramework.

In de WebApi is ook Cascade Delete ingeschakeld en dit wil zeggen dat alle child-records van een overeenkomstige parent-record worden verwijderd. Bijvoorbeeld: wanneer er een formulier wordt verwijderd, dan worden niet alleen alle bijbehorende velden van dat formulier verwijderd, maar ook de validatie, opties en extra data die bij dat veld hoort. Het resultaat is een schone database waar geen losse data blijft hangen (of in technische termen: dit voorkomt orphaned records).

In zowel de WebApi als de frontend Angular applicatie wordt er gebruik gemaakt van Enums. Deze Enums definiëren feitelijk constanten met dezelfde waarde die overal gebruikt kunnen worden in de applicatie. Voorbeelden daarvan zijn in het bovenstaande schema te zien, zoals: FormType, InputType, PropertyType en anderen. Niet alleen hebben Enums dezelfde waarde binnen de applicaties, maar ook is door het gebruik hiervan meteen duidelijk in de code waar dit betrekking op heeft.

Angular 21 en de nieuwe experimentele Signal Forms

In Angular 21 zijn de nieuwe experimentele Signal Forms aan veranderingen onderhevig. Met Signal Forms is er één bron van waarheid, namelijk het FormModel (in de code hieronder de WritableSignal person). Met de form functie wordt een nieuw Signal Form gemaakt, waarbij het FormModel en de Validators van elkaar gescheiden zijn. In het HTML-template wordt er nog maar gebruik gemaakt van een enkele directive: [formField]. In Signal Forms worden veranderingen in zowel het formulier als het FormModel automatisch gesynchroniseerd. Zie de afbeelding hieronder voor een simpel voorbeeld:

Onder de motorkap wordt de FormModel op een recursieve manier omgezet tot een FieldTree. Het voordeel is dat dit dezelfde datastructuur zal hebben en TypeScript weet altijd welk datatype ieder veld heeft. De benamingen van de ingebouwde validators zijn hetzelfde gebleven (zoals required, min, max, pattern, etc.). Het verschil met Reactive Forms is dat er met signals wordt gewerkt (bijvoorbeeld voor de states:  touched, valid, etc.) en de validators zijn functies. Er zijn verder functies om velden in- en uit te schakelen (met disable) en ook om een vertraging in te bouwen (met debounce). Ook de state van het formulier wordt automatisch bijgehouden door middel van signals.

Met de komst van Signal Forms zijn zaken veel eenvoudiger gemaakt. Een ander voordeel is Type Safety waarbij TypeScript het exacte type weet van een bepaald veld. De Reactive Forms kunnen daarentegen soms complex zijn en hebben meer imports nodig (zoals: FormBuilder, FormGroup, FormArray en FormControl). Een ander groot voordeel is dat er makkelijker nieuwe invoervelden aan een bestaand formulier zijn toe te voegen. Hiervoor hoeft alleen het FormModel geüpdatet te worden.

Zaken zijn zelfs zo vereenvoudigd dat dynamische formulieren meer binnen handbereik komen. Dit laatste is met de bestaande Reactieve Forms zeer moeilijk door de vele functies en het verlies van het datatype. De documentatie van Signal Forms is echter ook nog in ontwikkeling en veel zaken zijn nog niet gedocumenteerd. Het maken van dynamische formulieren met Signal Forms bleek echter in de praktijk een uitdaging te zijn, want met een dynamisch formulier zijn de velden in eerste instantie onbekend.

Dynamic Signal Forms – technische uitdagingen

De eerste uitdaging was om het FormModel dynamisch te maken, waarbij de properties en het datatype onbekend zijn. Een aantal jaar geleden heb ik een FlexibleObject gemaakt waarmee op een dynamische manier een object gemaakt kan worden. In de afbeelding hieronder is te zien dat er gebruik gemaakt wordt van het datatype any. Het gebruik hiervan is onder normale omstandigheden af te raden, want hiermee wordt de controle van TypeScript uitgeschakeld en TypeScript kan als gevolg daarvan niet verder behulpzaam zijn. Alleen in uitzonderlijke situaties is het gebruik van any acceptabel. Om Type Safety te behouden wordt het exacte type terug ‘ge-cast’ (letterlijk ‘gegoten’ – een onbekend datatype wordt bijvoorbeeld omgezet in een nummer met de parseInt functie) op basis van de Enum PropertyType (afkomstig uit de Database via de WebApi).

In de afbeelding hierboven is ook te zien dat form eveneens het datatype any heeft en er ook nog eens gebruik gemaakt is van de null forgiving operator (het uitroepteken). Dit bleek in de praktijk noodzakelijk te zijn, want het component dat het dynamische formulier maakt, ontvangt ook al data via een ModelSignal en moet tegelijkertijd de validators instellen van het formulier. De validators van een Signal Form zijn gescheiden van het FormModel. Binnen de life cycle hook methode ngOnInit wordt het form geïnstantieerd inclusief de validators. Er wordt ook gebruik gemaakt van geavanceerde Angular methoden, zoals runInInjectionContext, dat het mogelijk maakt om tegelijkertijd zowel data te ontvangen (via de ModelSignal) als de validators in te stellen voor het formulier.

dynamic_signal_forms_advanced_angular_methods

Een voordeel van de Signal Forms is dat er door middel van de validate functie op een makkelijkere manier aangepaste validators gemaakt kunnen worden. Dit is in de voorbeeldcode hieronder gedaan om de datums van twee Datepickers te vergelijken, waarbij de datums niet hetzelfde mogen zijn. Overigens is hier gebruik gemaakt van de moment library die het makkelijker maakt om datums met elkaar te vergelijken. Wanneer de validatie slaagt, dan zal de validator geen fout en dus undefined teruggeven, maar wanneer de validatie mislukt dan wordt er een object met de properties kind en message (het foutbericht) teruggegeven.

In dynamische formulieren zijn van te voren het FormModel en de validators onbekend. In het HTML-template is dit ook het geval voor de directive [formField] die niet kan weten welke aftakking of property van de FieldTree gebruikt moet worden. Wel is de naam van de property bekend in de aangeleverde data. Om dynamische Signal Forms mogelijk te maken, zijn er in de praktijk helper methoden toegevoegd (zoals: getField(), getAttribute(), getOptions(), etc.). Met de getField() methode hieronder wordt het benodigde veld opnieuw terug ‘ge-cast’ in de juiste FieldTree.

dynamic_signal_forms_template_and_component_method

Vergelijkbare methoden zijn er in een apart component dat FormArrays maakt (flexibele opties met meerdere velden – feitelijk een ‘formulier in een formulier’). Veel van de bovenstaande code is hergebruikt om een FormArray met het FormModel en validators te maken. Een verschil is dat veranderingen in de FormArray terug naar boven worden verzonden naar het (parent)form. Het component dat een FormArray maakt, heeft ook extra methoden om velden toe te voegen, te verwijderen en te verplaatsen.

dynamic_signal_forms_creating_form_array

De Dynamic Signal Forms in actie

De formulieren die uit de database worden opgehaald via de WebApi kunnen in de applicatie dynamisch worden geladen. Om dynamische formulieren te maken worden niet-dynamische Signal Forms gebruikt in de applicatie. Dit laatste leidt weliswaar tot aanzienlijk meer (herhalende) code, maar biedt ook meer controle over de gewenste invoer velden. Bovendien hoeven niet alle validators beschikbaar te zijn voor alle invoervelden. Verder hebben normale tekstvelden geen opties nodig, maar dropdown-velden weer wel. Met deze zaken wordt rekening gehouden in de applicatie.

Om de applicatie uit te testen zijn er simpele en complexere formulieren gemaakt. Een relatief simpel formulier is bijvoorbeeld een enkel veld dat bedoeld is voor een Nederlandse postcode. Om deze postcode te valideren wordt er in het Dynamic Signal Form gebruik gemaakt van de pattern functie die een Reguliere Expressie accepteert (Reguliere Expressies bestaan al sinds 1953, het is een krachtige reeks karakters waarmee een zoekpatroon gevonden kan worden in tekst). Zie de afbeelding hieronder:

dynamic_signal_forms_in_action_dutch_postcode

Zoals in de vorige paragraaf beschreven, was het al een hele technische uitdaging om dynamic Signal Forms mogelijk te maken in een Angular component. Het was ook een puzzel om zowel FormArrays te maken en te tonen binnen een dynamisch formulier. Wanneer er een nieuw formulier wordt gemaakt, kan er ook voor deze optie gekozen worden in plaats van een regulier formulier. Of met andere woorden: om een ‘formulier in een formulier’ te maken, moet er eerst een FormArray worden gemaakt dat later in het reguliere formulier gebruikt kan worden. Om een lang verhaal kort te maken, heb ik besloten om van een FormArray een speciaal invoerveld/input te maken dat gebruikt kan worden in een regulier formulier. In de afbeelding hieronder is er rechtsonder een knop ‘Add Form Array’ om een FormArray toe te voegen aan het (reguliere) formulier.

dynamic_signal_forms_available_inputs

Vervolgens kan er gekozen worden welk FormArray (beschikbare opties worden opgehaald via de WebApi) er binnen het formulier getoond moet worden:

Een al complexer formulier kan bijvoorbeeld bedoeld zijn om producten in een webwinkel toe te voegen. De afbeelding hieronder toont de velden die worden gebruikt in het formulier. Voor de sizes/groottes wordt er een FormArray gebruikt, waarbij er waardes op een makkelijkere manier toegevoegd, verwijderd of verplaats kunnen worden. Ook is er een dropdown met verschillende opties om het producttype te selecteren. Zie de afbeelding hieronder:

Tot slot

Tot zover dit nieuwe blogbericht waarin de ontwikkeling van Dynamic Signal Forms is besproken. De backend van de applicatie is een C#/.NET WebApi, maar dit zou in theorie ook vervangen kunnen worden door een JSON bestand met de configuratie voor de formulieren. In theorie zou dit ook vervangen kunnen worden door een AI-tool die formulieren genereert. Het moet echter nog steeds benadrukt worden dat de Signal Forms op dit moment nog experimenteel zijn in Angular 21 en nog niet klaar voor productie. Het ligt wel in de lijn van de verwachtingen dat de Signal Forms binnen Angular verder zullen evolueren.

Het grootste voordeel is dat er geen extra herhalende code geschreven hoeft te worden voor ieder formulier. Andere voordelen van Dynamic Signal Forms zijn: meer flexibiliteit, beter onderhoudbare en schaalbare formulieren. Het is eveneens makkelijk om de formulieren aan te passen aan de gewenste gebruikerszaken.

Wel zijn Dynamic Signal Forms alleen waardevol in grote applicaties, waar de verschillende formulieren regelmatig aan verandering onderhevig zijn. Met Dynamic Signal Forms is het mogelijk om via een admin-dashboard formulieren aan te passen. Vooral wanneer er sprake is van data-driven user interfaces dan zijn Dynamic Signal Forms een perfecte match. Maar wanneer de formulieren zelden veranderen, dan is het handmatig aanpassen hiervan nog steeds simpeler.

Voor geïnteresseerden zijn er meer afbeeldingen en is de code te zien op GitHub:

Dynamic Signal Forms op GitHub