jigErrors resolves a control's validation errors into a human-readable
message and hands it to an jig-hint. It is the bridge
between Angular's form validation and what the user actually sees.
It works the same way on all three form paradigms — template-driven
(ngModel), reactive (formControl / formControlName) and signal forms
([formField]) — and also with no form at all. See
Forms & Validation for the full picture.
Put jigErrors on the same element as the form binding and point it at a hint:
The hint stays empty until there is something to show. By default messages appear once the control is touched, so a pristine form is not a wall of red. An empty hint takes up no space, and the message expands into place instead of popping in.
Nothing changes — the same directive reads the field's errors through the
NgControl that [formField] provides:
Signal-forms validators report their own error kinds, and both spellings are
covered by the built-in table: classic minlength/maxlength and the signal
forms minLength/maxLength.
jigErrors never touches the control's appearance. The red border and
aria-invalid are the control's job, driven by its own invalid and
invalidOn inputs, so the two can be timed independently:
<!-- border as soon as it is invalid, message only after blur -->
<input jigInput invalidOn="immediate" jigErrors jigErrorsShowOn="touched" />A signal-forms binding writes invalid in for you; with reactive forms you set
it yourself. See State.
jigErrorsShowOn decides the trigger:
| Value | Messages appear |
|---|---|
touched |
after the control has been blurred (default) |
dirty |
as soon as the value has changed |
submitted |
after the surrounding form has been submitted |
always |
immediately |
never |
never — useful to suppress a field conditionally |
With no form present, "touched" falls back to the control's own touched
signal and its touch (blur) output, so it still behaves correctly.
jigErrorsMessages overrides messages for one control. A value can be a string
or a resolver that receives the error's params:
jigErrorsMode="all" shows every failing rule, joined by newlines, instead of
just the first.
To change messages application-wide, provide them once instead:
import { provideJigErrorsMessages } from '@awdlab/jig/errors';
providers: [
provideJigErrorsMessages({
required: 'This field is required.',
email: 'That does not look like an email address.',
}),
];The token is a multi provider, so several maps merge — a feature module can
add its own keys without replacing yours.
For each error key, the first source that yields a non-empty message wins:
jigErrorsMessages on this controlmessage field
({ tooShort: { message: '…' } }) or a plain-string error valueprovideJigErrorsMessages<scope>.errors.<key>errors.<key> (see i18n)An empty string counts as "no message" and falls through to the next source, so you can blank out a single key without losing the rest.
Step 4 lets a control give a shared error kind wording that actually fits it,
without any app code. jig-otp translates required as "Enter the full code",
and jig-tag-input as "Add at least one entry", while every other control keeps
the generic "Required".
The scope is the control's theme scope in camelCase, so the translation key is
otp.errors.required or tagInput.errors.required. A control that defines no
scoped entry for a key simply falls through to the shared one — nothing to
configure either way.
Errors whose message can only come from the built-in translations are held back until the locale has loaded, so a raw key like
requirednever flashes on screen first. Errors with your own message show immediately.
jigErrorsCustom layers errors on top of validation — server responses,
cross-field rules, anything Angular does not know about:
It accepts three shapes:
// 1. a ValidationErrors object
{
taken: true;
}
// 2. plain keys, resolved through the message table
['taken', 'reserved'][
// 3. entries with their own message and params
{ key: 'taken', message: 'That name is already in use.' }
];Validators on a FormGroup produce errors on the group, not on any one field.
jigErrors surfaces a parent error on a child control when the error value
names that control — under control, controlName, field, controls,
controlNames or fields, as a string or an array of them:
// shown on the control named 'confirmPassword'
{
passwordMismatch: {
controls: ['password', 'confirmPassword'];
}
}A group error that names nothing stays on the group and is not repeated under every field.
While a validator is pending the state is pending, and the hint shows the
translated "validating" message. Pending always displays, regardless of
jigErrorsShowOn, so a slow check never looks like nothing is happening.
Export the directive to build your own UI instead of using a hint:
<input jigInput [formControl]="email" jigErrors #errors="jigErrors" />
@if (errors.visible()) {
<ul>
@for (error of errors.errors(); track error.key) {
<li>{{ error.message }}</li>
}
</ul>
}errors() gives the normalized list (key, value, source, message,
params), plus firstError(), message(), visible(), pending() and a
combined state().
jig