ng-form-foundry
v0.9.0
Published
Build fully-typed Angular Reactive Forms and Angular Material UI from a declarative, recursive form-description schema.
Maintainers
Readme
ng-form-foundry
Build fully-typed Angular Reactive Forms and Angular Material UI from a single
declarative, recursive form-description schema. You write the schema; the
library gives you a typed FormGroup and a rendered form.
const schema = defineSchema({
kind: 'nodeGroup',
name: 'profile',
children: {
firstName: { kind: 'leaf', type: 'string', name: 'firstName', required: true },
age: { kind: 'leaf', type: 'number', name: 'age' },
subscribe: { kind: 'leaf', type: 'boolean', name: 'subscribe' },
},
});
form = buildFormFromSchema(schema);
// form: FormGroup<{ firstName: FormControl<string>; age: FormControl<number>; subscribe: FormControl<boolean> }><nff-dynamic-recursive-form [schema]="schema" [formGroup]="form" [editable]="true" />Features
- One schema → typed form + UI.
NodeGroup/Leaf/LeafList/NodeGroupList/NodeChoice/NodeMapdescribe nested objects, primitive lists, repeatable groups, discriminated selections, and open dictionaries. - Type inference. The returned
FormGroup's keys and control value types are inferred from the schema literal — no manualFormGroup<...>typing. - Validation in the schema. Per-field constraints (
pattern,min/max,minLength,multipleOf,integer,required, …) become Angular validators and inlinemat-errormessages. - Angular Material renderers for string, number, boolean, and enum fields, add/remove lists, collapsible groups, optional presence fields with a toggle, choice/case selection, and add/remove/rename map entries.
- Declarative field layout. An
appearanceon any group lays its fields on a CSS grid (grid: { cols }), packs as many equal-width fields per row as fit (minFieldWidth), gathers checkboxes into a compact row (booleanFields), and bounds text/number widths in the default wrapping flow — the options cascade to nested groups, list items, map entries, and choice cases, with per-node override. - Two views: an all-in-one recursive form (
nff-dynamic-recursive-form), or a tree/detail config editor (nff-config-editor) — structure on the left, a node's fields on the right. - Standalone components, Angular 20, signal inputs, reactive forms throughout.
Installation
npm install ng-form-foundryPeer dependencies
ng-form-foundry renders with Angular Material, so your app must have:
| Package | Version |
| --- | --- |
| @angular/core, @angular/common, @angular/forms | ^20.1.0 |
| @angular/material, @angular/cdk | ^20.2.0 |
| rxjs | ^7.8.0 |
npm install @angular/material @angular/cdkApplication setup
Load a Material theme and the Material Icons font. Animations are optional in Angular Material 20. Nothing else: the library's component styles are self-contained (including the compact add/remove/edit icon buttons) — there are no global stylesheet rules or Sass mixins to import from this package.
// styles.scss
@use '@angular/material' as mat;
html { @include mat.theme((color: mat.$violet-palette, typography: Roboto, density: 0)); }<!-- index.html <head> -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">See the documentation for optional animation setup.
Reading the value
buildFormFromSchema returns a standard reactive FormGroup:
this.form.value; // current value (omits disabled controls)
this.form.getRawValue(); // full value, typed to the schema
this.form.valid; // validity from the schema's constraint validatorsIf the schema contains choice nodes, getRawValue() carries their __case
discriminators; serializeForm(schema, form) returns the value with them
stripped — the inline wire encoding, which buildFormFromSchema accepts back
as initial (the active case is re-inferred from which fields are present and
required).
Validity mirrors what would go on the wire: presence fields are absent until
enabled and required while enabled (unless nullable), a mandatory or
enabled-presence choice errors until a case is picked, an optional
(presence) list is absent until you add it — adding it drops in a first
entry, and removing the last entry takes it away again — while a required list
stays empty ([]) rather than seeding a placeholder entry. A valid form
serializes to a value that satisfies the schema's own constraints.
Documentation
Full guide, schema reference, and worked examples: https://ng-form-foundry.readthedocs.io
License
Apache-2.0 © Mathias Santos de Brito
