Skip to main content

4. Add nesting and reactive state

Group related fields with shorthand objects. Use an explicit group() when that branch needs its own configuration.

The validators from the previous step are omitted here so the nested structure remains easy to scan. They can stay on the same fields in the complete application without changing how nesting works.

readonly useShippingAddress = signal(true);

form = form({
name: field(''),
age: field<number>(null),
email: field(''),

shippingAddress: {
street: field(''),
city: field(''),
postalCode: field(''),
},

billingAddress: group({
street: field(''),
city: field(''),
postalCode: field(''),
}, {
disabled: () => this.useShippingAddress()
? 'Using the shipping address for billing.'
: false,
}),
});

The billing branch reacts to useShippingAddress() automatically. While disabled:

  • Its values remain readable and writable.
  • Its errors and pending state are suppressed.
  • It does not make the root invalid.
  • disabledReasons() retains the message and source node.

Nested values and access remain direct:

this.form.shippingAddress.city(); // ''
this.form.billingAddress.disabled();
this.form.patch({
shippingAddress: {
city: 'Zurich',
},
});

Continue with Step 5: Manage a dynamic array.