Examples¶
Every example is complete and copy-pasteable. They assume you’ve done the Installation setup (animations, theme, icon font).
A field of every kind¶
Shows all four kinds and all leaf types in one schema.
import { defineSchema } from 'ng-form-foundry';
export const kitchenSink = defineSchema({
kind: 'nodeGroup',
name: 'account',
root: true,
children: {
// leaf — scalars
username: { kind: 'leaf', type: 'string', name: 'username', label: 'Username', required: true },
seats: { kind: 'leaf', type: 'number', name: 'seats', label: 'Seats', default: 1 },
active: { kind: 'leaf', type: 'boolean', name: 'active', label: 'Active' },
plan: {
kind: 'leaf', type: 'enum', name: 'plan', label: 'Plan',
enum: ['free', 'pro', 'enterprise'],
enumLabel: ['Free', 'Pro', 'Enterprise'],
default: 'free',
},
// leafList — a list of scalars
aliases: { kind: 'leafList', type: 'string', name: 'aliases', label: 'Aliases' },
// nodeGroup — a nested object
billing: {
kind: 'nodeGroup', name: 'billing', label: 'Billing address',
children: {
line1: { kind: 'leaf', type: 'string', name: 'line1', label: 'Address' },
country: { kind: 'leaf', type: 'string', name: 'country', label: 'Country' },
},
},
// nodeGroupList — a list of objects
members: {
kind: 'nodeGroupList', name: 'members', label: 'Team members', minItems: 1,
type: {
kind: 'nodeGroup', name: 'member',
children: {
email: { kind: 'leaf', type: 'string', name: 'email', label: 'Email', required: true },
admin: { kind: 'leaf', type: 'boolean', name: 'admin', label: 'Admin' },
},
},
},
},
});
import { Component } from '@angular/core';
import { buildFormFromSchema, DynamicRecursiveFormComponent } from 'ng-form-foundry';
import { kitchenSink } from './kitchen-sink.schema';
@Component({
selector: 'app-account-form',
imports: [DynamicRecursiveFormComponent],
template: `<nff-dynamic-recursive-form [schema]="schema" [formGroup]="form" [editable]="true" />`,
})
export class AccountFormComponent {
readonly schema = kitchenSink;
readonly form = buildFormFromSchema(kitchenSink);
}
Seeding and reading values¶
Pass a value object (keyed by the schema’s children keys) as the second argument
to buildFormFromSchema. Read it back with getRawValue().
readonly form = buildFormFromSchema(kitchenSink, {
username: 'ada',
seats: 5,
active: true,
plan: 'pro',
aliases: ['ada.l', 'countess'],
billing: { line1: '1 Analytical Way', country: 'UK' },
members: [
{ email: 'ada@example.com', admin: true },
{ email: 'charles@example.com', admin: false },
],
});
// later
const value = this.form.getRawValue();
// value.members[1].email === 'charles@example.com'
Lists are sized to the data you pass — a members array of two produces two
groups, an aliases array of two produces two inputs.
Validation and required fields¶
required: true and enum produce validators. Validity surfaces through the
standard reactive-forms API.
export const signup = defineSchema({
kind: 'nodeGroup',
name: 'signup',
children: {
email: { kind: 'leaf', type: 'string', name: 'email', label: 'Email', required: true },
role: { kind: 'leaf', type: 'enum', name: 'role', label: 'Role', enum: ['user', 'admin'] },
},
});
const form = buildFormFromSchema(signup);
form.valid; // false — email is required and empty
form.controls.email.errors; // { required: true }
form.controls.email.setValue('ada@example.com');
form.controls.role.setValue('root'); // not in the enum
form.controls.role.errors; // { enum: true }
Deeply nested groups¶
Groups nest to any depth. Use appearance.flatten to render an inner group’s
fields inline instead of in their own card.
export const shipment = defineSchema({
kind: 'nodeGroup',
name: 'shipment',
root: true,
children: {
reference: { kind: 'leaf', type: 'string', name: 'reference', label: 'Reference' },
origin: {
kind: 'nodeGroup', name: 'origin', label: 'Origin',
children: {
city: { kind: 'leaf', type: 'string', name: 'city', label: 'City' },
geo: {
kind: 'nodeGroup', name: 'geo', label: 'Coordinates',
appearance: { flatten: true }, // render lat/lon inline, no nested card
children: {
lat: { kind: 'leaf', type: 'number', name: 'lat', label: 'Latitude' },
lon: { kind: 'leaf', type: 'number', name: 'lon', label: 'Longitude' },
},
},
},
},
},
});
A list of scalars¶
leafList renders an add/remove list of primitive inputs.
export const survey = defineSchema({
kind: 'nodeGroup',
name: 'survey',
children: {
question: { kind: 'leaf', type: 'string', name: 'question', label: 'Question' },
options: { kind: 'leafList', type: 'string', name: 'options', label: 'Options', default: ['Yes', 'No'] },
},
});
const form = buildFormFromSchema(survey);
form.controls.options; // FormArray<FormControl<string>>
form.controls.options.getRawValue(); // ['Yes', 'No']
A repeatable group¶
nodeGroupList renders each item as a group you can add and remove. minItems
keeps at least one item.
export const invoice = defineSchema({
kind: 'nodeGroup',
name: 'invoice',
root: true,
children: {
number: { kind: 'leaf', type: 'string', name: 'number', label: 'Invoice #' },
lines: {
kind: 'nodeGroupList', name: 'lines', label: 'Line items', minItems: 1,
type: {
kind: 'nodeGroup', name: 'line',
children: {
description: { kind: 'leaf', type: 'string', name: 'description', label: 'Description' },
quantity: { kind: 'leaf', type: 'number', name: 'quantity', label: 'Qty', default: 1 },
unitPrice: { kind: 'leaf', type: 'number', name: 'unitPrice', label: 'Unit price' },
},
},
},
},
});
const form = buildFormFromSchema(invoice, {
number: 'INV-001',
lines: [
{ description: 'Consulting', quantity: 10, unitPrice: 150 },
{ description: 'Travel', quantity: 1, unitPrice: 300 },
],
});
form.controls.lines.length; // 2
form.controls.lines.at(0).controls.quantity.value; // 10