Angular Hierarchical Grid Editing and Validation
The Hierarchical Grid's editing exposes a built-in validation mechanism of user input when editing cells/rows. It extends the Angular Form validation functionality to allow easier integration with a well known functionality. When the state of the editor changes, visual indicators are applied to the edited cell.
Configuration
Configure via template-driven configuration
We extend some of the Angular Forms validator directives to directly work with the IgxColumn
. The same validators are available as attributes to be set declaratively in igx-column
. The following validators are supported out-of-the-box:
- required
- min
- max
- minlength
- maxlength
- pattern
To validate that a column input would be set and the value is going to be formatted as an email, you can use the related directives:
<igx-column [field]="email" [header]="User E-mail" required email></igx-column>
The following sample demonstrates how to use the prebuilt required
, email
and min
validator directives in a Hierarchical Grid.
Configure via reactive forms
We expose the FormGroup
that will be used for validation when editing starts on a row/cell via a formGroupCreated
event. You can modify it by adding your own validators for the related fields:
<igx-hierarchical-grid (formGroupCreated)='formCreateHandler($event)' ...>
public formCreateHandler(args: IGridFormGroupCreatedEventArgs) {
const formGroup = args.formGroup;
const orderDateRecord = formGroup.get('OrderDate');
const requiredDateRecord = formGroup.get('RequiredDate');
const shippedDateRecord = formGroup.get('ShippedDate');
orderDateRecord.addValidators(this.futureDateValidator());
requiredDateRecord.addValidators(this.pastDateValidator());
shippedDateRecord.addValidators(this.pastDateValidator());
}
You can decide to write your own validator function, or use one of the built-in Angular validator functions.
Validation service API
The grid exposes a validation service via the validation
property.
That service has the following public APIs:
valid
- returns if the grid validation state is valid.getInvalid
- returns records with invalid states.clear
- clears state for record by id or clears all state if no id is provided.markAsTouched
- marks the related record/field as touched.
Invalid states will persis until the validation errors in them are fixed according to the validation rule or they are cleared.
Validation triggers
Validation will be triggered in the following scenarios:
- While editing via the cell editor based on the grid's
validationTrigger
. Either onchange
while typing in the editor, or onblur
when the editor loses focus or closes. - When updating cells/rows via the API -
updateRow
,updateCell
etc.. - When using batch editing and the
undo
/redo
API of the transaction service.
Note: Validation will not trigger for records that have not been edited via user input or via the editing API. Visual indicators on the cell will only shown if the related input is considered touched - either via user interaction or via the
markAsTouched
API of the validation service.
Angular Hierarchical Grid Validation Customization Options
Set a custom validator
You can define your own validation directive to use on a <igx-column>
in the template.
@Directive({
selector: '[phoneFormat]',
providers: [{ provide: NG_VALIDATORS, useExisting: PhoneFormatDirective, multi: true }]
})
export class PhoneFormatDirective extends Validators {
@Input('phoneFormat')
public phoneFormatString = '';
public validate(control: AbstractControl): ValidationErrors | null {
return this.phoneFormatString ? phoneFormatValidator(new RegExp(this.phoneFormatString, 'i'))(control)
: null;
}
}
Once it is defined and added in your app module you can set it declaratively to a given column in the grid:
<igx-column phoneFormat="\+\d{1}\-(?!0)(\d{3})\-(\d{3})\-(\d{4})\b" ...>
Change default error template
You can define your own custom error template that will be displayed in the error tooltip when the cell enters invalid state. This is useful in scenarios where you want to add your own custom error message or otherwise change the look or content of the message.
<igx-column ... >
<ng-template igxCellValidationError let-cell='cell' let-defaultErr="defaultErrorTemplate">
<ng-container *ngTemplateOutlet="defaultErr">
</ng-container>
<div *ngIf="cell.validation.errors?.['phoneFormat']">
Please enter correct phone format
</div>
</ng-template>
</igx-column>
Prevent exiting edit mode on invalid state
In some cases you may want to disallow submitting an invalid value in the data.
In that scenarios you can use the cellEdit
or rowEdit
events and cancel the event in case the new value is invalid.
Both events' arguments have a valid
property and can be canceled accordingly. How it is used can be seen in the Cross-field Validation example
<igx-hierarchical-grid (cellEdit)='cellEdit($event)' ...>
public cellEdit(evt) {
if (!evt.valid) {
evt.cancel = true;
}
}
Example
The below example demonstrates the above-mentioned customization options.
Cross-field validation
In some scenarios validation of one field may depend on the value of another field in the record.
In that case a custom validator can be used to compare the values in the record via their shared FormGroup
.
Cross-field validators can be added to the formGroup on the formGroupCreated
event. In them multiple fields can be compared for validity.
public formCreateCustomerHandler(event: IGridFormGroupCreatedEventArgs) {
const formGroup = event.formGroup;
formGroup.addValidators(this.addressValidator());
}
public formCreateOrderHandler(event: IGridFormGroupCreatedEventArgs) {
const formGroup = event.formGroup;
formGroup.addValidators(this.dateValidator());
}
public addressValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const formGroup = control;
let returnObject = {};
const city = formGroup.get('City');
const country = formGroup.get('Country');
const validCities = this.countryData.get(country.value);
if (!validCities || !validCities[city.value]) {
returnObject['invalidAddress'] = true;
}
return returnObject;
}
}
public dateValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const formGroup = control;
let returnObject = {};
const orderDate = formGroup.get('OrderDate').value;
const shippedDate = formGroup.get('ShippedDate').value;
if (new Date(shippedDate) < new Date(orderDate)) {
returnObject['invalidRange'] = true;
}
return returnObject;
}
}
The multi-field errors can then be displayed in a separate pinned column.
<igx-column field="row_valid" header=" " [editable]="false" [dataType]="'number'" [pinned]="true" [width]="'50px'">
<ng-template igxCell let-cell="cell">
<div *ngIf="isRowValid(cell)" [igxTooltipTarget]="tooltipRef"
>
<img width="18" src="assets/images/grid/active.png"/>
</div>
<div *ngIf="!isRowValid(cell)" [igxTooltipTarget]="tooltipRef"
>
<img width="18" src="assets/images/grid/expired.png"/>
</div>
<div #tooltipRef="tooltip" igxTooltip [style.width]="'max-content'">
<div *ngFor="let message of stateMessage(cell)">
{{message}}
</div>
</div>
</ng-template>
</igx-column>
Errors and the detailed messages can be determined based on the row and cell's validity.
public isRowValid(cell: CellType) {
const hasErrors = !!cell.row.validation.errors || cell.row.cells.some(x => !!x.validation.errors);
return !hasErrors;
}
public stateMessage(cell: CellType) {
const messages = [];
const row = cell.row;
if (row.validation.errors?.invalidAddress) {
messages.push('The address information is invalid. City does not match the Country.');
}
if (row.validation.errors?.invalidRange) {
messages.push('The ShippedDate cannot be before the OrderDate.');
}
const cellValidationErrors = row.cells.filter(x => !!x.validation.errors);
if (cellValidationErrors && cellValidationErrors.length > 0) {
const fields = cellValidationErrors.map(x => x.column.field).join(',');
messages.push('The following fields are required: ' + fields);
}
if (messages.length === 0) {
// no errors
return ['Valid'];
}
return messages;
}
Cross-field example
The below sample demonstrates cross-field validation in a Hierarchical Grid for both the root and child data.
Styling
Using the Ignite UI for Angular Theme Library, we can alter the default validation styles while editing.
In the example below, we will make use of the exposed template for validation message, which pops out in a tooltip and overriding the error color to modify the default looks of the validation. We will also style the background of the invalid rows to make them more distinct.
Import theme
The easiest way to style and access css variables is to define styles in our app
's global style file (typically styles.scss
).
The first thing we need to do is import the themes/index
file - this gives us access to all the powerful tools of the Ignite UI for Angular Sass framework:
@use "igniteui-angular/theming" as *;
// IMPORTANT: Prior to Ignite UI for Angular version 13 use:
// @import '~igniteui-angular/lib/core/styles/themes/index';
Include the styles
In order to change the error color you can use the css variable --igx-error-500
:
--igx-error-500: 34, 80%, 63%;
Custom Templates
Changing the default error template allows setting custom classes and styles:
<ng-template igxCellValidationError let-cell='cell' let-defaultErr='defaultErrorTemplate'>
<div class="validator-container">
<ng-container *ngTemplateOutlet="defaultErr">
</ng-container>
</div>
</ng-template>
Invalid row and cell styles
Rows and cells provide API for the developers to know if a row or cell is invalid and what kind of errors are active.
public rowStyles = {
background: (row: RowType) => row.validation.status === 'INVALID' ? '#FF000033' : '#00000000'
};
public cellStyles = {
'invalid-cell': (rowData, columnKey) => {
let cell = this.hierarchicalGrid.getCellByKey(rowData, columnKey);
// search in child grids
if (!cell) {
for (let grid of this.childGrid.gridAPI.getChildGrids()) {
cell = grid.getCellByKey(rowData, columnKey);
if (cell) break;
}
}
return cell && cell.validation.status === 'INVALID';
}
}
<igx-hierarchical-grid [rowStyles]="rowStyles">
<igx-column field="Artist" [editable]="true" [dataType]="'string'" required [cellClasses]="cellStyles">
...
<igx-row-island [key]="'Albums'" [rowStyles]="rowStyles">
<igx-column field="Album" [editable]="true" [dataType]="'string'" required [cellClasses]="cellStyles">
Demo
API References
Known Issues and Limitations
Limitation | Description |
---|---|
When validationTrigger is blur, editValue and validation will trigger only after editor is blurred. |
Reason is that this utilizes the formControl's updateOn property. This determines the event on which the formControl will update and trigger related validators. |
Additional Resources
- Build CRUD operations with igxGrid
- Hierarchical Grid Overview
- Hierarchical Grid Editing
- Hierarchical Grid Row Editing
- Hierarchical Grid Row Adding
- Hierarchical Grid Transactions