@forge-form/angular

June 20, 2026 · View on GitHub

Schema-driven, signal-based reactive forms for Angular. Describe your form as a plain TypeScript object and let the engine build the FormGroup, render the fields, wire up validation, hints, error messages, and conditional visibility.

  • Schema-first — define controls, groups, validators, and layout declaratively.
  • Signal-based — built on Angular signals with OnPush change detection.
  • Reactive Forms under the hood — emits a strongly-typed value on submit.
  • Extensible — custom error and hint components, theming via SCSS.
  • Standalone — no NgModules required.

Requirements

Peer dependencyVersion
@angular/core^21.2.0
@angular/common^21.2.0
@angular/forms^21.2.0
rxjs^7.8.0

Installation

npm install @forge-form/angular

Quick start

Import FormRendererComponent, pass it a schema, and handle the typed formSubmit output.

import { Component } from '@angular/core';
import { FormRendererComponent, FormSchema, required, minLength } from '@forge-form/angular';

interface UserModel {
  firstName: string;
  age: number;
}

@Component({
  selector: 'app-user-form',
  imports: [FormRendererComponent],
  template: `
    <forge-form-angular [schema]="schema" (formSubmit)="onSubmit($event)" />
  `,
})
export class UserFormComponent {
  schema: FormSchema = {
    updateOn: 'blur',
    options: { orientation: 'column', theme: 'default' },
    controls: [
      {
        type: 'text',
        controlName: 'firstName',
        label: 'First name',
        placeholder: 'Enter your first name',
        validators: [required(), minLength({ value: 3 })],
      },
      {
        type: 'number',
        controlName: 'age',
        label: 'Age',
        validators: [required()],
      },
    ],
  };

  onSubmit(value: UserModel) {
    console.log('Submitted', value);
  }
}

Reading live value and validity

FormRendererComponent exposes value and valid signals, readable via a template reference variable without waiting for submit:

<forge-form-angular #userForm [schema]="schema" (formSubmit)="onSubmit($event)" />

<pre>{{ userForm.value() | json }}</pre>
<span>Valid: {{ userForm.valid() }}</span>

Styling

The package ships SCSS files. Import them once in your global stylesheet to get the default layout and theme:

// styles.scss
@use '@forge-form/angular/styles' as forge;
@use '@forge-form/angular/styles/default' as forge-theme;

To enable the default theme, set theme: 'default' in the schema's options. You can also skip the theme and style the forge-* CSS classes yourself.

Invalid, touched/dirty controls get an error class so you can style them even without the default theme: forge-form-input-error on text/number/select inputs, forge-form-checkbox-error on checkboxes.

Schema reference

FormSchema

PropertyTypeDescription
controls(GroupFieldSchema | ControlSchema)[]Top-level controls and groups.
idstringOptional form id.
updateOn'change' | 'blur' | 'submit'When control values/validation update.
optionsFormOptionsLayout, theme, and submit button.

FormOptions

PropertyTypeDescription
orientation'column' | 'row'Layout of top-level controls.
labelOrientation'column' | 'row'Default label placement, overridable per control.
theme'none' | 'default'Activates the bundled default theme.
hideSubmitButtonbooleanHides the built-in submit button, e.g. to provide your own outside the form.

Control types

All controls share controlName, label, options, initialValue, validators, visibility, updateOn, and hint.

typeExtra properties
textplaceholder
numberplaceholder, min, max
checkbox
selectitems: { label, value }[], placeholder

Groups

Nest controls with a group:

{
  type: 'group',
  options: { orientation: 'row' },
  controls: [
    { type: 'text', controlName: 'firstName', label: 'First' },
    { type: 'text', controlName: 'lastName', label: 'Last' },
  ],
}

Validators

Helper functions return a ValidatorSchema:

import { required, minLength, maxLength, min, max, customValidator } from '@forge-form/angular';

validators: [
  required(),
  minLength({ value: 3, errorMessage: 'Name is too short' }),
  customValidator({
    key: 'mustAccept',
    fn: (control) => (control.value === true ? null : { mustAccept: true }),
    errorMessage: 'You must accept the terms',
  }),
];

Each validator accepts an optional errorMessage that can be a string, a function of the validation error, or a custom component definition.

Conditional visibility

visibility: {
  fn: (ctx) => ctx.form.get('firstName')?.valid === true,
  behavior: 'hide', // or 'disable'
  clearOnHide: true,
}

Custom hint and error components

Custom hint components extend FormFieldContextComponent, which exposes control, controlValue, controlErrors, and controlSchema inputs:

import { Component, computed, input } from '@angular/core';
import { FormFieldContextComponent } from '@forge-form/angular';

@Component({
  selector: 'app-char-counter',
  template: `
    {{ currentLength() }} / {{ maxLength() }}
  `,
})
export class CharCounterComponent extends FormFieldContextComponent {
  maxLength = input<number>();
  currentLength = computed(() => (this.controlValue() as string | undefined)?.length ?? 0);
}

Reference it from a control's hint:

hint: {
  component: CharCounterComponent,
  inputs: { maxLength: 100 },
}

Public API

FormRendererComponent, FormFieldContextComponent, schema models (FormSchema, ControlSchema variants, FormOptions, VisibilitySchema, …), validator helpers, DI tokens (RENDERERS, FORM_OPTIONS, ERROR_MESSAGES, DEFAULT_ERROR_FALLBACK), and the error/hint models are exported from the package entry point.

Building from source

ng build forge-form-angular   # outputs to dist/forge-form-angular
ng test forge-form-angular    # run unit tests

License

MIT

Additional Resources

For more information on using the Angular CLI, including detailed command references, visit the Angular CLI Overview and Command Reference page.

Demo app - @forge-form/angular

This demo application was generated using Angular CLI version 21.2.1.

Development server

To start a local development server, run:

ng serve

Once the server is running, open your browser and navigate to http://localhost:4200/. The application will automatically reload whenever you modify any of the source files.

Code scaffolding

Angular CLI includes powerful code scaffolding tools. To generate a new component, run:

ng generate component component-name

For a complete list of available schematics (such as components, directives, or pipes), run:

ng generate --help

Building

To build the project run:

ng build

This will compile your project and store the build artifacts in the dist/ directory. By default, the production build optimizes your application for performance and speed.

Running unit tests

To execute unit tests with the Vitest test runner, use the following command:

ng test

Running end-to-end tests

For end-to-end (e2e) testing, run:

ng e2e

Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.

Additional Resources

For more information on using the Angular CLI, including detailed command references, visit the Angular CLI Overview and Command Reference page.