Concepts¶
The schema is a tree of nodes¶
A form description is a tree. Every node has a kind that determines what it
becomes:
|
Describes |
Builds |
|---|---|---|
|
a single scalar field |
|
|
a list of scalars |
|
|
a nested object |
|
|
a list of objects |
|
|
a discriminated selection |
|
The root of every schema is a nodeGroup. Its children map contains the fields;
each child is itself any of these kinds, so groups nest to any depth. A
nodeGroup can also be marked presence (see the
schema reference) to make it an optional, toggleable group.
{
kind: 'nodeGroup', // → FormGroup
name: 'order',
children: {
reference: { kind: 'leaf', type: 'string', name: 'reference' }, // → FormControl<string>
tags: { kind: 'leafList', type: 'string', name: 'tags' }, // → FormArray<FormControl<string>>
address: { kind: 'nodeGroup', name: 'address', children: {...} },// → nested FormGroup
lines: { kind: 'nodeGroupList', name: 'lines', type: {...} }, // → FormArray<FormGroup>
},
}
From schema to a typed FormGroup¶
buildFormFromSchema(schema) walks the tree and returns a FormGroup whose
shape and value types are inferred from the schema literal. A leaf of
type: 'number' becomes a FormControl<number>; a nodeGroup becomes a nested
FormGroup with the same keys as its children.
const form = buildFormFromSchema(orderSchema);
form.controls.reference; // FormControl<string>
form.controls.tags; // FormArray<FormControl<string>>
form.controls.address; // FormGroup<{ ... }>
This inference is the library’s core feature, and it only survives if the schema’s literal type is preserved. Read Typed schemas for how to author schemas so it works — and the one annotation that breaks it.
Leaf value types¶
type on a leaf maps to a runtime type:
|
Control value type |
|---|---|
|
|
|
|
|
|
|
|
Identity: the children key¶
Each node carries a name, and each nodeGroup also keys its children in a
children map. The name must equal the map key. The builder registers each
control under its map key, while the renderers resolve controls by name — if the
two differ, the control silently fails to bind.
// correct — key and name agree
children: {
email: { kind: 'leaf', type: 'string', name: 'email' },
}
// wrong — control is registered as `email`, renderer looks up `mail` → no binding
children: {
email: { kind: 'leaf', type: 'string', name: 'mail' },
}
Using defineSchema does not catch this mismatch at compile time, so
keep name and its key identical by convention.
Data vs. presentation¶
A schema node carries two kinds of information:
Data — what shapes the value and its validation:
kind,type,required,enum,default,minItems,maxItems, and the tree structure itself.Presentation — what shapes the rendered UI:
label,enumLabel, and theappearanceoptions (flatten,noBorder) androotlayout flag.
The Schema reference tags every property with which concern it belongs to, so you can tell at a glance whether a field affects the submitted value or only the display.
Validation¶
The builder attaches validators from the schema:
required: trueon a leaf →Validators.required.enumon an enum leaf → a membership validator (values outside theenumarray are invalid).
Validity surfaces through the standard form.valid / control.errors API. See
Examples › Validation.