Skip to content

Commit 93cf295

Browse files
fix(combo,select): reset to INITIAL state on control.disable() when touched/dirty (#17262)
* fix(combo,select): reset to INITIAL state on control.disable() when touched/dirty Closes #17234 Replace `!this.disabled` with `!this.ngControl.disabled`, which reflects the live control status and is already `true` at the time `statusChanges` emits. This matches the pattern used by IgxTimePicker and IgxDateRangePicker. * Potential fix for pull request finding 'Semicolon insertion' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Potential fix for pull request finding 'Semicolon insertion' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
1 parent e95d9fc commit 93cf295

5 files changed

Lines changed: 111 additions & 2 deletions

File tree

projects/igniteui-angular/combo/src/combo/combo.common.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
12891289
public onClick(event: Event) {
12901290
event.stopPropagation();
12911291
event.preventDefault();
1292+
12921293
if (!this.disabled) {
12931294
this.toggle();
12941295
}
@@ -1313,7 +1314,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
13131314
}
13141315

13151316
protected onStatusChanged = () => {
1316-
if (this.ngControl && this.isTouchedOrDirty && !this.disabled) {
1317+
if (this.ngControl && this.isTouchedOrDirty && !this.ngControl.disabled) {
13171318
if (this.hasValidators && (!this.collapsed || this.inputGroup.isFocused)) {
13181319
this.valid = this.ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
13191320
} else {
@@ -1323,6 +1324,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
13231324
// B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526
13241325
this.valid = IgxInputState.INITIAL;
13251326
}
1327+
13261328
this.manageRequiredAsterisk();
13271329
};
13281330

projects/igniteui-angular/combo/src/combo/combo.component.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3345,6 +3345,7 @@ describe('igxCombo', () => {
33453345
combo = fixture.componentInstance.combo;
33463346
input = fixture.debugElement.query(By.css(`.${CSS_CLASS_INPUTGROUP}`));
33473347
});
3348+
33483349
it('should properly initialize when used as a form control', () => {
33493350
expect(combo).toBeDefined();
33503351
const comboFormReference = fixture.componentInstance.reactiveForm.controls.townCombo;
@@ -3375,6 +3376,7 @@ describe('igxCombo', () => {
33753376
expect(combo.valid).toEqual(IgxInputState.INITIAL);
33763377
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
33773378
});
3379+
33783380
it('should properly initialize when used as a form control - without validators', () => {
33793381
const form: UntypedFormGroup = fixture.componentInstance.reactiveForm;
33803382
form.controls.townCombo.validator = null;
@@ -3407,6 +3409,7 @@ describe('igxCombo', () => {
34073409
expect(combo.valid).toEqual(IgxInputState.INITIAL);
34083410
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
34093411
});
3412+
34103413
it('should be possible to be enabled/disabled when used as a form control', () => {
34113414
const form = fixture.componentInstance.reactiveForm;
34123415
const comboFormReference = form.controls.townCombo;
@@ -3439,6 +3442,47 @@ describe('igxCombo', () => {
34393442
expect(comboFormReference.disabled).toBeFalsy();
34403443
expect(combo.disabled).toBeFalsy();
34413444
});
3445+
3446+
it('should render as INITIAL after control.disable() and touched/dirty', () => {
3447+
const form = fixture.componentInstance.reactiveForm;
3448+
const control = form.controls.townCombo;
3449+
3450+
combo.select([combo.data.at(0)], true);
3451+
fixture.detectChanges();
3452+
3453+
control.markAsTouched();
3454+
fixture.detectChanges();
3455+
3456+
expect(combo.valid).toEqual(IgxInputState.INITIAL);
3457+
3458+
control.disable();
3459+
fixture.detectChanges();
3460+
3461+
expect(combo.disabled).toBe(true);
3462+
expect(combo.valid).toEqual(IgxInputState.INITIAL);
3463+
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
3464+
});
3465+
3466+
it('should render as INITIAL (not INVALID) after control.disable() and previously invalid', () => {
3467+
const form = fixture.componentInstance.reactiveForm;
3468+
const control = form.controls.townCombo;
3469+
3470+
// markAsTouched must come BEFORE setValue: markAsTouched does not emit
3471+
// statusChanges, so onStatusChanged must see touched=true at the moment
3472+
// statusChanges fires from setValue.
3473+
control.markAsTouched();
3474+
control.setValue([]);
3475+
fixture.detectChanges();
3476+
3477+
expect(combo.valid).toEqual(IgxInputState.INVALID);
3478+
3479+
control.disable();
3480+
fixture.detectChanges();
3481+
3482+
expect(combo.valid).toEqual(IgxInputState.INITIAL);
3483+
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
3484+
});
3485+
34423486
it('should change value when addressed as a form control', () => {
34433487
expect(combo).toBeDefined();
34443488
const form = fixture.componentInstance.reactiveForm;
@@ -3458,6 +3502,7 @@ describe('igxCombo', () => {
34583502
fixture.detectChanges();
34593503
expect(comboFormReference.value).toEqual([{ field: 'South Carolina', region: 'South Atlantic' }]);
34603504
});
3505+
34613506
it('should properly submit values when used as a form control', () => {
34623507
expect(combo).toBeDefined();
34633508
const form = fixture.componentInstance.reactiveForm;
@@ -3473,6 +3518,7 @@ describe('igxCombo', () => {
34733518
expect(form.status).toEqual('VALID');
34743519
fixture.debugElement.query(By.css('button')).triggerEventHandler('click', UIInteractions.simulateClickAndSelectEvent);
34753520
});
3521+
34763522
it('should add/remove asterisk when setting validators dynamically', () => {
34773523
let inputGroupIsRequiredClass = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUTGROUP_REQUIRED));
34783524
let asterisk = window.getComputedStyle(fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUTGROUP_LABEL)).nativeElement, ':after').content;
@@ -3522,6 +3568,7 @@ describe('igxCombo', () => {
35223568
expect((combo as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(false);
35233569
}));
35243570
});
3571+
35253572
describe('Template form tests: ', () => {
35263573
let inputGroupRequired: DebugElement;
35273574
beforeEach(fakeAsync(() => {

projects/igniteui-angular/select/src/select/select.component.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,27 @@ describe('igxSelect', () => {
687687
expect((selectComp as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(false);
688688
}));
689689

690+
it('should render as INITIAL (not INVALID) after control.disable() when previously invalid', () => {
691+
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
692+
fix.detectChanges();
693+
694+
const selectComp = fix.componentInstance.select;
695+
const control = fix.componentInstance.reactiveForm.controls.optionsSelect;
696+
697+
// markAsTouched does not emit statusChanges; use updateValueAndValidity() to
698+
// trigger onStatusChanged while touched=true so the INVALID precondition is established.
699+
control.markAsTouched();
700+
control.updateValueAndValidity();
701+
fix.detectChanges();
702+
703+
expect(selectComp.input.valid).toEqual(IgxInputState.INVALID);
704+
705+
control.disable();
706+
fix.detectChanges();
707+
708+
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
709+
});
710+
690711
it('Should properly initialize when used as a form control - with initial validators', fakeAsync(() => {
691712
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
692713

projects/igniteui-angular/select/src/select/select.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
391391
if (this.disabled || this.items.length === 0) {
392392
return;
393393
}
394+
394395
if (!this.selectedItem) {
395396
this.navigateFirst();
396397
}
@@ -539,7 +540,8 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
539540

540541
protected onStatusChanged() {
541542
this.manageRequiredAsterisk();
542-
if (this.ngControl && !this.disabled && this.isTouchedOrDirty) {
543+
544+
if (this.ngControl && !this.ngControl.disabled && this.isTouchedOrDirty) {
543545
if (this.hasValidators && this.inputGroup.isFocused) {
544546
this.input.valid = this.ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
545547
} else {

projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2566,13 +2566,15 @@ describe('IgxSimpleCombo', () => {
25662566
]
25672567
}).compileComponents();
25682568
}));
2569+
25692570
beforeEach(() => {
25702571
fixture = TestBed.createComponent(IgxSimpleComboInReactiveFormComponent);
25712572
fixture.detectChanges();
25722573
combo = fixture.componentInstance.reactiveCombo;
25732574
reactiveForm = fixture.componentInstance.reactiveForm;
25742575
reactiveControl = reactiveForm.form.controls['comboValue'];
25752576
});
2577+
25762578
it('should not select null, undefined and empty string in a reactive form with required', fakeAsync(() => {
25772579
// array of objects
25782580
combo.data = [
@@ -2685,6 +2687,7 @@ describe('IgxSimpleCombo', () => {
26852687
expect(reactiveForm.status).toEqual('INVALID');
26862688
expect(reactiveControl.status).toEqual('INVALID');
26872689
}));
2690+
26882691
it('should not select null, undefined and empty string with "writeValue" method in a reactive form with required', () => {
26892692
// array of objects
26902693
combo.data = [
@@ -2844,6 +2847,40 @@ describe('IgxSimpleCombo', () => {
28442847
expect(combo.value).toEqual(1);
28452848
expect(form.controls['comboValue'].value).toEqual(1);
28462849
}));
2850+
2851+
it('should render as INITIAL after control.disable() and touched/dirty', () => {
2852+
combo.select([combo.data.at(0)]);
2853+
fixture.detectChanges();
2854+
2855+
reactiveControl.markAsTouched();
2856+
fixture.detectChanges();
2857+
2858+
expect(combo.valid).toEqual(IgxInputState.INITIAL);
2859+
2860+
reactiveControl.disable();
2861+
fixture.detectChanges();
2862+
2863+
expect(combo.disabled).toBe(true);
2864+
expect(combo.valid).toEqual(IgxInputState.INITIAL);
2865+
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
2866+
});
2867+
2868+
it('should render as INITIAL (not INVALID) after control.disable() and previously invalid', () => {
2869+
// markAsTouched must come BEFORE setValue: markAsTouched does not emit
2870+
// statusChanges, so onStatusChanged must see touched=true at the moment
2871+
// statusChanges fires from setValue.
2872+
reactiveControl.markAsTouched();
2873+
reactiveControl.setValue([]);
2874+
fixture.detectChanges();
2875+
2876+
expect(combo.valid).toEqual(IgxInputState.INVALID);
2877+
2878+
reactiveControl.disable();
2879+
fixture.detectChanges();
2880+
2881+
expect(combo.valid).toEqual(IgxInputState.INITIAL);
2882+
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
2883+
});
28472884
});
28482885
});
28492886

0 commit comments

Comments
 (0)