Angular Grid の編集と検証
Grid の編集は、セル/行の編集時のユーザー入力の組み込み検証メカニズムを公開します。これは Angular Form 検証 機能を拡張し、既知の機能と簡単に統合できるようにします。エディターの状態が変更されると、視覚的なインジケーターが編集されたセルに適用されます。
構成
テンプレート駆動で構成する
Angular Forms 検証ディレクティブは、IgxColumn
で直接動作するよう拡張されています。同じ検証が igx-column
で宣言的に設定される属性として利用できます。以下の検証は追加設定なしでサポートされます。
required
min
max
email
minlength
maxlength
pattern
列入力が設定され、値がメールとして書式設定されることを検証するには、関連するディレクティブを使用できます。
<igx-column [field ]="email" [header ]="User E-mail" required email > </igx-column >
html
以下のサンプルは、Grid に組み込み済みの required
、email
および min
検証ディレクティブを使用する方法を示しています。
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import { GridValidatorServiceComponent } from "./grid/grid-validator-service/grid-validator-service.component" ;
import {
IgxGridModule,
IgxFocusModule,
IgxSwitchModule
} from "igniteui-angular" ;
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
GridValidatorServiceComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxGridModule,
IgxFocusModule,
IgxSwitchModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { Component } from '@angular/core' ;
import { employeesData } from '../../data/employeesData' ;
@Component ({
selector : 'app-grid-validator-service' ,
styleUrls : ['./grid-validator-service.component.scss' ],
templateUrl : './grid-validator-service.component.html'
})
export class GridValidatorServiceComponent {
public data: any [];
public employeesData: any [];
public rowEdit: boolean = true ;
constructor ( ) {
this .data = employeesData;
}
}
ts コピー <div class ="top-row" >
<igx-switch [(ngModel )]="rowEdit" > Row edit</igx-switch >
</div >
<div class ="grid-wrapper" >
<igx-grid #grid1 [data ]="data" [width ]="'100%'" [height ]="'480px'" [autoGenerate ]="false" [batchEditing ]="true"
[rowEditable ]="rowEdit" [primaryKey ]="'id'" >
<igx-column field ="Avatar" header ="Photo" dataType ="string" width ="80" [editable ]="false" >
<ng-template igxCell let-cell ="cell" >
<div >
<igx-avatar [src ]="cell.row.data.avatar" [roundShape ]="true" size ="small" > </igx-avatar >
</div >
</ng-template >
</igx-column >
<igx-column field ="name" header ="Name" [editable ]="true" required > </igx-column >
<igx-column field ="company" header ="Company" [editable ]="true" > </igx-column >
<igx-column field ="email" width ="190" header ="Email" [editable ]="true" required email > </igx-column >
<igx-column field ="fax" header ="Phone" [editable ]="true" > </igx-column >
<igx-column field ="created_on" header ="Date of Registration" width ="170" [editable ]="true"
[dataType ]="'date'" required >
<ng-template igxCell let-cell >
{{ cell | date: 'longDate' }}
</ng-template >
</igx-column >
<igx-column field ="last_activity" header ="Last Active" width ="170" [editable ]="true" [dataType ]="'date'"
required >
<ng-template igxCell let-cell >
{{ cell | date: 'longDate' }}
</ng-template >
</igx-column >
<igx-column field ="estimated_sales" header ="Estimated Sales" [editable ]="true" [dataType ]="'number'" required min ="0" > </igx-column >
<igx-column field ="deals_lost" header ="Deals Lost" [editable ]="true" [dataType ]="'number'" required min ="0" >
</igx-column >
<igx-column field ="deals_won" header ="Deals Won" [editable ]="true" [dataType ]="'number'" required min ="0" >
</igx-column >
<igx-column field ="deals_pending" header ="Deals Pending" [editable ]="true" [dataType ]="'number'" required
min ="0" > </igx-column >
</igx-grid >
</div >
html コピー .top-row ,
.grid-wrapper {
padding : 16px ;
}
.grid-wrapper {
margin : 0 auto;
padding : 16px ;
}
scss コピー
このサンプルが気に入りましたか? 完全な Ignite UI for Angularツールキットにアクセスして、すばやく独自のアプリの作成を開始します。無料でダウンロードできます。
リアクティブ フォームで構成する
formGroupCreated
イベントを介して行/セルで編集を開始するときに検証に使用する FormGroup
を公開します。関連するフィールドに独自の検証を追加して変更できます。
<igx-grid (formGroupCreated )='formCreateHandler($event)' ... >
html
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());
}
ts
独自の検証関数を作成するか、組み込みの Angular 検証関数 を使用できます。
検証サービス API
グリッドは、validation
プロパティを介して検証サービスを公開します。
このサービスには以下のパブリック API があります。
valid
- グリッドの検証状態が有効であるかどうかを返します。
getInvalid
- 無効な状態のレコードを返します。
clear
- レコードの状態を ID でクリアします。ID が提供されない場合はすべてのレコードの状態をクリアします。
markAsTouched
- 関連するレコード/フィールドをタッチ済みとしてマークします。
無効な状態は、検証ルールに従って検証エラーが修正されるか、クリアされるまで保持されます。
検証トリガー
検証は以下のシナリオでトリガーされます。
注: ユーザー入力または編集 API で編集されていないレコードに対しては、検証はトリガーされません。セルの視覚的なインジケーターは、ユーザー操作または検証サービスの markAsTouched
API を介して入力がタッチ済みと見なされる場合のみ表示されます。
Angular Grid 検証のカスタマイズ オプション
カスタム検証を設定する
テンプレート内の <igx-column>
で使用する独自の検証ディレクティブを定義することができます。
@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 ;
}
}
ts
定義して app モジュールに追加した以降、宣言的にグリッドの指定の列に設定できます。
<igx-column phoneFormat ="\+\d{1}\-(?!0)(\d{3})\-(\d{3})\-(\d{4})\b" ... >
html
デフォルトのエラー テンプレートを変更する
セルが無効な状態になったときにエラー ツールチップに表示されるカスタム エラー テンプレートを定義できます。
これは、カスタム エラー メッセージを追加したり、メッセージの外観やコンテンツを変更したりする場合に便利です。
<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 >
html
無効な状態での編集モードの終了を防止する
場合によっては、データ中の無効な値を送信しないようにしたいことがあります。
その場合は、cellEdit
または rowEdit
イベントを使用し、新しい値が無効な場合にイベントをキャンセルできます。
いずれのイベントも引数には valid
プロパティがあり、これによってキャンセルできます。その使用方法は、クロス フィールド検証の例 で確認できます。
<igx-grid (cellEdit )='cellEdit($event)' ... >
html
public cellEdit (evt ) {
if (!evt.valid) {
evt.cancel = true ;
}
}
ts
例
以下の例は、上記のカスタマイズ オプションを示しています。
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import { GridValidatorServiceExtendedComponent } from "./grid/grid-validator-service-extended/grid-validator-service-extended.component" ;
import {
IgxGridModule,
IgxFocusModule
} from "igniteui-angular" ;
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
GridValidatorServiceExtendedComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxGridModule,
IgxFocusModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { Component, Directive, Input, ViewChild } from '@angular/core' ;
import { AbstractControl, FormGroup, NG_VALIDATORS, ValidationErrors, ValidatorFn, Validators } from '@angular/forms' ;
import { IgxGridComponent } from 'igniteui-angular' ;
import { IGridFormGroupCreatedEventArgs } from 'igniteui-angular/lib/grids/common/grid.interface' ;
import { employeesData } from '../../data/employeesData' ;
export function phoneFormatValidator (phoneReg: RegExp ): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const match = phoneReg.test(control.value);
return match ? null : { phoneFormat : { value : control.value } } ;
}
}
@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 ;
}
}
@Component ({
selector : 'app-grid-validator-service-extended' ,
styleUrls : ['./grid-validator-service-extended.component.scss' ],
templateUrl : './grid-validator-service-extended.component.html'
})
export class GridValidatorServiceExtendedComponent {
@ViewChild ('grid1' , { read : IgxGridComponent })
public grid: IgxGridComponent;
public data = employeesData;
public formCreateHandler (formGroupArgs: IGridFormGroupCreatedEventArgs ) {
const createdOnRecord = formGroupArgs.formGroup.get('created_on' );
const lastActiveRecord = formGroupArgs.formGroup.get('last_activity' );
createdOnRecord.addValidators(this .futureDateValidator());
lastActiveRecord.addValidators([this .pastDateValidator(), this .futureDateValidator()]);
}
public commit ( ) {
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 .data);
this .grid.validation.clear();
}
public undo ( ) {
this .grid.endEdit(true );
this .grid.transactions.undo();
}
public redo ( ) {
this .grid.endEdit(true );
this .grid.transactions.redo();
}
public futureDateValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const date = control.value;
if (date > new Date ()){
return { futureDate : { value : control.value } };
}
return null ;
}
}
public pastDateValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const date = control.value;
let pastDate = new Date ('Nov 5 2010' );
if (pastDate){
return pastDate < date ? null : { pastDate : { value : control.value } }
} else return null ;
}
}
}
ts コピー <div class ="grid-wrapper" >
<igx-grid #grid1 [data ]="data" [width ]="'100%'" [height ]="'480px'" [autoGenerate ]="false" [batchEditing ]="true"
[primaryKey ]="'id'" (formGroupCreated )='formCreateHandler($event)' >
<igx-column field ="Avatar" header ="Photo" dataType ="string" width ="80" [editable ]="false" >
<ng-template igxCell let-cell ="cell" >
<div class ="cell__inner avatar-cell" >
<igx-avatar [src ]="cell.row.data.avatar" [roundShape ]="true" size ="small" > </igx-avatar >
</div >
</ng-template >
</igx-column >
<igx-column field ="name" header ="Name" [editable ]="true" required > </igx-column >
<igx-column field ="company" header ="Company" [editable ]="true" > </igx-column >
<igx-column field ="email" width ="190" header ="Email" [editable ]="true" required email > </igx-column >
<igx-column field ="fax" header ="Phone" [editable ]="true" phoneFormat ="\+\d{1}\-(?!0)(\d{3})\-(\d{3})\-(\d{4})\b" >
<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 >
<igx-column field ="created_on" header ="Date of Registration" width ="170" [editable ]="true" [dataType ]="'date'" required >
<ng-template igxCellValidationError let-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-template igxCell let-cell >
{{ cell | date: 'longDate' }}
</ng-template >
</igx-column >
<igx-column field ="last_activity" header ="Last Active" width ="170" [editable ]="true" [dataType ]="'date'" required >
<ng-template igxCell let-cell >
{{ cell | date: 'longDate' }}
</ng-template >
<ng-template igxCellValidationError let-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-column field ="estimated_sales" header ="Estimated Sales" [editable ]="true" [dataType ]="'number'" required min ="0" >
</igx-column >
<igx-column field ="deals_lost" header ="Deals Lost" [editable ]="true" [dataType ]="'number'" required min ="0" >
</igx-column >
<igx-column field ="deals_won" header ="Deals Won" [editable ]="true" [dataType ]="'number'" required min ="0" >
</igx-column >
<igx-column field ="deals_pending" header ="Deals Pending" [editable ]="true" [dataType ]="'number'" required min ="0" >
</igx-column >
</igx-grid >
</div >
<div class ="buttons-wrapper" >
<button igxButton [disabled ]="!grid1.transactions.canUndo" (click )="undo()" > Undo</button >
<button igxButton [disabled ]="!grid1.transactions.canRedo" (click )="redo()" > Redo</button >
<button igxButton [disabled ]="grid1.transactions.getAggregatedChanges(false).length < 1" (click )="commit()" > Commit</button >
</div >
html コピー .grid-wrapper {
margin : 0 auto;
padding : 16px ;
}
.buttons-wrapper {
display : flex;
flex-direction : row;
justify-content : left;
padding : 10px 0 ;
}
scss コピー
クロス フィールド検証
場合によっては、1 つのフィールドの検証がレコード内の別のフィールドの値に依存することがあります。
その場合、カスタム検証を使用して共有 FormGroup
を介してレコード内の値を比較できます。
以下のサンプルは、同じレコードの異なるフィールド間のクロスフィールド検証を示しています。レコードのアクティブな日付と作成日付とを現在の日付と比較した有効性、および各従業員の商談成立/失効を確認します。すべてのエラーは別のピン固定列に収集され、レコードが無効であることを示し、関連するエラーを表示します。
次のコード行は、比較を含み、それらに関連する関連エラーを設定するクロス フィールド検証関数を示しています。
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' );
const curDate = new Date ();
if (new Date (createdOnRecord.value) > curDate) {
returnObject['createdInvalid' ] = true ;
}
if (new Date (lastActiveRecord.value) > curDate) {
returnObject['lastActiveInvalid' ] = true ;
}
if (new Date (createdOnRecord.value) > new Date (lastActiveRecord.value)) {
returnObject['createdLastActiveInvalid' ] = true ;
}
const 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;
};
}
public calculateDealsRatio (dealsWon, dealsLost ) {
if (dealsLost === 0 ) return dealsWon + 1 ;
return Math .round(dealsWon / dealsLost * 100 ) / 100 ;
}
ts
クロス フィールド検証は、編集モードに入ったときに各行の新しい formGroup
を返す formGroupCreated
イベントから、その行の formGroup
に追加することができます。
<igx-grid #grid1 [data ]="transactionData" [width ]="'100%'" [height ]="'480px'" [autoGenerate ]="false"
[batchEditing ]="true" [rowEditable ]="true" [primaryKey ]="'id'"
(formGroupCreated )='formCreateHandler($event)' >
</igx-grid >
html
public formCreateHandler (evt: IGridFormGroupCreatedEventArgs ) {
evt.formGroup.addValidators(this .rowValidator());
}
typescript
異なるエラーはテンプレート セルに表示され、すべてのエラーは一つのツールチップに結合されます。行の有効状態に応じて、異なるアイコンが表示されます。
<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 >
html
各列にはテンプレート化されたフォーム検証があり、カスタム rowValidator
によって行ごとのエラーを確認するため、エラー メッセージ は各セルのエラーを収集する stateMessage
関数で収集されます。
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.` );
}
}
});
if (row.validation.errors?.createdInvalid) {
messages.push(`The \`Date of Registration\` date cannot be in the future.` );
}
return messages;
}
typescript
クロス フィールドの例
以下のサンプルは、クロス フィールド検証の動作を示しています。
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import { GridValidatorServiceCrossFieldComponent } from "./grid/grid-validator-service-cross-field/grid-validator-service-cross-field.component" ;
import {
IgxGridModule,
IgxTooltipModule,
IgxFocusModule
} from "igniteui-angular" ;
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
GridValidatorServiceCrossFieldComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxGridModule,
IgxFocusModule,
IgxTooltipModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { Component, ViewChild } from '@angular/core' ;
import { AbstractControl, FormGroup, ValidationErrors, ValidatorFn } from '@angular/forms' ;
import { CellType, IgxGridComponent, IGridEditEventArgs } from 'igniteui-angular' ;
import { IGridFormGroupCreatedEventArgs } from 'igniteui-angular/lib/grids/common/grid.interface' ;
import { employeesData } from '../../data/employeesData' ;
@Component ({
selector : 'app-grid-validator-service-cross-field' ,
styleUrls : ['./grid-validator-service-cross-field.component.scss' ],
templateUrl : './grid-validator-service-cross-field.component.html'
})
export class GridValidatorServiceCrossFieldComponent {
@ViewChild ('grid1' , { read : IgxGridComponent })
public grid: IgxGridComponent;
public transactionData = JSON .parse(JSON .stringify(employeesData));
public rowEdit: boolean = true ;
public formCreateHandler (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());
}
public editHandler (event: IGridEditEventArgs ) {
if (!event.valid) {
event.cancel = true ;
}
}
public commit ( ) {
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();
}
public calculateDealsRatio (dealsWon, dealsLost ) {
if (dealsLost === 0 ) return dealsWon + 1 ;
return Math .round(dealsWon / dealsLost * 100 ) / 100 ;
}
public getDealsRatio (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;
return this .calculateDealsRatio(dealsWonValue, dealsLostValue);
}
private futureDateValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const date = control.value;
if (date > new Date ()){
return { beyondThreshold : { value : control.value } };
}
return null ;
}
}
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' );
const curDate = new Date ();
if (new Date (createdOnRecord.value) > curDate) {
returnObject['createdInvalid' ] = true ;
}
if (new Date (lastActiveRecord.value) > curDate) {
returnObject['lastActiveInvalid' ] = true ;
}
if (new Date (createdOnRecord.value) > new Date (lastActiveRecord.value)) {
returnObject['createdLastActiveInvalid' ] = true ;
}
const 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;
};
}
public isRowValid (cell: CellType ) {
return !cell.row.validation.errors && !cell.row.cells.some(c => !!c.validation.errors);
}
public stateMessage (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 コピー <div class ="top-row" >
<igx-switch [(ngModel )]="rowEdit" > Row edit</igx-switch >
</div >
<div class ="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-column field ="Avatar" header ="Photo" dataType ="string" width ="80" [editable ]="false" >
<ng-template igxCell let-cell ="cell" >
<div class ="cell__inner avatar-cell" >
<igx-avatar [src ]="cell.row.data.avatar" [roundShape ]="true" size ="small" > </igx-avatar >
</div >
</ng-template >
</igx-column >
<igx-column field ="name" header ="Name" [editable ]="true" required > </igx-column >
<igx-column field ="company" header ="Company" [editable ]="true" > </igx-column >
<igx-column field ="country" header ="Country" [editable ]="true" > </igx-column >
<igx-column field ="city" header ="City" [editable ]="true" > </igx-column >
<igx-column field ="email" width ="190" header ="Email" [editable ]="true" required email > </igx-column >
<igx-column field ="created_on" header ="Date of Registration" width ="170" [editable ]="true" [dataType ]="'date'" required >
<ng-template igxCellValidationError let-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-template igxCell let-cell >
{{ cell | date: 'longDate' }}
</ng-template >
</igx-column >
<igx-column field ="last_activity" header ="Last Active" width ="170" [editable ]="true" [dataType ]="'date'" required >
<ng-template igxCell let-cell >
{{ cell | date: 'longDate' }}
</ng-template >
<ng-template igxCellValidationError let-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-column field ="estimated_sales" header ="Estimated Sales" [editable ]="true" [dataType ]="'number'" required min ="0" > </igx-column >
<igx-column field ="actual_sales" header ="Actual Sales" [editable ]="true" [dataType ]="'number'" required min ="0" > </igx-column >
<igx-column field ="deals_lost" header ="Deals Lost" [editable ]="true" [dataType ]="'number'" required min ="0" > </igx-column >
<igx-column field ="deals_won" header ="Deals Won" [editable ]="true" [dataType ]="'number'" required min ="0" > </igx-column >
<igx-column field ="deals_ratio" header ="Deals Ratio" [editable ]="false" [dataType ]="'number'" >
<ng-template igxCell let-cell ="cell" >
{{ getDealsRatio(cell) }}
</ng-template >
</igx-column >
<igx-column field ="row_valid" header =" " [editable ]="false" [pinned ]="true" [width ]="'50px'" >
<ng-template igxCell let-cell ="cell" >
<div *ngIf ="isRowValid(cell)" [igxTooltipTarget ]="tooltipRef" class ="valid-image" >
<img width ="18" src ="https://www.infragistics.com/angular-demos-lob/assets/images/grid/active.png" />
</div >
<div *ngIf ="!isRowValid(cell)" [igxTooltipTarget ]="tooltipRef" class ="valid-image" >
<img width ="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 >
<div class ="buttons-wrapper" >
<button igxButton [disabled ]="grid1.transactions.getAggregatedChanges(false).length < 1" (click )="commit()" > Commit</button >
</div >
</div >
html コピー .top-row , .grid__wrapper {
padding : 16px ;
padding-bottom : 0 ;
}
.valid-image {
margin-left : -5px ;
}
.buttons-wrapper {
display : flex;
flex-direction : row;
justify-content : start;
padding : 10px 0 ;
}
scss コピー
スタイル設定
Ignite UI for Angular テーマ ライブラリ を使用して、編集時のデフォルトの検証スタイルを変更できます。
以下の例では、検証メッセージの公開されたテンプレートを使用します。ツールチップをポップアウトし、および、検証のデフォルトの外観を変更するためにエラー時の色をオーバーライドします。
また、無効な行をより明確にするために背景のスタイルを設定します。
テーマのインポート
スタイルを設定し、css 変数にアクセスする最も簡単な方法は、app
のグローバル スタイル ファイル (通常 は styles.scss
です) でスタイルを定義することです。
はじめに themes/index
ファイルをインポートすることにより、Ignite UI for Angular Sass フレームワークの強力なツールへアクセスできるようになります。
@use "igniteui-angular/theming" as *;
scss
スタイルを含める
エラーの色を変更するには、css 変数 --igx-error-500
を使用します。
--igx-error-500: 34 , 80% , 63% ;
scss
カスタム テンプレート
デフォルトのエラー テンプレートを変更することで、カスタム クラスとスタイルを設定できます。
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ='defaultErrorTemplate' >
<div class ="validator-container" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
</div >
</ng-template >
html
無効な行とセルのスタイル
行とセルは、開発者が行またはセルが無効かどうか、およびアクティブなエラーの種類を知るための API を提供します。
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' ;
}
}
ts
<igx-grid [rowStyles ]="rowStyles" >
<igx-column field ="ReorderLevel" header ="ReorderLever" required [cellClasses ]="cellStyles" >
html
デモ
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import { IgxGridModule } from "igniteui-angular" ;
import { GridValidationStyleComponent } from "./grid/grid-validation-style/grid-validation-style.component" ;
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
GridValidationStyleComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxGridModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { Component, ViewChild } from '@angular/core' ;
import { DATA } from '../../data/nwindData' ;
import { IgxGridComponent, RowType } from 'igniteui-angular' ;
@Component ({
selector : 'app-grid-validation-style' ,
styleUrls : [`grid-validation-style.component.scss` ],
templateUrl : 'grid-validation-style.component.html'
})
export class GridValidationStyleComponent {
@ViewChild ('grid' , { read : IgxGridComponent, static : true }) public grid: IgxGridComponent;
public data: any [];
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' ;
}
}
constructor ( ) {
this .data = DATA;
}
}
ts コピー <div class ="sample-wrapper" >
<igx-grid [igxPreventDocumentScroll ]="true" #grid [data ]="data" [primaryKey ]="'ProductID'" width ="100%" height ="500px"
[rowEditable ]="true" [rowStyles ]="rowStyles" >
<igx-column field ="ProductID" header ="Product ID" > </igx-column >
<igx-column field ="ReorderLevel" header ="ReorderLever" required [cellClasses ]="cellStyles" >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ='defaultErrorTemplate' >
<div class ="validator-container" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
</div >
</ng-template >
</igx-column >
<igx-column field ="ProductName" header ="ProductName" [dataType ]="'string'" required [cellClasses ]="cellStyles" >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ='defaultErrorTemplate' >
<div class ="validator-container" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
</div >
</ng-template >
</igx-column >
<igx-column field ="UnitsInStock" header ="UnitsInStock" [dataType ]="'number'" required [cellClasses ]="cellStyles" >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ='defaultErrorTemplate' >
<div class ="validator-container" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
</div >
</ng-template >
</igx-column >
</igx-grid >
</div >
html コピー @use '../../../variables' as *;
.sample-wrapper {
margin : 0 auto;
padding : 16px ;
}
.validator-container {
color : hsl(34 , 80% , 63% );
}
:host ::ng-deep {
--ig-error-500 : 34deg , 80% , 63% ;
.invalid-cell {
box-shadow : inset 0 0 0 0.125rem color($color : error);
}
}
scss コピー
API リファレンス
既知の問題と制限
制限
説明
validationTrigger
が blur の場合、editValue
と検証は、エディターからフォーカスが外れた後にのみトリガーされます。
理由は、これが formControl の updateOn
プロパティを利用しているためです。これにより、formControl が更新され、関連する検証をトリガーするイベントが決定されます。
その他のリソース
コミュニティに参加して新しいアイデアをご提案ください。