Custom control contracts
These types describe custom Angular components that bind through [formNode]. They provide
compile-time checks for the supported model and optional UI members. They are not constructors,
providers, or a replacement for the directive's runtime adapter discovery.
Choose the contract
| Component or integration | Public type |
|---|---|
Component with a value model | FormNodeValueControl<TValue, TNode> |
Component with a boolean checked model | FormNodeCheckboxControl<TNode> |
| Generic code accepting either model shape | FormNodeControl<TValue, TNode> |
| Shared optional state inputs and hooks | FormNodeUiControl<TValue, TNode> |
| The directive attached to a host | FormNodeBinding<TNode> or FormNodeDirective<TNode> |
| Read-only state across supported binding APIs | ControlState<TValue>, returned by useFormNodeState() |
| A native form submission output | FormNodeSubmitEvent<TNode> |
Value and checkbox components
A value control exposes value: ModelSignal<TValue>. A checkbox control exposes
checked: ModelSignal<boolean>. The contracts reserve the other model property so that the
component declares one transport. Match nullability to the node: the following components accept
non-nullable values and bind to field.strict() declarations.
import { Component, input, model, output } from '@angular/core';
import { field, form, FormNodeDirective, type FormNodeValueControl, type FormNodeCheckboxControl } from '@ngblocks/form-nodes';
@Component({
selector: 'app-contract-text',
template: `<input #text [value]="value()" [disabled]="disabled()"
(input)="value.set(text.value)" (blur)="touch.emit()">`,
})
export class ContractTextControl implements FormNodeValueControl<string> {
value = model('');
disabled = input(false);
touch = output<void>();
}
@Component({
selector: 'app-contract-checkbox',
template: `<input #checkbox type="checkbox" [checked]="checked()"
(change)="checked.set(checkbox.checked)" (blur)="touch.emit()">`,
})
export class ContractCheckboxControl implements FormNodeCheckboxControl {
checked = model(false);
touch = output<void>();
}
@Component({
imports: [FormNodeDirective, ContractTextControl, ContractCheckboxControl],
template: `
<app-contract-text [formNode]="form.name" />
<app-contract-checkbox [formNode]="form.subscribe" />
`,
})
export class ProfileComponent {
form = form({
name: field.strict('Ada'),
subscribe: field.strict(false),
});
}
The first generic describes the model value, not the node. Where available, the node generic
specializes the optional node signal for direct integration. Ordinary components usually need
only the model and selected UI members.
Optional UI state and interaction
FormNodeUiControl groups signal inputs such as disabled, readonly, required, errors,
and constraints, plus optional touch, focus(), reset(), and node integration. See its
full declaration for exact member types.
Expose touch to report blur or another completed interaction independently of a value change.
Use focus() when the binding needs to focus an inner element. A component's reset() hook
clears its own transient UI state; the node remains responsible for its form state and value rules.
Input synchronization follows configuration; declaring a compatible input does not override
syncInputs or an explicit template binding. See
control binding for synchronization precedence and adapter selection.
Binding state and value notifications
A FormNodeBinding describes a rendered connection. It exposes the host, the bound-node signal,
error state, and outputs. A FormNodeControl describes the component on the other side of that
connection. ControlState is a source-neutral observation facade and does not expose node actions.
formNodeControlValueChange reports the immediate control value; formNodeValueChange follows
commit and debounce. Native submission outputs are separate and carry FormNodeSubmitEvent.
See the directive reference for event ordering and programmatic-write rules.
CVA components can continue using Angular's ControlValueAccessor contract through the CVA
adapter. They do not need to implement these model-based types. See
custom controls and advanced integrations
for complete adapter workflows.