Installation
import { WrFormField, WrFormError } from 'ngwr/form';
import { WrInput } from 'ngwr/input'; // plus whichever control you project
@Component({
imports: [WrFormField, WrFormError, WrInput],
})
export class MyComponent {}Basic usage
Each <wr-form-error> is gated on the matching validator key. Errors appear once the control is touched or dirty — submit to see them all at once.
<wr-form-field label="Email" hint="We'll never share it." required>
<input wrInput [formControl]="email" type="email" />
<wr-form-error key="required">Email is required.</wr-form-error>
<wr-form-error key="email">That isn't a valid email.</wr-form-error>
</wr-form-field>Messages without markup
With no <wr-form-error> projected, the field renders the message the validator reports — resolved from the ngwr/i18n validation.* catalog, so it is already localized. Submit to see it.
<!-- No <wr-form-error> at all. wr-form-field renders the message the
validator reports, from the ngwr/i18n `validation.*` catalog. -->
<wr-form-field label="Handle" required>
<input wrInput formControlName="handle" />
</wr-form-field>
<wr-form-field label="Age">
<input wrInput formControlName="age" type="number" />
</wr-form-field>App-wide overrides
provideWrFormErrors() replaces only the keys you name. A projected <wr-form-error key> still wins over it — per-field copy beats app-wide copy.
import { provideWrFormErrors } from 'ngwr/form';
bootstrapApplication(App, {
providers: [
provideWrFormErrors({
// Only the keys you name — the rest keep resolving through the catalog.
required: 'Please fill this in.',
minlength: ({ error }) => `At least ${(error as { requiredLength: number }).requiredLength} characters.`,
}),
],
});Optional marker
Use optional for the inverse — fields users can skip.
<wr-form-field label="Bio" optional hint="Up to 140 characters.">
<input wrInput [formControl]="bio" placeholder="A short tagline" />
</wr-form-field>`required` is a marker, not a validator — and it is not automatic
<wr-form-field required> draws the asterisk beside the label. It adds no validator, and no validator adds it: nothing on the FormControl reaches back to set it.
This is the one place the two halves of a field have to be kept in step by hand. Validators.required on a FormControl reaches an ngwr control as nothing at all — not a required attribute, and not aria-required. Angular 22's bridge does offer required to a control, but it lands only where the control declares an input of that name, and an ngwr control declares exactly two: disabled and readonly. So write the validator and the marker together, or a screen reader is told the field is optional while the form refuses to submit without it. The same is true of a signal-forms required() rule.
<!-- Both halves, every time. The validator refuses the submit;
the marker is what the reader (and the screen reader) is told. -->
<wr-form-field label="Email" required>
<input wrInput formControlName="email" type="email" />
</wr-form-field>
<!-- If the asterisk must follow the validator rather than be repeated, drive it
off the control — nothing does that for you. -->
<wr-form-field label="Email" [required]="form.controls.email.hasValidator(requiredValidator)">
<input wrInput formControlName="email" type="email" />
</wr-form-field> The states that do reach the control, and the ones this component renders on their behalf, are listed on the reactive-forms guide. In short: this component reads touched, dirty and errors off the projected NgControl and owns everything it draws from them — the message, the wr-form-field--invalid class and the aria-invalid / aria-describedby pair — because the control itself is never told about any of them.
Behavior notes
<!-- Hint shows under the control. Hidden the moment an error becomes
visible (the matching <wr-form-error> takes its slot). -->
<!-- Errors only render after the control is touched OR dirty,
so the user doesn't see red on first paint. -->API
| Name | Description | Type | Default |
|---|---|---|---|
label | Label text shown above the projected control. | string | '' |
hint | Subtext under the control. Hidden when an error is visible. | string | '' |
required | Show a red * next to the label. | boolean | false |
optional | Show (optional) next to the label. Ignored if required is on. | boolean | false |
controlId | Set the <label for> target manually. Auto-generated otherwise. | string | auto |
<wr-form-error> | One message per validator key. Renders only when the control is touched / dirty and has that error. | component | — |
See also
- DirectivewrInputThe most common control to wrap. Add `wrInput` to a native `<input>` and drop it inside.
- Componentwr-selectPairs cleanly — the form-field surfaces select errors too.
- Componentwr-form-itemThe bare alternative: layout only, `hasError` is a boolean you compute. Use it when the control is not an Angular form control.
- ValidatorWrValidatorsThe error-key contract — every `WrValidators.*` member keys its error under its own name. `matchFields` is the one that lands on the GROUP, which this component does not read.