Developer Programs

Learn

Docs

Validating Forms

Components > Editing Data > Forms > Validating Forms
Use this feature to...
Validate the user’s input

Overview

It’s important to validate user input as soon as possible, pointing out any errors they need to correct.

There are three types of errors that we need to validate:

Field-level validation errors

Most validation errors apply to individual fields in the form. Display these validation errors beneath the field’s input. The field label and input border turn red to indicate the error status.

Field-level validation errors don’t display until after the user has either changed the field or submitted the form. This gives the user a chance to provide input for a field before we flag it as invalid. This is especially important for cases where the initial field value is invalid, as is often the case when adding a new record.

Try to catch as many validation errors in the client (browser) as possible before your application posts the form data to the server. This saves time for the user and saves network and server resources. For example, catch places where a required field is missing, or a field doesn’t follow the required format, like an e-mail address, etc. The server must still perform all of the same validation checks; it should never assume that the client caught any validation errors.

However, some validation errors can only be caught at the server, after the client has posted the form data to the server. These validations depend on business logic and/or data only available at the server. Display server validation errors below the field, just like we do with client validation errors.

A field-level validation error displayed beneath the field’s input. The field label and input border turn red to indicate the error status.
Field-level validation error

Cross-field validation errors

Cross-field validation errors occur when the value for two or more fields in the form are valid separately but are invalid when combined. For example, the value provided for an Expiration Date field may be valid on its own, and the value provided for an Effective Date field may also be valid on its own, but if the value specified for the Expiration Date is before the value specified for the Effective Date, the combination of the two values is invalid.

When this happens, display the validation error beneath the input for each corresponding field. This keeps the error message close to each of the affected fields. The field label and input border turn red to indicate the error status. You can either repeat the same validation error beneath all affected fields, or display slight variations of the same validation error that are centric to that field.

A cross-level validation error displayed beneath both affected fields. Both fields reflect the same validation error, but the error text is slightly different for each in order to be centric to that field.
Cross-level validation error

Form-level validation errors

In less common cases, there can be validation errors for the entire form rather than for an individual field. These validation errors display at the top of the form, between the Save/Cancel buttons and the first field in the form.

In cases where you get field-level validation errors from the server that don’t identify the individual fields, you can display these as form-level validation errors, although this can make the user work harder to figure out which field to correct.

Do not display cross-field validation errors as form-level validation errors. Display these as field-level validation errors, so that the error message displays close to each affected field.

It’s important to make it easy for the user to find and fix validation errors in a form’s inputs.

This is less of a problem with small forms, since the fields and any validation errors are mostly visible at a glance. But if the form contains a large number of fields, the user sees less fields at a time, which can make it difficult for the user to know where the field-level validation errors are.

And that’s if the user even knows that there are validation errors in the first place. If all fields with validation errors are currently scrolled out of view, the only clue that the user has that validation errors exist is the fact that the Save button is disabled.

This form contains a validation error in a field that has been scrolled out of view. There is no indication for the user that a validation error exists, other than the fact that the Save button is disabled.
Large Form with non-visible validation error

The validation error navigator helps solve these problems.

  • Add the validation error navigator to the display block toolbar and the floating toolbar. It’s visible whenever the form contains any field-level validation errors and hides when there are no validation errors in the form.
  • The validation error navigator specifies the number of validation errors present in the form. Its very presence is a reliable indicator to the user that the form contains validation errors.
  • The validation error navigator includes arrow buttons for navigating between validation errors. Pressing the “next” button scrolls to and sets focus to the next validation error in the form. The “back” button does the same for the previous validation error in the form.

The validation error navigator must appear to the right of all other buttons in the toolbar. The typical order (left to right) will be Save button, Cancel button, then the validation error navigator.

The validation error navigator does not reproduce the list of validation errors, it simply alerts the user to the fact that the form contains validation errors and helps the user navigate to and fix each validation error.

This form contains six validation errors. The validation error navigator displays the number of validation errors in the form, along with buttons that allow the user to navigate to the next and previous validation errors.
Validation error navigator

Development

Web component development

Component reference

jha-validation-error-navigator
jha-validation-error-navigator
Module: JhaFormsModule - Package: @jkhy/responsive-ui-angular
⚠️ This component requires the use of Angular and will not work in non-Angular applications.

Displays navigational buttons to jump to previous/next form inputs currently failing validation

NameTypeDefaultDescription
jhaFormFormGroupnew FormGroup({})Bind this to the name of the FormGroup in your view. This is used to iterate through the form components and create an array of those with validation errors.
jhaManualErrorListArray<String>[]Bind this property to an array of formControlName strings to manually include as errors in the error navigator.
jhaErrorNavigationeventEvent handler that fires when the user hits the previous or next button. When clicked this event sends an argument of the formControlName. Create an event handler that accepts a string. Use this handler to set focus on the form element with the supplied formControlName.
jhaCustomValidationMappingsArray<JhaCustomValidationMap>[]

If your form uses custom form validators, bind this property to an array of JhaCustomValidationMap objects in your TypeScript.

The JhaCustomValidationMap class contains two properties - name: string, formControlNames: string[].

Implementation

The validation error handling supported by Responsive UI uses the validation functionality built into Angular. Be sure to thoroughly read the Angular validation information here first: https://angular.io/guide/form-validation.

If you’re using the Responsive UI web components in a non-Angular application, you’ll need to use a validation approach that works in that UI technology.

Angular includes several useful built-in validators for the most common validation cases like required field, min/max value, min/max length, pattern (for regular expression field values like a tax ID number or phone number), and email. But these obviously can’t cover every validation use case your app will need, so you’re able to create your own custom validators as well.

We strongly recommend using reactive forms, but you can also use template-driven forms if needed. Both are supported, although be aware that all of our examples and samples use reactive forms.

Imports

First, import FormsModule and ReactiveFormsModule in app.module.ts.

Module Imports
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

...

imports: [
    ...
    FormsModule,
    ReactiveFormsModule,
    ...
]

In the TypeScript that backs the form, import FormGroup, FormControl, and Validators.

Import FormGroup, FormControl, and Validators
import { FormGroup, FormControl, Validators } from '@angular/forms';
Defining the Form

Define the form, its fields, and the validators for each field (if any) in the TypeScript. Create a FormGroup for the form and a FormControl for each field, passing in the initial value for the field as well as zero or more validators.

In this example, the name, ssn, birthdate, street, city, state, zip, and country fields are all required, so we add the required validator for each. The interestRate field has min and max validators and the zip field has a pattern (regex) validator. While Angular supports several built-in validators, you can also create custom validators. The last argument to the FormGroup constructor wires up the CountryAddressTypeValidator (a custom validation directive) as a cross-field validator. This particular validator looks at more than one field, so it runs at the form level instead of the individual field level.

Creating the form
public editForm: FormGroup = new FormGroup({
    'name': new FormControl(this.customerInfo.name, Validators.required),
    'ssn': new FormControl(this.customerInfo.ssn, Validators.required),
    'birthdate': new FormControl(this.customerInfo.birthdate, Validators.required),
    'grossIncome': new FormControl(this.customerInfo.grossIncome),
    'interestRate': new FormControl(this.customerInfo.interestRate, [Validators.min(3), Validators.max(100)]),
    'activeCustomer': new FormControl(this.customerInfo.activeCustomer),
    'idChecked': new FormControl(this.customerInfo.idChecked),
    'street': new FormControl(this.customerInfo.street, Validators.required),
    'city': new FormControl(this.customerInfo.city, Validators.required),
    'state': new FormControl(this.customerInfo.state, Validators.required),
    'zip': new FormControl(this.customerInfo.zip, [Validators.required, Validators.pattern('^[0-9]{5}(?:[0-9]{4})?$')]),
    'country': new FormControl(this.customerInfo.country, Validators.required),
    'addressType': new FormControl(this.customerInfo.addressType),
    'homePhone': new FormControl(this.customerInfo.homePhone),
    'businessPhone': new FormControl(this.customerInfo.businessPhone),
    'mobilePhone': new FormControl(this.customerInfo.mobilePhone),
    'email': new FormControl(this.customerInfo.email, Validators.email)
    }, { validators: CountryAddressTypeValidator });

Next, create a property for each of the form fields to make it easier to interact with each of them in the HTML.

Creating properties for form fields
get name() { return this.editForm.get('name'); }
get ssn() { return this.editForm.get('ssn'); }
get birthdate() { return this.editForm.get('birthdate'); }
get grossIncome() { return this.editForm.get('grossIncome'); }
get interestRate() { return this.editForm.get('interestRate'); }
get activeCustomer() { return this.editForm.get('activeCustomer'); }
get idChecked() { return this.editForm.get('idChecked'); }
get street() { return this.editForm.get('street'); }
get city() { return this.editForm.get('city'); }
get state() { return this.editForm.get('state'); }
get zip() { return this.editForm.get('zip'); }
get country() { return this.editForm.get('country'); }
get addressType() { return this.editForm.get('addressType'); }
get homePhone() { return this.editForm.get('homePhone'); }
get businessPhone() { return this.editForm.get('businessPhone'); }
get mobilePhone() { return this.editForm.get('mobilePhone'); }
get email() { return this.editForm.get('email'); }
Form HTML

Define the form element in the HTML, binding the formGroup attribute to the FormGroup variable you defined in the TypeScript (editForm in this case). Wire up a submit event handler.

Form HTML
<form [formGroup]="editForm" (ngSubmit)="saveCustomerChanges()">

    ...

</form>
Save/Cancel Buttons

Add Save and Cancel buttons for the form. The Save button is a submit button that is disabled any time the editForm is invalid (i.e. when [disabled]="!editForm.valid"). The Cancel button is a regular button that calls a method to abandon the form changes when pressed.

At a minimum you’ll add the Save and Cancel buttons to the display block toolbar, but you should also add them to a floating toolbar if the form is long. The floating toolbar displays after the user scrolls down the page a bit, so having the Save/Cancel buttons in the floating toolbar makes them available to the user no matter how far down they scroll.

Save/Cancel Buttons
<form [formGroup]="editForm" (ngSubmit)="saveChanges()">

    <rui-floating-toolbar>
        <rui-button text="Save" buttonStyle="primary" buttonType="submit" [isDisabled]="!editForm.valid"></rui-button>
        <rui-button text="Cancel" buttonStyle="secondary" (rui-click)="cancelChanges()"></rui-button>
    </rui-floating-toolbar>

    <rui-display-block>
        <rui-toolbar slot="display-block-toolbar-below">
            <rui-button text="Save" buttonStyle="primary" buttonType="submit" [isDisabled]="!editForm.valid"></rui-button>
            <rui-button text="Cancel" buttonStyle="secondary" (rui-click)="cancelChanges()"></rui-button>
        </rui-toolbar>

        ...

    </rui-display-block>

</form>
Field-Level Validation Errors

See Editing Forms for information on how to set up form fields, including field-level validation errors.

Form-Level Validation Errors

Display form-level validations at the top of the form, between the Save/Cancel buttons and the form elements.

  • Wrap the form-level validation errors in an error inline notification
  • We display validation errors only if the user has changed or visited the form’s fields, which we test with the Angular attributes editForm.dirty and editForm.touched.
Form-Level validation errors
<rui-display-block>

    <rui-toolbar slot="display-block-toolbar-below">
        <rui-button text="Save" buttonStyle="primary" buttonType="submit" [isDisabled]="!editForm.valid"></rui-button>
        <rui-button text="Cancel" buttonStyle="secondary" (rui-click)="cancelChanges()"></rui-button>
    </rui-toolbar>

    <!-- Form-level validation errors -->
    <rui-inline-notification type="error" iconType="error" *ngIf="editForm.errors && (editForm.touched || editForm.dirty)">
        <div *ngIf="editForm.errors?.duplicateRecord">
            You cannot add a second record of this type
        </div>
    </rui-inline-notification>

    <!-- Form elements go here -->

</rui-display-block>
Validation Error Navigator

The jha-validation-error-navigator allows users to easily go to the previous/next validation error by clicking on the navigator arrows. Clicking the arrows fires an event, which you will need to handle to set focus on the correct form component.

Import the JhaFormsModule using the .forRoot() method to inject the validator service.

Import JhaFormsModule
import { JhaFormsModule } from '@jkhy/responsive-ui-angular/jha-forms';

...

imports: [
    ...
    JhaFormsModule.forRoot(),
    ...
]

In this example, we’re using the form component id attribute to find the correct element. This means your form component elements will need to relate their formControlName attributes with the id attributes. To do so, take the formControlName of the input and append 'field-' to create your id.

Setting field ID
<input formControlName="name" id="field-name" type="text" class="rui-form-control" />

If the form includes a FormArray, the string returned from the error navigator will include the FormArray name followed by a dash and an index to highlight the correct input (example: teachers-0). For cases like this, construct your element id to include the index. If the FormArray contains FormGroups around the FormControls, the string returned will include the formArrayName followed by a dash and the formGroupName followed by a dash and the formControlName of the input (example: teachers-0-name).

Setting field ID with FormArray
<div formArrayName="teachers">
    <div *ngFor="let teacher of teachers.controls; index as i">
        <input id="field-teachers-{{i}}" [formControlName]="i">
    </div>
</div>

Once the form is set up, add a jha-validation-error-navigator component to the rui-toolbar within the form’s display block. Bind the jhaForm property to your FormGroup and bind the jhaErrorNavigation property to a newly created event handler. If your form uses custom form validators, bind an array of JhaCustomValidationMap elements to the jhaCustomValidationMappings property.

Adding a validation error navigator to the display block toolbar
<form [formGroup]="editForm" (ngSubmit)="saveChanges()">
    <rui-display-block>

        <rui-toolbar slot="display-block-toolbar-below">
            <rui-button text="Save" buttonStyle="primary" buttonType="submit" [isDisabled]="!editForm.valid"></rui-button>
            <rui-button text="Cancel" (rui-click)="cancelChanges()"></rui-button>
            <jha-validation-error-navigator [jhaForm]="editForm" (jhaErrorNavigation)="goToFormControl($event)"
                [jhaCustomValidationMappings]="customValidationMaps"></jha-validation-error-navigator>
        </rui-toolbar>

        ...

    </rui-display-block>
</form>

If the view uses a rui-floating-toolbar, add a jha-validation-error-navigator component with the same properties and values.

Adding a validation error navigator to the floating toolbar
<form [formGroup]="editForm" (ngSubmit)="saveChanges()">

    <rui-floating-toolbar>
        <rui-button text="Save" buttonStyle="primary" buttonType="submit" [isDisabled]="!editForm.valid"></rui-button>
        <rui-button text="Cancel" (rui-click)="cancelChanges()"></rui-button>
        <jha-validation-error-navigator [jhaForm]="editForm" (jhaErrorNavigation)="goToFormControl($event)"
            [jhaCustomValidationMappings]="customValidationMaps"></jha-validation-error-navigator>
    </rui-floating-toolbar>

    <rui-display-block>

        ...

    </rui-display-block>
</form>

In your event handler, accept a string argument to capture the formControlName of the input where focus should be applied. This handler will call a method to apply that focus.

If you’re using a large form within a wizard or accordion group boxes, there are additional steps that need to be taken to navigate the user to the correct step or group box before focusing on the form component. You can find details on the additional coding needed in Large Forms.

Navigating to the error field in a wizard - part 1
public goToFormControl(formControlName: string) {
    setTimeout(() => { this.focusField(formControlName); }, 100);
}

Next, create a method to apply focus to the desired input. Find the form component by using the constructed id. Then test if the element has any children inputs (this would be the case for custom components such as p-calendar, p-checkbox, etc.) and assign focus to the first child input, or (else) this element is a standard html element an assign focus to it directly.

Navigating to the error field in a wizard - part 2
public focusField(formControlName: string) {
    const nextControl = document.getElementById('field-' + formControlName);
    if (nextControl.getElementsByTagName('input')[0]) {
        // If element has child inputs, set focus on the first child input
        nextControl.getElementsByTagName('input')[0].focus();
    } else {
        // This is a standard input, just set focus
        nextControl.focus();
    }
}

And finally, create a public property populated with any custom validators used in the form. Import the JhaCustomValidationMap class, then create an array with the name of the error thrown by the validator, followed by an array of the formControlName values of the fields that need to be addressed in the validation error.

Navigating to the error field in a wizard - part 3
import { JhaCustomValidationMap } from '@jkhy/responsive-ui-angular/jha-forms';

...

public customValidationMaps: JhaCustomValidationMap[] = [
    new JhaCustomValidationMap('countryAddressTypeMismatch', ['country', 'addressType'])
];

Angular wrapper development

Wrapper reference

jha-validation-error-navigator
jha-validation-error-navigator
Module: JhaFormsModule - Package: @jkhy/responsive-ui-angular

Displays navigational buttons to jump to previous/next form inputs currently failing validation

NameTypeDefaultDescription
jhaFormFormGroupnew FormGroup({})Bind this to the name of the FormGroup in your view. This is used to iterate through the form components and create an array of those with validation errors.
jhaManualErrorListArray<String>[]Bind this property to an array of formControlName strings to manually include as errors in the error navigator.
jhaErrorNavigationeventEvent handler that fires when the user hits the previous or next button. When clicked this event sends an argument of the formControlName. Create an event handler that accepts a string. Use this handler to set focus on the form element with the supplied formControlName.
jhaCustomValidationMappingsArray<JhaCustomValidationMap>[]

If your form uses custom form validators, bind this property to an array of JhaCustomValidationMap objects in your TypeScript.

The JhaCustomValidationMap class contains two properties - name: string, formControlNames: string[].

Implementation

The validation error handling supported by Responsive UI uses the validation functionality built into Angular. Be sure to thoroughly read the Angular validation information here first: https://angular.io/guide/form-validation.

If you’re using the Responsive UI web components in a non-Angular application, you’ll need to use a validation approach that works in that UI technology.

Angular includes several useful built-in validators for the most common validation cases like required field, min/max value, min/max length, pattern (for regular expression field values like a tax ID number or phone number), and email. But these obviously can’t cover every validation use case your app will need, so you’re able to create your own custom validators as well.

We strongly recommend using reactive forms, but you can also use template-driven forms if needed. Both are supported, although be aware that all of our examples and samples use reactive forms.

Imports

First, import FormsModule and ReactiveFormsModule in app.module.ts.

Module Imports
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

...

imports: [
    ...
    FormsModule,
    ReactiveFormsModule,
    ...
]

In the TypeScript that backs the form, import FormGroup, FormControl, and Validators.

Import FormGroup, FormControl, and Validators
import { FormGroup, FormControl, Validators } from '@angular/forms';
Defining the Form

Define the form, its fields, and the validators for each field (if any) in the TypeScript. Create a FormGroup for the form and a FormControl for each field, passing in the initial value for the field as well as zero or more validators.

In this example, the name, ssn, birthdate, street, city, state, zip, and country fields are all required, so we add the required validator for each. The interestRate field has min and max validators and the zip field has a pattern (regex) validator. While Angular supports several built-in validators, you can also create custom validators. The last argument to the FormGroup constructor wires up the CountryAddressTypeValidator (a custom validation directive) as a cross-field validator. This particular validator looks at more than one field, so it runs at the form level instead of the individual field level.

Creating the form
public editForm: FormGroup = new FormGroup({
    'name': new FormControl(this.customerInfo.name, Validators.required),
    'ssn': new FormControl(this.customerInfo.ssn, Validators.required),
    'birthdate': new FormControl(this.customerInfo.birthdate, Validators.required),
    'grossIncome': new FormControl(this.customerInfo.grossIncome),
    'interestRate': new FormControl(this.customerInfo.interestRate, [Validators.min(3), Validators.max(100)]),
    'activeCustomer': new FormControl(this.customerInfo.activeCustomer),
    'idChecked': new FormControl(this.customerInfo.idChecked),
    'street': new FormControl(this.customerInfo.street, Validators.required),
    'city': new FormControl(this.customerInfo.city, Validators.required),
    'state': new FormControl(this.customerInfo.state, Validators.required),
    'zip': new FormControl(this.customerInfo.zip, [Validators.required, Validators.pattern('^[0-9]{5}(?:[0-9]{4})?$')]),
    'country': new FormControl(this.customerInfo.country, Validators.required),
    'addressType': new FormControl(this.customerInfo.addressType),
    'homePhone': new FormControl(this.customerInfo.homePhone),
    'businessPhone': new FormControl(this.customerInfo.businessPhone),
    'mobilePhone': new FormControl(this.customerInfo.mobilePhone),
    'email': new FormControl(this.customerInfo.email, Validators.email)
    }, { validators: CountryAddressTypeValidator });

Next, create a property for each of the form fields to make it easier to interact with each of them in the HTML.

Creating properties for form fields
get name() { return this.editForm.get('name'); }
get ssn() { return this.editForm.get('ssn'); }
get birthdate() { return this.editForm.get('birthdate'); }
get grossIncome() { return this.editForm.get('grossIncome'); }
get interestRate() { return this.editForm.get('interestRate'); }
get activeCustomer() { return this.editForm.get('activeCustomer'); }
get idChecked() { return this.editForm.get('idChecked'); }
get street() { return this.editForm.get('street'); }
get city() { return this.editForm.get('city'); }
get state() { return this.editForm.get('state'); }
get zip() { return this.editForm.get('zip'); }
get country() { return this.editForm.get('country'); }
get addressType() { return this.editForm.get('addressType'); }
get homePhone() { return this.editForm.get('homePhone'); }
get businessPhone() { return this.editForm.get('businessPhone'); }
get mobilePhone() { return this.editForm.get('mobilePhone'); }
get email() { return this.editForm.get('email'); }
Form HTML

Define the form element in the HTML, binding the formGroup attribute to the FormGroup variable you defined in the TypeScript (editForm in this case). Wire up a submit event handler.

Form HTML
<form [formGroup]="editForm" (ngSubmit)="saveCustomerChanges()">

    ...

</form>
Save/Cancel Buttons

Add Save and Cancel buttons for the form. The Save button is a submit button that is disabled any time the editForm is invalid (i.e. when [disabled]="!editForm.valid"). The Cancel button is a regular button that calls a method to abandon the form changes when pressed.

At a minimum you’ll add the Save and Cancel buttons to the display block toolbar, but you should also add them to a floating toolbar if the form is long. The floating toolbar displays after the user scrolls down the page a bit, so having the Save/Cancel buttons in the floating toolbar makes them available to the user no matter how far down they scroll.

Save/Cancel Buttons
<form [formGroup]="editForm" (ngSubmit)="saveCustomerChanges()">

    <jha-floating-toolbar>
        <jha-button jhaText="Save" jhaButtonStyle="Primary" jhaButtonType="submit" [jhaIsDisabled]="!editForm.valid"></jha-button>
        <jha-button jhaText="Cancel" jhaButtonStyle="Secondary" (jhaClick)="cancelChanges()"></jha-button>
    </jha-floating-toolbar>

    <jha-display-block>

        <jha-display-block-toolbar>
            <jha-button jhaText="Save" jhaButtonStyle="Primary" jhaButtonType="submit" [jhaIsDisabled]="!editForm.valid"></jha-button>
            <jha-button jhaText="Cancel" jhaButtonStyle="Secondary" (jhaClick)="cancelChanges()"></jha-button>
        </jha-display-block-toolbar>

        ...

    </jha-display-block>

</form>
Field-Level Validation Errors

See Editing Forms for information on how to set up form fields, including field-level validation errors.

Form-Level Validation Errors

Display form-level validations at the top of the form, between the Save/Cancel buttons and the form elements.

  • Wrap the form-level validation errors in an error inline notification
  • We display validation errors only if the user has changed or visited the form’s fields, which we test with the Angular attributes editForm.dirty and editForm.touched.
Form-Level validation errors
<jha-display-block>

    <jha-display-block-toolbar>
        <jha-button jhaText="Save" jhaButtonStyle="Primary" jhaButtonType="submit" [jhaIsDisabled]="!editForm.valid"></jha-button>
        <jha-button jhaText="Cancel" jhaButtonStyle="Secondary" (jhaClick)="cancelChanges()"></jha-button>
    </jha-display-block-toolbar>

    <!-- Form-level validation errors -->
    <jha-inline-notification jhaType="Error" jhaIconType="Error" *ngIf="editForm.errors && (editForm.touched || editForm.dirty)">
        <div *ngIf="editForm.errors?.duplicateRecord">
            You cannot add a second record of this type
        </div>
    </jha-inline-notification>

    <!-- Form elements go here -->

</jha-display-block>
Validation Error Navigator

The jha-validation-error-navigator allows users to easily go to the previous/next validation error by clicking on the navigator arrows. Clicking the arrows fires an event, which you will need to handle to set focus on the correct form component.

Import the JhaFormsModule using the .forRoot() method to inject the validator service.

Import JhaFormsModule
import { JhaFormsModule } from '@jkhy/responsive-ui-angular/jha-forms';

...

imports: [
    ...
    JhaFormsModule.forRoot(),
    ...
]

In this example, we’re using the form component id attribute to find the correct element. This means your form component elements will need to relate their formControlName attributes with the id attributes. To do so, take the formControlName of the input and append 'field-' to create your id.

Setting field ID
<input formControlName="name" id="field-name" type="text" class="rui-form-control" />

If the form includes a FormArray, the string returned from the error navigator will include the FormArray name followed by a dash and an index to highlight the correct input (example: teachers-0). For cases like this, construct your element id to include the index. If the FormArray contains FormGroups around the FormControls, the string returned will include the formArrayName followed by a dash and the formGroupName followed by a dash and the formControlName of the input (example: teachers-0-name).

Setting field ID with FormArray
<div formArrayName="teachers">
    <div *ngFor="let teacher of teachers.controls; index as i">
        <input id="field-teachers-{{i}}" [formControlName]="i">
    </div>
</div>

Once the form is set up, add a jha-validation-error-navigator component to the jha-display-block-toolbar within the form’s display block. Bind the jhaForm property to your FormGroup and bind the jhaErrorNavigation property to a newly created event handler. If your form uses custom form validators, bind an array of JhaCustomValidationMap elements to the jhaCustomValidationMappings property.

Adding a validation error navigator to the display block toolbar
<form [formGroup]="editForm" (ngSubmit)="saveChanges()">
    <jha-display-block>

        <jha-display-block-toolbar>
            <jha-button jhaText="Save" jhaButtonStyle="Primary" jhaButtonType="submit" [jhaIsDisabled]="!editForm.valid"></jha-button>
            <jha-button jhaText="Cancel" (jhaClick)="cancelChanges()"></jha-button>
            <jha-validation-error-navigator [jhaForm]="editForm" (jhaErrorNavigation)="goToFormControl($event)"
             [jhaCustomValidationMappings]="customValidationMaps"></jha-validation-error-navigator>
        </jha-display-block-toolbar>

        ...

    </jha-display-block>
</form>```

If the view uses a jha-floating-toolbar, add a jha-validation-error-navigator component with the same properties and values.

Adding a validation error navigator to the floating toolbar
<form [formGroup]="editForm" (ngSubmit)="saveChanges()">

    <jha-floating-toolbar>
        <jha-button jhaText="Save" jhaButtonStyle="Primary" jhaButtonType="submit" [jhaIsDisabled]="!editForm.valid"></jha-button>
        <jha-button jhaText="Cancel" (jhaClick)="cancelChanges()"></jha-button>
        <jha-validation-error-navigator [jhaForm]="editForm" (jhaErrorNavigation)="goToFormControl($event)"
         [jhaCustomValidationMappings]="customValidationMaps"></jha-validation-error-navigator>
    </jha-floating-toolbar>

    <jha-display-block>

        ...

    </jha-display-block>
</form>

In your event handler, accept a string argument to capture the formControlName of the input where focus should be applied. This handler will call a method to apply that focus.

If you’re using a large form within a wizard or accordion group boxes, there are additional steps that need to be taken to navigate the user to the correct step or group box before focusing on the form component. You can find details on the additional coding needed in Large Forms.

Navigating to the error field in a wizard - part 1
public goToFormControl(formControlName: string) {
    setTimeout(() => { this.focusField(formControlName); }, 100);
}

Next, create a method to apply focus to the desired input. Find the form component by using the constructed id. Then test if the element has any children inputs (this would be the case for custom components such as p-calendar, p-checkbox, etc.) and assign focus to the first child input, or (else) this element is a standard html element an assign focus to it directly.

Navigating to the error field in a wizard - part 2
public focusField(formControlName: string) {
    const nextControl = document.getElementById('field-' + formControlName);
    if (nextControl.getElementsByTagName('input')[0]) {
        // If element has child inputs, set focus on the first child input
        nextControl.getElementsByTagName('input')[0].focus();
    } else {
        // This is a standard input, just set focus
        nextControl.focus();
    }
}

And finally, create a public property populated with any custom validators used in the form. Import the JhaCustomValidationMap class, then create an array with the name of the error thrown by the validator, followed by an array of the formControlName values of the fields that need to be addressed in the validation error.

Navigating to the error field in a wizard - part 3
import { JhaCustomValidationMap } from '@jkhy/responsive-ui-angular/jha-forms';

...

public customValidationMaps: JhaCustomValidationMap[] = [
    new JhaCustomValidationMap('countryAddressTypeMismatch', ['country', 'addressType'])
];

Design

Figma design

Figma design info
You can find this component in the Components - Forms page in the Figma UI Kit.
Dev ComponentDesign Component Name
Validation error navigatorRUI / Forms / Validation Error Navigator
Text input with validation errorRUI / Forms / Text Input; select “Error” as the value for the State property
Field-level validation error textRUI / Forms / Validation Error

Design documents typically do not show validation errors. That information is usually conveyed in a separate design artifact.

If you still need to display validation errors in your design, you can do the following:

  • Add a validation error navigator to the display block toolbar, to the right of the Save and Cancel buttons.
  • Separate the Save and Cancel buttons from the validation error navigator with a display block toolbar separator.
  • For every field for which you’re showing a validation:
    • Add a validation error component below the input component that contains the appropriate validation error text. Change its text to display the appropriate validation error text.
    • Select the field label and apply the “rui-pacific/text-error” color style to it.
    • Specify a value of “Error” for the State property in the field’s input component.

Adobe XD design

Adobe XD design info
You can find this component in these artboards in the Adobe XD design samples:
  • Sample App - Validating Form Data
Dev ComponentDesign Component Name
Validation error navigatorJHA / Forms / Validation Error Navigator

Design documents typically do not show validation errors. That information is usually conveyed in a separate design artifact.

If you still need to display validation errors in your design, you can do the following:

  • Add a validation error navigator to the display block toolbar, to the right of the Save and Cancel buttons.
  • Separate the Save and Cancel buttons from the validation error navigator with a display block toolbar separator.
  • For every field for which you’re showing a validation:
    • Add help text below the input component that contains the appropriate validation error text.
    • Select the field label, the required field indicator (if present), and the help text.
    • Search for the JHA / Color / Text / Error color asset in the Assets panel. Click the color asset or right-click it and select Apply as Fill to apply the error color to the field label, required field indicator, and help text.
    • Select the input component.
    • Right-click the error color asset and select Apply as Border to apply the error color as the input component’s border.

Support options
Have questions on this topic?
Join the Responsive UI team in Microsoft Teams to connect with the community.
See something in this page that needs to change?
Send us feedback on this page.
Last updated Tue Feb 27 2024