Skip to content

Commit b3f28e4

Browse files
Handle validation for fields with '.' - 19.2.x (#16053)
* fix(grid): handle validation for fields with '.' * chore(*): fix failing test * fix(grid): use column field names as names for the ngControls * chore(*): remove not needed comments * chore(*): explicitly return false if formGroup does not exist * Update grid-validation.service.ts --------- Co-authored-by: Hristo <hanastasov@infragistics.com>
1 parent 1dd09c0 commit b3f28e4

File tree

4 files changed

+73
-30
lines changed

4 files changed

+73
-30
lines changed

projects/igniteui-angular/src/lib/grids/cell.component.html

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -226,37 +226,38 @@
226226
}
227227

228228
<ng-template #defaultError>
229-
@if (formGroup?.get(column?.field).errors?.['required']) {
229+
@let errors = formControl.errors;
230+
@if (errors?.['required']) {
230231
<div>
231232
{{grid.resourceStrings.igx_grid_required_validation_error}}
232233
</div>
233234
}
234-
@if (formGroup?.get(column?.field).errors?.['minlength']) {
235+
@if (errors?.['minlength']) {
235236
<div>
236-
{{grid.resourceStrings.igx_grid_min_length_validation_error | igxStringReplace:'{0}':formGroup.get(column.field).errors.minlength.requiredLength }}
237+
{{grid.resourceStrings.igx_grid_min_length_validation_error | igxStringReplace:'{0}':errors.minlength.requiredLength }}
237238
</div>
238239
}
239-
@if (formGroup?.get(column?.field).errors?.['maxlength']) {
240+
@if (errors?.['maxlength']) {
240241
<div>
241-
{{grid.resourceStrings.igx_grid_max_length_validation_error | igxStringReplace:'{0}':formGroup.get(column.field).errors.maxlength.requiredLength }}
242+
{{grid.resourceStrings.igx_grid_max_length_validation_error | igxStringReplace:'{0}':errors.maxlength.requiredLength }}
242243
</div>
243244
}
244-
@if (formGroup?.get(column?.field).errors?.['min']) {
245+
@if (errors?.['min']) {
245246
<div>
246-
{{grid.resourceStrings.igx_grid_min_validation_error | igxStringReplace:'{0}':formGroup.get(column.field).errors.min.min }}
247+
{{grid.resourceStrings.igx_grid_min_validation_error | igxStringReplace:'{0}':errors.min.min }}
247248
</div>
248249
}
249-
@if (formGroup?.get(column?.field).errors?.['max']) {
250+
@if (errors?.['max']) {
250251
<div>
251-
{{grid.resourceStrings.igx_grid_max_validation_error | igxStringReplace:'{0}':formGroup.get(column.field).errors.max.max }}
252+
{{grid.resourceStrings.igx_grid_max_validation_error | igxStringReplace:'{0}':errors.max.max }}
252253
</div>
253254
}
254-
@if (formGroup?.get(column?.field).errors?.['email']) {
255+
@if (errors?.['email']) {
255256
<div>
256257
{{grid.resourceStrings.igx_grid_email_validation_error }}
257258
</div>
258259
}
259-
@if (formGroup?.get(column?.field).errors?.['pattern']) {
260+
@if (errors?.['pattern']) {
260261
<div>
261262
{{grid.resourceStrings.igx_grid_pattern_validation_error}}
262263
</div>

projects/igniteui-angular/src/lib/grids/cell.component.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -538,8 +538,11 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy, CellT
538538
@HostBinding('class.igx-grid__td--invalid')
539539
@HostBinding('attr.aria-invalid')
540540
public get isInvalid() {
541-
const isInvalid = this.formGroup?.get(this.column?.field)?.invalid && this.formGroup?.get(this.column?.field)?.touched;
542-
return !this.intRow.deleted && isInvalid;
541+
if (this.formGroup) {
542+
const isInvalid = this.grid.validation?.isFieldInvalid(this.formGroup, this.column?.field);
543+
return !this.intRow.deleted && isInvalid;
544+
}
545+
return false;
543546
}
544547

545548
/**
@@ -548,8 +551,11 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy, CellT
548551
*/
549552
@HostBinding('class.igx-grid__td--valid')
550553
public get isValidAfterEdit() {
551-
const formControl = this.formGroup?.get(this.column?.field);
552-
return this.editMode && formControl && !formControl.invalid && formControl.dirty;
554+
if (this.formGroup) {
555+
const isValidAfterEdit = this.grid.validation?.isFieldValidAfterEdit(this.formGroup, this.column?.field);
556+
return this.editMode && isValidAfterEdit;
557+
}
558+
return false;
553559
}
554560

555561
/**

projects/igniteui-angular/src/lib/grids/grid/grid-validation.service.ts

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,21 @@ export class IgxGridValidationService {
5959
*/
6060
private addFormControl(formGroup: FormGroup, data: any, column: ColumnType) {
6161
const value = resolveNestedPath(data || {}, columnFieldPath(column.field));
62-
const field = this.getFieldKey(column.field);
6362
const control = new FormControl(value, { updateOn: this.grid.validationTrigger });
6463
control.addValidators(column.validators);
65-
formGroup.addControl(field, control);
64+
formGroup.addControl(column.field, control);
6665
control.setValue(value);
6766
}
68-
67+
6968
/**
7069
* @hidden
7170
* @internal
71+
Wraps the provided path into an array. This way FormGroup.get will return proper result.
72+
Otherwise, if the path is a string (e.g. 'address.street'), FormGroup.get will treat it as there is a nested structure
73+
and will look for control with a name of 'address' which returns undefined.
7274
*/
73-
private getFieldKey(path: string) {
74-
const parts = path?.split('.') ?? [];
75-
return parts.join('_');
75+
private getFormControlPath(path: string): (string)[] {
76+
return [path];
7677
}
7778

7879
/**
@@ -89,8 +90,8 @@ export class IgxGridValidationService {
8990
*/
9091
public getFormControl(rowId: any, columnKey: string) {
9192
const formControl = this.getFormGroup(rowId);
92-
const field = this.getFieldKey(columnKey);
93-
return formControl?.get(field);
93+
const path = this.getFormControlPath(columnKey);
94+
return formControl?.get(path);
9495
}
9596

9697
/**
@@ -101,6 +102,22 @@ export class IgxGridValidationService {
101102
this._validityStates.set(rowId, form);
102103
}
103104

105+
/**
106+
* Checks the validity of the native ngControl
107+
*/
108+
public isFieldInvalid(formGroup: FormGroup, fieldName: string): boolean {
109+
const path = this.getFormControlPath(fieldName);
110+
return formGroup.get(path)?.invalid && formGroup.get(path)?.touched;
111+
}
112+
113+
/**
114+
* Checks the validity of the native ngControl after edit
115+
*/
116+
public isFieldValidAfterEdit(formGroup: FormGroup, fieldName: string): boolean {
117+
const path = this.getFormControlPath(fieldName);
118+
return !formGroup.get(path)?.invalid && formGroup.get(path)?.dirty;
119+
}
120+
104121
/**
105122
* @hidden
106123
* @internal
@@ -110,10 +127,10 @@ export class IgxGridValidationService {
110127
this._validityStates.forEach((formGroup, key) => {
111128
const state: IFieldValidationState[] = [];
112129
for (const col of this.grid.columns) {
113-
const colKey = this.getFieldKey(col.field);
114-
const control = formGroup.get(colKey);
130+
const path = this.getFormControlPath(col.field);
131+
const control = formGroup.get(path);
115132
if (control) {
116-
state.push({ field: colKey, status: control.status as ValidationStatus, errors: control.errors })
133+
state.push({ field: col.field, status: control.status as ValidationStatus, errors: control.errors })
117134
}
118135
}
119136
states.push({ key: key, status: formGroup.status as ValidationStatus, fields: state, errors: formGroup.errors });
@@ -138,8 +155,8 @@ export class IgxGridValidationService {
138155
const keys = Object.keys(rowData);
139156
const rowGroup = this.getFormGroup(rowId);
140157
for (const key of keys) {
141-
const colKey = this.getFieldKey(key);
142-
const control = rowGroup?.get(colKey);
158+
const path = this.getFormControlPath(key);
159+
const control = rowGroup?.get(path);
143160
if (control && control.value !== rowData[key]) {
144161
control.setValue(rowData[key], { emitEvent: false });
145162
}
@@ -174,8 +191,8 @@ export class IgxGridValidationService {
174191
rowGroup.markAsTouched();
175192
const fields = field ? [field] : this.grid.columns.map(x => x.field);
176193
for (const currField of fields) {
177-
const colKey = this.getFieldKey(currField);
178-
rowGroup?.get(colKey)?.markAsTouched();
194+
const path = this.getFormControlPath(currField);
195+
rowGroup?.get(path)?.markAsTouched();
179196
}
180197
}
181198

projects/igniteui-angular/src/lib/grids/grid/grid-validation.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,25 @@ describe('IgxGrid - Validation #grid', () => {
171171
expect(erorrMessage).toEqual(' Entry should be at least 4 character(s) long ');
172172
});
173173

174+
it('should mark invalid cell with igx-grid__td--invalid class and show the related error cell template when the field contains "."', () => {
175+
const grid = fixture.componentInstance.grid as IgxGridComponent;
176+
// add new column
177+
fixture.componentInstance.columns.push({ field: 'New.Column', dataType: 'string' });
178+
fixture.detectChanges();
179+
expect(grid.columns.length).toBe(5);
180+
181+
let cell = grid.gridAPI.get_cell_by_visible_index(1, 4);
182+
UIInteractions.simulateDoubleClickAndSelectEvent(cell.element);
183+
cell.update('asd');
184+
fixture.detectChanges();
185+
186+
cell = grid.gridAPI.get_cell_by_visible_index(1, 4);
187+
//min length should be 4
188+
GridFunctions.verifyCellValid(cell, false);
189+
const erorrMessage = cell.errorTooltip.first.elementRef.nativeElement.children[0].textContent;
190+
expect(erorrMessage).toEqual(' Entry should be at least 4 character(s) long ');
191+
});
192+
174193
it('should show the error message on error icon hover and when the invalid cell becomes active.', fakeAsync(() => {
175194
const grid = fixture.componentInstance.grid as IgxGridComponent;
176195

0 commit comments

Comments
 (0)