A template binds, or sets as a static attribute, a property that Signal Forms already controls on an element using the [formField] directive.
The field state is the single source of truth for properties such as min, max, required, disabled, and readonly. Specifying them in the template conflicts with the field's schema logic.
The error message names the binding. It reads either "Binding to '[min]' is not allowed..." or "Setting the 'required' attribute is not allowed...".
import {Component, signal} from '@angular/core';
import {form, FormField} from '@angular/forms/signals';
@Component({
selector: 'app-root',
imports: [FormField],
template: `
<!-- NG8022: [min] and [max] are not allowed with [formField]. -->
<input type="number" [formField]="f.age" [min]="18" [max]="99" />
<!-- NG8022: the required attribute is not allowed with [formField]. -->
<input type="text" [formField]="f.name" required />
`,
})
export class App {
f = form(signal({age: 20, name: ''}));
}
The same applies to a custom form control when the template binds an input that the field manages, like [max] on a control that declares a max input.
The properties that cannot be used with [formField] include:
- Values:
value,checked(managed directly by the underlying model signal) - Validation rules:
required,min,max,minLength,maxLength,pattern(as well as native HTMLminlengthandmaxlength) - Interactive state:
disabled,readonly,hidden - Field status and metadata:
name,dirty,touched,invalid,errors,pending,disabledReasons(on custom controls,FormFieldautomatically passes these states to the control, so manually binding them conflicts with the framework's automatic wiring)
There are a few exceptions on native elements:
minandmaxare allowed on<input>elements of typedate,time,month,week, anddatetime-local. On these types the limits are date or time strings like2026-01-01, which the numericmin()andmax()rules can't express.minandmaxare also allowed when thetypeis bound dynamically, because the compiler can't tell what type the input has.valueis allowed on<input type="radio">, because there it identifies the option. The field value decides which radio button is checked.
Debugging the error
Remove the binding from the template, then handle the property according to its category:
- Values (
value,checked):[formField]automatically binds and two-way synchronizes the element with the underlying model signal. To initialize or change values, update the model signal passed toform(). - Validation rules (
required,min,max,maxLength, etc.): Declare them in the schema function ofform()using the corresponding validation rules. - Interactive state (
disabled,readonly,hidden): Use the schema rulesdisabled(),readonly(), andhidden(), which also accept an optionalwhencondition, so a rule can depend on other fields. See form logic. - Field status and metadata (
dirty,touched,errors, etc.): No replacement is needed.[formField]applies them from the field state, and you can read them from the field, for examplef.age().dirty().
The example above then becomes:
import {Component, signal} from '@angular/core';
import {disabled, form, FormField, max, maxLength, min, required} from '@angular/forms/signals';
@Component({
selector: 'app-root',
imports: [FormField],
template: `
<input type="number" [formField]="f.age" />
<input type="text" [formField]="f.name" />
`,
})
export class App {
f = form(signal({age: 20, name: ''}), (p) => {
min(p.age, 18);
max(p.age, 99);
required(p.name);
maxLength(p.name, 20);
disabled(p.name, {when: ({valueOf}) => valueOf(p.age) < 18});
});
}