The Age column only accepts values from 22 and 60.
We intentionally left the Email and Hire Date columns with no validators because we set them using the API.
Please, go to the live example and test the validators we have set.
Conveying error messages
When a field value does not meet the validator criteria, the DikeGrid displays a red bar at the left of the field.
You click on the red bar to see the specific validation message.
Standard validation messages
We have seen how to display the validation message specific to a field.
The DikeGrid offers default validation messages when validators throw an error.
To see the complete list of validation messages, see the DikeStandardErrorMessage class definition.
Custom validation messages
You can provide custom validation messages by providing an Injection Token or at the column level.
Messages by providing an Injection Token
When you modify or add validation messages by providing an Injection Token, you affect all your DikeGrid instances that live under the place you give your Injection Token.
Let us overwrite the required validator.
editing.module.ts
@NgModule({ providers: [ { provide:CUSTOM_EDITION_ERROR_MESSAGES,useFactory: ():CustomErrorMessage=>newCustomErrorMessage().addMessage(ErrorType.REQUIRED,'You can not leave the field empty.') } ]})exportclassEditingModule { }
As you can see, all the columns show the required validator message defined in the module.
Messages at the column level
When you want to be more specific in the messages you want to convey, you can define those messages at the column level.
In the following code snippet, we overwrite several validators' messages.
edition-validation.component.ts
ngOnInit(): void {// ...// Define the column edition settings:this.nameEditionSettings = { required:true, validators: [ Validators.maxLength(12) ], errorMessages:newCustomErrorMessage().addMessage(ErrorType.MAX_LENGTH,'The Name column maximum length must be 12.') };this.lastNameEditionSettings = { required:true, validators: [ Validators.maxLength(15) ], errorMessages:newCustomErrorMessage().addMessage(ErrorType.MAX_LENGTH,'The Surname column maximum length must be 15.') };this.genderEditionSettings = { required:true, options: [ { label:'M', value:'male' }, { label:'F', value:'female' } ] };this.ageEditionSettings = { required:true, validators: [ Validators.min(22),Validators.max(60) ], errorMessages:newCustomErrorMessage().addMessage(ErrorType.REQUIRED,'You must enter the age of the employee.').addMessage(ErrorType.MIN,'The minimum age to be hired must be 22 years old.').addMessage(ErrorType.MAX,'The maximum age to be hired must be 60 years old.') };this.emailEditionSettings = { required:true };this.hireDateEditionSettings = { required:true };}
When running the live example, pay attention to the required validator for the Age column.
The column level definition has the highest precedence despite overwriting the required validator at the module level. Therefore, the DikeGrid shows the Age column required-validator message.
Custom validators
If the pre-built Angular validators are not enough for your use cases, you can create your custom validators.
See the official Angular docs for further details on custom validators.
See the following code snippet.
ngOnInit(): void {//...// Define the column edition settings:this.nameEditionSettings = { required:true, validators: [ Validators.maxLength(12),forbiddenNameValidator(/erick/i) ], errorMessages:newCustomErrorMessage().addMessage(ErrorType.MAX_LENGTH,'The Name column maximum length must be 12.').addMessage('forbiddenName','The Name column can not contain the erick string.') };//...}
We have taken the custom validator forbiddenNameValidator from the Angular docs and attached it to the Name column. The custom validator does not allow the user to type a name that contains the erick string.
We have attached a custom message to the forbiddenNameValidator validator as well.
Cross-field validation
When defining columns, the DikeGrid add them to the center panel if you do not specify a different panel for each column. Therefore, a DikeGrid instance could have one panel (center panel) or three (left, center, and right panels).
When you allow the user to edit rows, the DikeGrid creates one FormGroup for the entire row, one FormGroup for each panel, and one FormControl for each column field.
Having said how the DikeGrid organizes the FormControl's and the FormGroup's, you can add cross-validation on the row-level FormGroup, the outermost FormGroup.
@Component({ selector:'edition-validation', templateUrl:'./edition-validation.component.html', styleUrls: ['./edition-validation.component.scss'], encapsulation:ViewEncapsulation.None, changeDetection:ChangeDetectionStrategy.OnPush})exportclassEditionValidationComponentimplementsOnInit,OnDestroy {//... gridEditionSettings:DikeGridEditionSettings<Employee>;ngOnInit():void {//...this.gridEditionSettings = { crossFieldValidation: { customValidator: menAgeCrossValidation, errorMessages:newCustomErrorMessage().addMessage('menAge','The minimum age for men is 36 years old.') } }; }}
// This function validates that men must be older than 35 years old:exportconstmenAgeCrossValidation= (columnDef:DikeGridColumnDef<Employee>):ValidatorFn=> {// Get the desired columns:constgenderColumn=columnDef.findColumn(column =>column.fieldName ==='gender');constageColumn=columnDef.findColumn(column =>column.fieldName ==='age');// This validator function is attached to the row's FormGroup:return (rowGroup:AbstractControl):ValidationErrors|null=> {// If the columns can not be found, there is no error:if (!genderColumn ||!ageColumn) {returnnull; }// Get the controls from the corresponding FormGroup.// The slotId property identifies each column control inside the corresponding FormGroup:constgenderControl=rowGroup.get(genderColumn.panel)?.get(genderColumn.slotId);constageControl=rowGroup.get(ageColumn.panel)?.get(ageColumn.slotId);// If the controls can not be found, there is no error:if (!genderControl ||!ageControl) {returnnull; }returngenderControl.value asstring === 'male'&&ageControl.value asnumber<=35? { menAge:true } :null; };};
In the previous code snippet:
We have defined a custom validator but following a specific signature that receives the related DikeGridColumnDef instance.
We have also created the corresponding validator message.
Then, we have added the cross-validator to the edition settings at the level of the DikeGrid instance.
onEmailValidator(): void {constemailColumn=this.dikeGrid.columnDef.findColumn(column =>column.fieldName ==='email');// The column exists and is a data column:if (!!emailColumn &&isDikeDataColumnDef(emailColumn)) {// Assign the email validator:this.dikeGrid.columnDef.setColumnEditable(emailColumn,true, { editionSettings: { ...this.emailEditionSettings, validators: [ Validators.email ] } }); }}onHireDateValidator(): void {consthireDateColumn=this.dikeGrid.columnDef.findColumn(column =>column.fieldName ==='hireDate');// The column exists and is a data column:if (!!hireDateColumn &&isDikeDataColumnDef(hireDateColumn)) {consttoday=newDate();// Assign the custom limitHireDateValidator validator:this.dikeGrid.columnDef.setColumnEditable(hireDateColumn,true, { editionSettings: {...this.hireDateEditionSettings, validators: [ limitHireDateValidator(newDate(today.getFullYear(),today.getMonth(),today.getDate())) ], errorMessages:newCustomErrorMessage().addMessage('limitHireDate','You can not enter a date after today.') } }); }}
exportconstlimitHireDateValidator= (limitDate:Date):ValidatorFn=> (control:AbstractControl):ValidationErrors|null=> {if (!(control.value instanceofDate)) {returnnull; }// Get the Date from the input:constcontrolDate=control.value asDate;returncontrolDate.getTime() >limitDate.getTime() ? { limitHireDate:true } :null; };
The previous definition generates the following output:
We describe the button actions in the following list:
Email validator. Clicking on this button will add the email validator to the Email column.
Hire Date validator. This action adds the limitHireDateValidator custom validator to the Hire Date column.
The limitHireDateValidator custom validator prevents users from entering dates after the current date.
When changing the validators at runtime, the DikeGrid re-renders the view for the involved columns.
Summary
When using the Angular validator functions, the DikeGrid offers a set of validations messages. You can overwrite these validation messages or add new ones.
Finally, you can create custom validation functions at the column or row levels. You can assign these validators at column definition or runtime.
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from'@angular/core';import { Validators } from'@angular/forms';import { Subscription } from'rxjs';import { CustomErrorMessage, DikeGridComponent, DikeGridDataSourceInput, DikeGridEditionSettings, EditionFieldSettings, ErrorType, isDikeDataColumnDef} from'@dikesoft/angular-data-grid';import { forbiddenNameValidator, limitHireDateValidator } from'./custom-validator';import { menAgeCrossValidation } from'./cross-validator';import { DikeGridProperties } from'app/core/config/dike-grid.properties';import { Employee } from'app/mock-api/common/employees/data.model';import { SampleData } from'app/services/sample-data.service';import { DikeGridConfig } from'app/services/dike-grid.config.service';@Component({ selector:'edition-validation', templateUrl:'./edition-validation.component.html', styleUrls: ['./edition-validation.component.scss'], encapsulation:ViewEncapsulation.None, changeDetection:ChangeDetectionStrategy.OnPush})exportclassEditionValidationComponentimplementsOnInit,OnDestroy {// Retrieve the DikeGridComponent<T> instance from the view: @ViewChild('grid') dikeGrid:DikeGridComponent<Employee>; dkgDataSource:DikeGridDataSourceInput<Employee>; gridProperties:DikeGridProperties; nameEditionSettings:EditionFieldSettings; lastNameEditionSettings:EditionFieldSettings; genderEditionSettings:EditionFieldSettings; ageEditionSettings:EditionFieldSettings; emailEditionSettings:EditionFieldSettings; hireDateEditionSettings:EditionFieldSettings; gridEditionSettings:DikeGridEditionSettings<Employee>;private changeGridPropertiesSubscription:Subscription=Subscription.EMPTY;constructor(private cdr:ChangeDetectorRef,private gridConfig:DikeGridConfig,private sampleData:SampleData) { }ngOnInit():void {// Get 1000 entries from the REST API:this.dkgDataSource =this.sampleData.getEmployees(1000);// Listening to any config property change:this.setChangeGridPropertiesSubscription();// Define the column edition settings:this.nameEditionSettings = { required:true, validators: [ Validators.maxLength(12),forbiddenNameValidator(/erick/i) ], errorMessages:newCustomErrorMessage().addMessage(ErrorType.MAX_LENGTH,'The Name column maximum length must be 12.').addMessage('forbiddenName','The Name column can not contain the erick string.') };this.lastNameEditionSettings = { required:true, validators: [ Validators.maxLength(15) ], errorMessages:newCustomErrorMessage().addMessage(ErrorType.MAX_LENGTH,'The Surname column maximum length must be 15.') };this.genderEditionSettings = { required:true, options: [ { label:'M', value:'male' }, { label:'F', value:'female' } ] };this.ageEditionSettings = { required:true, validators: [ Validators.min(22),Validators.max(60) ], errorMessages:newCustomErrorMessage().addMessage(ErrorType.REQUIRED,'You must enter the age of the employee.').addMessage(ErrorType.MIN,'The minimum age to be hired must be 22 years old.').addMessage(ErrorType.MAX,'The maximum age to be hired must be 60 years old.') };this.emailEditionSettings = { required:true };this.hireDateEditionSettings = { required:true };this.gridEditionSettings = { crossFieldValidation: { customValidator: menAgeCrossValidation, errorMessages:newCustomErrorMessage().addMessage('menAge','The minimum age for men is 36 years old.') } }; }ngOnDestroy():void {this.changeGridPropertiesSubscription.unsubscribe(); }onEmailValidator():void {constemailColumn=this.dikeGrid.columnDef.findColumn(column =>column.fieldName ==='email');// The column exists and is a data column:if (!!emailColumn &&isDikeDataColumnDef(emailColumn)) {// Assign the email validator:this.dikeGrid.columnDef.setColumnEditable(emailColumn,true, { editionSettings: { ...this.emailEditionSettings, validators: [ Validators.email ] } }); } }onHireDateValidator():void {consthireDateColumn=this.dikeGrid.columnDef.findColumn(column =>column.fieldName ==='hireDate');// The column exists and is a data column:if (!!hireDateColumn &&isDikeDataColumnDef(hireDateColumn)) {consttoday=newDate();// Assign the custom limitHireDateValidator validator:this.dikeGrid.columnDef.setColumnEditable(hireDateColumn,true, { editionSettings: {...this.hireDateEditionSettings, validators: [ limitHireDateValidator(newDate(today.getFullYear(),today.getMonth(),today.getDate())) ], errorMessages:newCustomErrorMessage().addMessage('limitHireDate','You can not enter a date after today.') } }); } }privatesetChangeGridPropertiesSubscription():void {this.changeGridPropertiesSubscription.unsubscribe();this.changeGridPropertiesSubscription =this.gridConfig.configChange.subscribe((props:DikeGridProperties) => {this.gridProperties = props;this.cdr.markForCheck(); }); }}
import { AbstractControl, ValidationErrors, ValidatorFn } from'@angular/forms';exportconstforbiddenNameValidator= (nameRe:RegExp):ValidatorFn=> (control:AbstractControl):ValidationErrors|null=>nameRe.test(control.value) ? { forbiddenName:true } :null;exportconstlimitHireDateValidator= (limitDate:Date):ValidatorFn=> (control:AbstractControl):ValidationErrors|null=> {if (!(control.value instanceofDate)) {returnnull; }// Get the Date from the input:constcontrolDate=control.value asDate;returncontrolDate.getTime() >limitDate.getTime() ? { limitHireDate:true } :null; };
import { AbstractControl, ValidationErrors, ValidatorFn } from'@angular/forms';import { DikeGridColumnDef } from'@dikesoft/angular-data-grid';import { Employee } from'app/mock-api/common/employees/data.model';// This function validates that men must be older than 35 years old:exportconstmenAgeCrossValidation= (columnDef:DikeGridColumnDef<Employee>):ValidatorFn=> {// Get the desired columns:constgenderColumn=columnDef.findColumn(column =>column.fieldName ==='gender');constageColumn=columnDef.findColumn(column =>column.fieldName ==='age');// This validator function is attached to the row's FormGroup:return (rowGroup:AbstractControl):ValidationErrors|null=> {// If the columns can not be found, there is no error:if (!genderColumn ||!ageColumn) {returnnull; }// Get the controls from the corresponding FormGroup.// The slotId property identifies each column control inside the corresponding FormGroup:constgenderControl=rowGroup.get(genderColumn.panel)?.get(genderColumn.slotId);constageControl=rowGroup.get(ageColumn.panel)?.get(ageColumn.slotId);// If the controls can not be found, there is no error:if (!genderControl ||!ageControl) {returnnull; }returngenderControl.value asstring === 'male'&&ageControl.value asnumber<=35? { menAge:true } :null; };};