Quickstart

This guide builds a small profile form end to end: define a schema, build a typed form, render it, and read the value back.

Make sure you’ve completed Installation first.

1. Define a schema

A schema is a plain object. The root is always a nodeGroup; its children map holds the fields.

Important

Author schemas with defineSchema(...) (or a satisfies NodeGroup annotation), not const schema: NodeGroup = .... The annotation widens the schema type and erases the field names and value types that buildFormFromSchema relies on. See Typed schemas.

// profile.schema.ts
import { defineSchema } from 'ng-form-foundry';

export const profileSchema = defineSchema({
  kind: 'nodeGroup',
  name: 'profile',
  children: {
    firstName: { kind: 'leaf', type: 'string', name: 'firstName', label: 'First name', required: true },
    lastName:  { kind: 'leaf', type: 'string', name: 'lastName', label: 'Last name' },
    age:       { kind: 'leaf', type: 'number', name: 'age', label: 'Age' },
    role:      { kind: 'leaf', type: 'enum', name: 'role', label: 'Role', enum: ['admin', 'editor', 'viewer'], default: 'viewer' },
    subscribe: { kind: 'leaf', type: 'boolean', name: 'subscribe', label: 'Subscribe to newsletter' },
  },
});

Note

Each child’s name must equal its key in the children map (firstName’s key is firstName). The builder keys controls by the map key, and the renderers look them up by name; if they differ, the control won’t bind. See Concepts › Identity.

2. Build a typed form and render it

// profile-form.component.ts
import { Component } from '@angular/core';
import { buildFormFromSchema, DynamicRecursiveFormComponent } from 'ng-form-foundry';
import { profileSchema } from './profile.schema';

@Component({
  selector: 'app-profile-form',
  imports: [DynamicRecursiveFormComponent],
  template: `
    <nff-dynamic-recursive-form
      [schema]="schema"
      [formGroup]="form"
      [editable]="true"
    />
    <button (click)="save()">Save</button>
  `,
})
export class ProfileFormComponent {
  readonly schema = profileSchema;
  readonly form = buildFormFromSchema(profileSchema);

  save() {
    console.log(this.form.getRawValue());
    // { firstName: '', lastName: null, age: null, role: 'viewer', subscribe: null }
  }
}

form is fully typed — form.controls.role is a FormControl<string | number>, form.controls.age a FormControl<number>, and so on.

3. Seed initial values

Pass a value object as the second argument to buildFormFromSchema. Keys match the schema’s children keys:

readonly form = buildFormFromSchema(profileSchema, {
  firstName: 'Ada',
  age: 36,
  role: 'admin',
  subscribe: true,
});

4. Read the value

The result is a standard reactive FormGroup:

this.form.value;         // current value; omits disabled controls
this.form.getRawValue(); // full value object, typed to the schema
this.form.valid;         // reflects required / enum / list-cardinality validators
this.form.controls.role.setValue('editor');

Next

  • Add nested groups, primitive lists, and repeatable groups — see Examples.

  • Understand each node type — see Concepts.