Angular Grid Editing and Validation
The 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 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-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 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-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
.
The below sample demonstrates a cross-field validation between different field of the same record. It checks the dates validity compared to the current date and between the active and created on date of the record as well as the deals won/lost ration for each employee. All errors are collected in a separate pinned column that shows that the record is invalid and displays the related errors.
The next lines of code show the cross-field validator function, which contains the comparisons and sets the related errors relative to them.
private rowValidator(): ValidatorFn {
return (formGroup: FormGroup): ValidationErrors | null => {
let returnObject = {};
const createdOnRecord = formGroup.get('created_on');
const lastActiveRecord = formGroup.get('last_activity');
const winControl = formGroup.get('deals_won');
const loseControl = formGroup.get('deals_lost');
const actualSalesControl = formGroup.get('actual_sales');
// Validate dates
const curDate = new Date();
if (new Date(createdOnRecord.value) > curDate) {
// The created on date shouldn't be greater than current date.
returnObject['createdInvalid'] = true;
}
if (new Date(lastActiveRecord.value) > curDate) {
// The last active date shouldn't be greater than current date.
returnObject['lastActiveInvalid'] = true;
}
if (new Date(createdOnRecord.value) > new Date(lastActiveRecord.value)) {
// The created on date shouldn't be greater than last active date.
returnObject['createdLastActiveInvalid'] = true;
}
// Validate deals
const dealsRatio = this.calculateDealsRatio(winControl.value, loseControl.value);
if (actualSalesControl.value === 0 && dealsRatio > 0) {
// If the actual sales value is 0 but there are deals made.
returnObject['salesZero'] = true;
}
if (actualSalesControl.value > 0 && dealsRatio === 0) {
// If the deals ratio based on deals won is 0 but the actual sales is bigger than 0.
returnObject['salesNotZero'] = true;
}
return returnObject;
};
}
public calculateDealsRatio(dealsWon, dealsLost) {
if (dealsLost === 0) return dealsWon + 1;
return Math.round(dealsWon / dealsLost * 100) / 100;
}
The cross-field validator can be added to the formGroup
of the row from formGroupCreated
event, which returns the new formGroup
for each row when entering edit mode:
<igx-grid #grid1 [data]="transactionData" [width]="'100%'" [height]="'480px'" [autoGenerate]="false"
[batchEditing]="true" [rowEditable]="true" [primaryKey]="'id'"
(formGroupCreated)='formCreateHandler($event)'>
<!-- ... -->
</igx-grid>
public formCreateHandler(evt: IGridFormGroupCreatedEventArgs) {
evt.formGroup.addValidators(this.rowValidator());
}
The different errors are displayed in a templated cell that combines all errors in a single tooltip. Depending on the row valid state different icon is displayed:
<igx-column field="row_valid" header=" " [editable]="false" [pinned]="true" [width]="'50px'">
<ng-template igxCell let-cell="cell">
<div *ngIf="isRowValid(cell)" [igxTooltipTarget]="tooltipRef" style="margin-right: '-10px';">
<img width="18" src="assets/images/grid/active.png"/>
</div>
<div *ngIf="!isRowValid(cell)" [igxTooltipTarget]="tooltipRef" style="margin-right: '-10px';">
<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>
The error messages are gathered in the stateMessage
function, which gathers the errors for each cell, because each column could have templated form validations and then checks the errors for the row itself, which come from the custom rowValidator
.
public stateMessage(cell: CellType) {
const messages = [];
const row = cell.row;
const cellValidationErrors = row.cells.filter(x => !!x.validation.errors);
cellValidationErrors.forEach(cell => {
if (cell.validation.errors) {
if (cell.validation.errors.required) {
messages.push(`The \`${cell.column.header}\` column is required.`);
}
// Other cell errors ...
}
});
if (row.validation.errors?.createdInvalid) {
messages.push(`The \`Date of Registration\` date cannot be in the future.`);
}
// Other cross-field errors...
return messages;
}
Cross-field example
The below sample demonstrates the cross-field validation in action.
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) => {
const pKey = this.grid.primaryKey;
const cell = this.grid.getCellByKey(rowData[pKey], columnKey);
return cell && cell.validation.status === 'INVALID';
}
}
<igx-grid [rowStyles]="rowStyles">
<igx-column field="ReorderLevel" header="ReorderLever" 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
- Grid Overview
- Grid Editing
- Grid Row Editing
- Grid Row Adding
- Grid Transactions