La edición de la cuadrícula expone un mecanismo de validación integrado de la entrada del usuario al editar celdas o filas. Amplía la funcionalidad de validación de Angular Form para permitir una integración más sencilla con una funcionalidad conocida. Cuando cambia el estado del editor, se aplican indicadores visuales a la celda editada.
最速で機能豊富な Angular Data Grid は、ページング、ソート、フィルタリング、グループ化、PDF および Excel へのエクスポートなどの機能を提供します。究極のアプリ構築エクスペリエンスとデータ操作に必要なすべてが揃っています。
Configurar mediante configuración basada en plantillas
Ampliamos algunas de las directivas de validación Angular Forms para que funcionen directamente con IgxColumn. Los mismos validadores están disponibles como atributos que se pueden configurar de forma declarativa en igx-column. Los siguientes validadores son compatibles de manera predeterminada:
requerido
mín.
máximo
correo electrónico
longitud mínima
longitud máxima
patrón
Para validar que se establecerá una entrada de columna y que el valor se formateará como un correo electrónico, puede utilizar las directivas relacionadas:
¿Te gusta esta muestra? Obtenga acceso a nuestro kit de herramientas de Ignite UI for Angular completo y comience a crear sus propias aplicaciones en minutos. Descárgalo gratis.
Configurar mediante formularios reactivos
Exponemos el FormGroup que se utilizará para la validación cuando comience la edición en una fila/celda a través de un evento formGroupCreated. Puedes modificarlo agregando tus propios validadores para los campos relacionados:
La cuadrícula expone un servicio de validación a través de la propiedad validation. Ese servicio tiene las siguientes API públicas:
valid: devuelve si el estado de validación de la cuadrícula es válido.
getInvalid: devuelve registros con estados no válidos.
clear: borra el estado para el registro por identificación o borra todos los estados si no se proporciona ninguna identificación.
markAsTouched: marca el registro/campo relacionado como tocado.
Los estados no válidos persistirán hasta que los errores de validación que contengan se corrijan de acuerdo con la regla de validación o se borren.
Activadores de validación
La validación se activará en los siguientes escenarios:
Mientras se edita a través del editor de celdas según el validationTrigger de la cuadrícula. Ya sea al change mientras se escribe en el editor, o al blur cuando el editor pierde el foco o se cierra.
Cuando se utiliza la edición por lotes y la API undo / redo del servicio de transacciones.
Nota: La validación no se activará para los registros que no se hayan editado mediante la entrada del usuario o mediante la API de edición. Los indicadores visuales en la celda solo se mostrarán si la entrada relacionada se considera tocada, ya sea mediante la interacción del usuario o mediante la API markAsTouched del servicio de validación.
Opciones de personalización de la validación de cuadrícula Angular
Establecer un validador personalizado
Puede definir su propia directiva de validación para usar en una <igx-column> en la plantilla.
Una vez que esté definido y agregado en el módulo de su aplicación, puede configurarlo de forma declarativa en una columna determinada en la cuadrícula:
Puede definir su propia plantilla de error personalizada que se mostrará en la información sobre herramientas de error cuando la celda entre en un estado no válido. Esto es útil en escenarios en los que desea agregar su propio mensaje de error personalizado o cambiar la apariencia o el contenido del mensaje.
<igx-column... ><ng-templateigxCellValidationErrorlet-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>html
Evitar salir del modo de edición en estado no válido
En algunos casos, es posible que desee impedir el envío de un valor no válido en los datos. En esos escenarios, puede usar los eventos cellEdit o rowEdit y cancelar el evento en caso de que el nuevo valor no sea válido. Los argumentos de ambos eventos tienen una propiedad valid y pueden cancelarse en consecuencia. Cómo se utiliza se puede ver en el ejemplo de Validación entre campos.
<divclass="grid-wrapper"><igx-grid #grid1 [data]="data" [width]="'100%'" [height]="'480px'" [autoGenerate]="false" [batchEditing]="true"
[primaryKey]="'id'" (formGroupCreated)='formCreateHandler($event)'><igx-columnfield="Avatar"header="Photo"dataType="string"width="80" [editable]="false"><ng-templateigxCelllet-cell="cell"><divclass="cell__inner avatar-cell"><igx-avatar [src]="cell.row.data.avatar"shape="circle"size="small"></igx-avatar></div></ng-template></igx-column><igx-columnfield="name"header="Name" [editable]="true"required></igx-column><igx-columnfield="company"header="Company" [editable]="true"></igx-column><igx-columnfield="email"width="190"header="Email" [editable]="true"requiredemail></igx-column><igx-columnfield="fax"header="Phone" [editable]="true"phoneFormat="\+\d{1}\-(?!0)(\d{3})\-(\d{3})\-(\d{4})\b"><ng-templateigxCellValidationErrorlet-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><igx-columnfield="created_on"header="Date of Registration"width="170" [editable]="true" [dataType]="'date'"required><ng-templateigxCellValidationErrorlet-cell='cell'let-defaultErr='defaultErrorTemplate'><ng-container *ngTemplateOutlet="defaultErr" ></ng-container><div *ngIf="cell.validation.errors?.['futureDate']">
The date cannot be in the future.
</div></ng-template><ng-templateigxCelllet-cell>
{{ cell | date: 'longDate' }}
</ng-template></igx-column><igx-columnfield="last_activity"header="Last Active"width="170" [editable]="true" [dataType]="'date'"required><ng-templateigxCelllet-cell>
{{ cell | date: 'longDate' }}
</ng-template><ng-templateigxCellValidationErrorlet-cell='cell'let-defaultErr="defaultErrorTemplate"><ng-container *ngTemplateOutlet="defaultErr"></ng-container><div *ngIf="cell.validation.errors?.['futureDate']">
The date cannot be in the future.
</div><div *ngIf="cell.validation.errors?.['pastDate']">
The date cannot be before the 5th of November 2010
</div></ng-template></igx-column><igx-columnfield="estimated_sales"header="Estimated Sales" [editable]="true" [dataType]="'number'"requiredmin="0"></igx-column><igx-columnfield="deals_lost"header="Deals Lost" [editable]="true" [dataType]="'number'"requiredmin="0"></igx-column><igx-columnfield="deals_won"header="Deals Won" [editable]="true" [dataType]="'number'"requiredmin="0"></igx-column><igx-columnfield="deals_pending"header="Deals Pending" [editable]="true" [dataType]="'number'"requiredmin="0"></igx-column></igx-grid></div><divclass="buttons-wrapper"><buttonigxButton [disabled]="!grid1.transactions.canUndo" (click)="undo()">Undo</button><buttonigxButton [disabled]="!grid1.transactions.canRedo" (click)="redo()">Redo</button><buttonigxButton [disabled]="grid1.transactions.getAggregatedChanges(false).length < 1" (click)="commit()">Commit</button></div>html
En algunos escenarios, la validación de un campo puede depender del valor de otro campo en el registro. En ese caso, se puede utilizar un validador personalizado para comparar los valores en el registro a través de su FormGroup compartido.
El siguiente ejemplo demuestra una validación entre campos entre diferentes campos del mismo registro. Comprueba la validez de las fechas en comparación con la fecha actual y entre la fecha activa y la de creación del registro, así como la proporción de transacciones ganadas/perdidas para cada empleado. Todos los errores se recopilan en una columna fijada separada que muestra que el registro no es válido y muestra los errores relacionados.
Las siguientes líneas de código muestran la función de validación de campos cruzados, que contiene las comparaciones y establece los errores relacionados con ellas.
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 datesconst curDate = newDate();
if (newDate(createdOnRecord.value) > curDate) {
// The created on date shouldn't be greater than current date.
returnObject['createdInvalid'] = true;
}
if (newDate(lastActiveRecord.value) > curDate) {
// The last active date shouldn't be greater than current date.
returnObject['lastActiveInvalid'] = true;
}
if (newDate(createdOnRecord.value) > newDate(lastActiveRecord.value)) {
// The created on date shouldn't be greater than last active date.
returnObject['createdLastActiveInvalid'] = true;
}
// Validate dealsconst 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;
};
}
publiccalculateDealsRatio(dealsWon, dealsLost) {
if (dealsLost === 0) return dealsWon + 1;
returnMath.round(dealsWon / dealsLost * 100) / 100;
}
ts
El validador de campos cruzados se puede agregar al formGroup de la fila del evento formGroupCreated, que devuelve el nuevo formGroup para cada fila al ingresar al modo de edición:
Los diferentes errores se muestran en una celda con plantilla que combina todos los errores en una única información sobre herramientas. Dependiendo del estado válido de la fila, se muestra un icono diferente:
Los mensajes de error se recopilan en la función stateMessage, que recopila los errores de cada celda, porque cada columna podría tener validaciones de formularios con plantilla y luego verifica los errores de la fila misma, que provienen del rowValidator personalizado.
publicstateMessage(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;
}
typescript
Ejemplo de campo cruzado
El siguiente ejemplo demuestra la validación entre campos en acción.
EXAMPLE
TS
HTML
SCSS
import { Component, ViewChild } from'@angular/core';
import { AbstractControl, FormGroup, ValidationErrors, ValidatorFn, FormsModule } from'@angular/forms';
import { CellType, IgxGridComponent, IGridEditEventArgs, IgxSwitchComponent, IgxColumnComponent, IgxCellTemplateDirective, IgxAvatarComponent, IgxColumnRequiredValidatorDirective, IgxColumnEmailValidatorDirective, IgxCellValidationErrorDirective, IgxColumnMinValidatorDirective, IgxTooltipTargetDirective, IgxTooltipDirective, IgxButtonDirective } from'igniteui-angular';
import { IGridFormGroupCreatedEventArgs } from'igniteui-angular/lib/grids/common/grid.interface';
import { employeesData } from'../../data/employeesData';
import { NgTemplateOutlet, NgIf, NgFor, DatePipe } from'@angular/common';
@Component({
selector: 'app-grid-validator-service-cross-field',
styleUrls: ['./grid-validator-service-cross-field.component.scss'],
templateUrl: './grid-validator-service-cross-field.component.html',
imports: [IgxSwitchComponent, FormsModule, IgxGridComponent, IgxColumnComponent, IgxCellTemplateDirective, IgxAvatarComponent, IgxColumnRequiredValidatorDirective, IgxColumnEmailValidatorDirective, IgxCellValidationErrorDirective, NgTemplateOutlet, NgIf, IgxColumnMinValidatorDirective, IgxTooltipTargetDirective, IgxTooltipDirective, NgFor, IgxButtonDirective, DatePipe]
})
exportclassGridValidatorServiceCrossFieldComponent{
@ViewChild('grid1', { read: IgxGridComponent })
public grid: IgxGridComponent;
public transactionData = JSON.parse(JSON.stringify(employeesData));
public rowEdit: boolean = true;
publicformCreateHandler(evt: IGridFormGroupCreatedEventArgs) {
const createdOnRecord = evt.formGroup.get('created_on');
const lastActiveRecord = evt.formGroup.get('last_activity');
createdOnRecord.addValidators(this.futureDateValidator());
lastActiveRecord.addValidators(this.futureDateValidator());
evt.formGroup.addValidators(this.rowValidator());
}
publiceditHandler(event: IGridEditEventArgs) {
if (!event.valid) {
event.cancel = true;
}
}
publiccommit() {
const invalidTransactions = this.grid.validation.getInvalid();
if (invalidTransactions.length > 0 && !confirm('You\'re committing invalid transactions. Are you sure?')) {
return;
}
this.grid.transactions.commit(this.transactionData);
this.grid.validation.clear();
}
publiccalculateDealsRatio(dealsWon, dealsLost) {
if (dealsLost === 0) return dealsWon + 1;
returnMath.round(dealsWon / dealsLost * 100) / 100;
}
publicgetDealsRatio(cell: CellType) {
const dealsWon = cell.row.cells.find(c => c.column.field === 'deals_won');
const dealsLost = cell.row.cells.find(c => c.column.field === 'deals_lost');
const dealsWonValue = dealsWon.editValue != null ? dealsWon.editValue : dealsWon.value;
const dealsLostValue = dealsLost.editValue != null ? dealsLost.editValue : dealsLost.value;
returnthis.calculateDealsRatio(dealsWonValue, dealsLostValue);
}
private futureDateValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const date = control.value;
if(date > newDate()){
return { beyondThreshold: { value: control.value } };
}
returnnull;
}
}
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 datesconst curDate = newDate();
if (newDate(createdOnRecord.value) > curDate) {
// The created on date shouldn't be greater than current date.
returnObject['createdInvalid'] = true;
}
if (newDate(lastActiveRecord.value) > curDate) {
// The last active date shouldn't be greater than current date.
returnObject['lastActiveInvalid'] = true;
}
if (newDate(createdOnRecord.value) > newDate(lastActiveRecord.value)) {
// The created on date shouldn't be greater than last active date.
returnObject['createdLastActiveInvalid'] = true;
}
// Validate dealsconst dealsRatio = this.calculateDealsRatio(winControl.value, loseControl.value);
if (actualSalesControl.value === 0 && dealsRatio > 0) {
returnObject['salesZero'] = true;
}
if (actualSalesControl.value > 0 && dealsRatio === 0) {
returnObject['salesNotZero'] = true;
}
return returnObject;
};
}
publicisRowValid(cell: CellType) {
return !cell.row.validation.errors && !cell.row.cells.some(c => !!c.validation.errors);
}
publicstateMessage(cell: CellType) {
const messages = [];
const cellValidationErrors = cell.row.cells.filter(x => !!x.validation.errors);
cellValidationErrors.forEach(cell => {
const cellErrors = cell.validation.errors;
if (cellErrors?.required) {
messages.push(`The \`${cell.column.header}\` column is required.`);
}
if (cellErrors?.min) {
messages.push(`A value of at least ${cellErrors.min.min} should be entered for \`${cell.column.header}\` column.`);
}
if (cellErrors?.email) {
messages.push(`Please enter a valid email for \`${cell.column.header}\` column.`);
}
});
const rowErrors = cell.row.validation.errors;
if (rowErrors?.createdInvalid) {
messages.push(`The \`Date of Registration\` date cannot be in the future.`);
}
if (rowErrors?.lastActiveInvalid) {
messages.push(`The \`Last Active\` date cannot be in the future.`);
}
if (rowErrors?.createdLastActiveInvalid) {
messages.push(`The \`Date of Registration\` cannot be greater than the \`Last Active\` date.`);
}
if (rowErrors?.salesZero) {
messages.push(`The \`Actual Sales\` cannot be 0 when the deals ratio is greater than 0.`);
}
if (rowErrors?.salesNotZero) {
messages.push(`The \`Actual Sales\` cannot be greater than 0 when the deals ratio is 0.`);
}
if (messages.length === 0 && this.isRowValid(cell)) {
messages.push('OK');
}
return messages;
}
}
ts
<divclass="top-row"><igx-switch [(ngModel)]="rowEdit">Row edit</igx-switch></div><divclass="grid__wrapper"><igx-grid #grid1 [data]="transactionData" [width]="'100%'" [height]="'500px'" [autoGenerate]="false" [batchEditing]="true" [rowEditable]="rowEdit"
[primaryKey]="'id'" (formGroupCreated)="formCreateHandler($event)" (cellEdit)="editHandler($event)" (rowEdit)="editHandler($event)"><igx-columnfield="Avatar"header="Photo"dataType="string"width="80" [editable]="false"><ng-templateigxCelllet-cell="cell"><divclass="cell__inner avatar-cell"><igx-avatar [src]="cell.row.data.avatar"shape="circle"size="small"></igx-avatar></div></ng-template></igx-column><igx-columnfield="name"header="Name" [editable]="true"required></igx-column><igx-columnfield="company"header="Company" [editable]="true"></igx-column><igx-columnfield="country"header="Country" [editable]="true"></igx-column><igx-columnfield="city"header="City" [editable]="true"></igx-column><igx-columnfield="email"width="190"header="Email" [editable]="true"requiredemail></igx-column><igx-columnfield="created_on"header="Date of Registration"width="170" [editable]="true" [dataType]="'date'"required><ng-templateigxCellValidationErrorlet-cell='cell'let-defaultErr='defaultErrorTemplate'><ng-container *ngTemplateOutlet="defaultErr" ></ng-container><div *ngIf="cell.validation.errors?.['beyondThreshold']">
The date cannot be in the future.
</div></ng-template><ng-templateigxCelllet-cell>
{{ cell | date: 'longDate' }}
</ng-template></igx-column><igx-columnfield="last_activity"header="Last Active"width="170" [editable]="true" [dataType]="'date'"required><ng-templateigxCelllet-cell>
{{ cell | date: 'longDate' }}
</ng-template><ng-templateigxCellValidationErrorlet-cell='cell'let-defaultErr='defaultErrorTemplate'><ng-container *ngTemplateOutlet="defaultErr"></ng-container><div *ngIf="cell.validation.errors?.['beyondThreshold']">
The date cannot be in the future.
</div></ng-template></igx-column><igx-columnfield="estimated_sales"header="Estimated Sales" [editable]="true" [dataType]="'number'"requiredmin="0"></igx-column><igx-columnfield="actual_sales"header="Actual Sales" [editable]="true" [dataType]="'number'"requiredmin="0"></igx-column><igx-columnfield="deals_lost"header="Deals Lost" [editable]="true" [dataType]="'number'"requiredmin="0"></igx-column><igx-columnfield="deals_won"header="Deals Won" [editable]="true" [dataType]="'number'"requiredmin="0"></igx-column><igx-columnfield="deals_ratio"header="Deals Ratio" [editable]="false" [dataType]="'number'" ><ng-templateigxCelllet-cell="cell">
{{ getDealsRatio(cell) }}
</ng-template></igx-column><igx-columnfield="row_valid"header=" " [editable]="false" [pinned]="true" [width]="'50px'"><ng-templateigxCelllet-cell="cell"><div *ngIf="isRowValid(cell)" [igxTooltipTarget]="tooltipRef"class="valid-image"><imgwidth="18"src="https://www.infragistics.com/angular-demos-lob/assets/images/grid/active.png"/></div><div *ngIf="!isRowValid(cell)" [igxTooltipTarget]="tooltipRef"class="valid-image"><imgwidth="18"src="https://www.infragistics.com/angular-demos-lob/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></igx-grid><divclass="buttons-wrapper"><buttonigxButton [disabled]="grid1.transactions.getAggregatedChanges(false).length < 1" (click)="commit()">Commit</button></div></div>html
En el siguiente ejemplo, utilizaremos la plantilla expuesta para el mensaje de validación, que aparece en una información sobre herramientas y anula el color del error para modificar la apariencia predeterminada de la validación. También aplicaremos estilo al fondo de las filas no válidas para hacerlas más distintas.
Importar tema
La forma más fácil de diseñar y acceder a las variables css es definir estilos en nuestro app archivo de estilo global (típicamente styles.scss). Lo primero que tenemos que hacer es importar el themes/index archivo, esto nos da acceso a todas las potentes herramientas del framework Ignite UI for Angular Sass:
@use"igniteui-angular/theming" as *;
// IMPORTANT: Prior to Ignite UI for Angular version 13 use:// @import '~igniteui-angular/lib/core/styles/themes/index';scss
Incluir los estilos
Para cambiar el color del error puedes usar la variable css--igx-error-500:
--igx-error-500: 34, 80%, 63%;
scss
Plantillas personalizadas
Cambiar la plantilla de error predeterminada permite configurar clases y estilos personalizados:
CuandovalidationTrigger es borroso,editValue y la validación se activará solo después de que el editor esté borroso.
La razón es que esto utiliza el formControlupdateOn propiedad. Esto determina el evento en el que formControl se actualizará y activará los validadores relacionados.