Dynamic Forms: Building a Form Generator with Signal Forms

  1. All About Angular’s New Signal Forms
  2. Dynamic Forms: Building a Form Generator with Signal Forms

Dynamic Forms built by a form generator have quite a history in Angular. Such form generators enable us to build a form at runtime using metadata, such as field names and validation rules. A typical use cases are situations where the user defines types or extensions to existing types.

With Reactive Forms, building such form generators was quite easy. In this article, I show how we can achieve the same with Angular's new Signal Forms (experimental).

📂 Source Code (🔀 branch dynamic-forms)

Consumer's Perspective

Before we look at the implementation of the forms generator, let's discuss the consumer's perspective: They just provide an array with FieldDef objects that provide metadata describing the form:

@Component([...])
export class FlightEditComponent {

  [...]

  meta: FieldDef[] = [
    { name: 'id', label: 'Id', required: true },
    {
        name: 'from',
        label: 'From',
        required: true,
        minLength: 3,
        maxLength: 20,
    },
    { name: 'to', label: 'To', required: true, minLength: 3, maxLength: 20 },
    { name: 'date', label: 'Date', required: true, type: 'datetime-local' },
    { name: 'delayed', label: 'Delayed', type: 'checkbox' },
 ];

  // Let's assume we don't know the structure of the entity
  // upfront but we have fitting meta data
  entity = [...] as WritableSignal<unknown>;
  dynamicForm = form(this.entity, toSchema(this.meta));

  [...]
}

A function toSchema converts this metadata into a schema used by Signal Forms. The form function takes the entity to display and the schema. The entity can be represented by a Signal<unknown> as we don't know about the shape of user-defined objects at compile-time.

To render the form, the template delegates to a component app-dynamic-form:

<form [...]>

  <app-dynamic-form [metaInfo]="meta" [dynamicForm]="dynamicForm" />

 [...]
</form>

This results in the following form at runtime:

Implementation

The implementation shown here is minimal to make it easier to convey the idea. Basically, we need a type FieldDef, a function toSchema, and a component for rendering the form. The type and the function are shown in the following listening:

import { maxLength, minLength, required, Schema, schema } from "@angular/forms/signals";

export interface FieldDef {
  name: string;
  label: string;
  required?: boolean;
  minLength?: number;
  maxLength?: number;
  type?: string;
}

export function toSchema(meta: FieldDef[]): Schema<unknown> {
  return schema<unknown>((path) => {
    for (const fieldDef of meta) {
      const prop = fieldDef.name;
      const fieldPath = (path as any)[prop];

      if (!fieldPath) {
        continue;
      }
      if (fieldDef.required) {
        required(fieldPath);
      }
      if (typeof fieldDef.minLength !== 'undefined') {
        minLength(fieldPath, fieldDef.minLength);
      }
      if (typeof fieldDef.maxLength !== 'undefined') {
        maxLength(fieldPath, fieldDef.maxLength);
      }
    }
  });
}

This function creates a schema<unknown> by iterating the meta data and describing the respective field by seting up validators. As the field names are not known at compile-time, we need to access their properties via index notation (brackets).

The app-dynamic-form component gets the metadata and the entity passed. It iterates the metadata and renders respective fields:

@for(fieldDef of metaInfo(); track fieldDef.name) { 

    @let field = $any(dynamicForm())[fieldDef.name];

<div class="form-group">
  <label>
    {{ fieldDef.label }}
    <input
      [type]="fieldDef.type"
      [control]="field"
      [class.form-control]="fieldDef.type !== 'checkbox'"
    />
  </label>
  <app-validation-errors
    [errors]="field().errors()"
  ></app-validation-errors>
</div>
}

More: Modern Angular Workshop

Subscribe to our Modern Angular Workshop to stay up to date!

English Version | German Version

Conclusion

This example demonstrates how Signal Forms can be used to build a simple dynamic form generator. By leveraging metadata such as field names and validation rules, a form can be created at runtime without knowing the shape of the underlying object at compile time.

The implementation is intentionally kept minimal to clearly illustrate the core idea:

  • Metadata is converted into a schema,
  • the schema is bound to a signal-based form,
  • and a generic component handles the rendering of the fields.

In real-world scenarios, this approach can easily be extended to support:

  • Arrays and form groups,
  • Additional validators,
  • or more complex UI components.

Signal Forms thus provide a flexible and powerful foundation for building dynamic forms.

eBook: Modern Angular

Stay up to date and learn to implement modern and lightweight solutions with Angular’s latest features: Standalone, Signals, Build-in Control Flow.

Free Download