ComponentStandalone

Form Field

The default wrapper for a form control: label + control + hint + validation errors. It reads the projected control's NgControl, so it decides for itself when to show an error (touched or dirty) and which message to show. <wr-form-item> is the bare alternative — layout only, no knowledge of the control — and has its own page.

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.

We'll never share it.
<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.

Up to 140 characters.
<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

NameDescriptionTypeDefault
labelLabel text shown above the projected control.string''
hintSubtext under the control. Hidden when an error is visible.string''
requiredShow a red * next to the label.booleanfalse
optionalShow (optional) next to the label. Ignored if required is on.booleanfalse
controlIdSet the <label for> target manually. Auto-generated otherwise.stringauto
<wr-form-error>One message per validator key. Renders only when the control is touched / dirty and has that error.component

See also