From aa36463f717c3f33b447af995e55c08ee0ac3770 Mon Sep 17 00:00:00 2001 From: Ivan Kitanov Date: Thu, 26 Jun 2025 10:28:52 +0300 Subject: [PATCH 01/12] fix(combo): Sync aria required with required state --- projects/igniteui-angular/src/lib/combo/combo.common.ts | 1 + .../igniteui-angular/src/lib/combo/combo.component.spec.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/combo/combo.common.ts b/projects/igniteui-angular/src/lib/combo/combo.common.ts index 6084b0ef637..23f8446813b 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.common.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.common.ts @@ -1322,6 +1322,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh protected manageRequiredAsterisk(): void { if (this.ngControl) { this.inputGroup.isRequired = this.required; + this.comboInput.nativeElement.setAttribute('aria-required', this.required.toString()); } } diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts index e60214a6542..c81f51f3e2b 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts @@ -58,7 +58,7 @@ const CSS_CLASS_ITME_CHECKBOX_CHECKED = 'igx-checkbox--checked'; const defaultDropdownItemHeight = 40; const defaultDropdownItemMaxHeight = 400; -describe('igxCombo', () => { +fdescribe('igxCombo', () => { let fixture: ComponentFixture; let combo: IgxComboComponent; let input: DebugElement; @@ -3390,8 +3390,10 @@ describe('igxCombo', () => { it('should add/remove asterisk when setting validators dynamically', () => { let inputGroupIsRequiredClass = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUTGROUP_REQUIRED)); let asterisk = window.getComputedStyle(fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUTGROUP_LABEL)).nativeElement, ':after').content; + input = fixture.debugElement.query(By.css(`.${CSS_CLASS_COMBO_INPUTGROUP}`)); expect(asterisk).toBe('"*"'); expect(inputGroupIsRequiredClass).toBeDefined(); + expect(input.nativeElement.getAttribute('aria-required')).toMatch('true'); fixture.componentInstance.reactiveForm.controls.townCombo.clearValidators(); fixture.componentInstance.reactiveForm.controls.townCombo.updateValueAndValidity(); @@ -3400,6 +3402,7 @@ describe('igxCombo', () => { asterisk = window.getComputedStyle(fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUTGROUP_LABEL)).nativeElement, ':after').content; expect(asterisk).toBe('none'); expect(inputGroupIsRequiredClass).toBeNull(); + expect(input.nativeElement.getAttribute('aria-required')).toMatch('false'); fixture.componentInstance.reactiveForm.controls.townCombo.setValidators(Validators.required); fixture.componentInstance.reactiveForm.controls.townCombo.updateValueAndValidity(); @@ -3408,6 +3411,7 @@ describe('igxCombo', () => { asterisk = window.getComputedStyle(fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUTGROUP_LABEL)).nativeElement, ':after').content; expect(asterisk).toBe('"*"'); expect(inputGroupIsRequiredClass).toBeDefined(); + expect(input.nativeElement.getAttribute('aria-required')).toMatch('true'); }); it('Should update validity state when programmatically setting errors on reactive form controls', fakeAsync(() => { From 926d79a3d625f689cee00940aa32296036109a0b Mon Sep 17 00:00:00 2001 From: Ivan Kitanov Date: Fri, 27 Jun 2025 09:56:33 +0300 Subject: [PATCH 02/12] chore(combo): Remove fdescribe --- projects/igniteui-angular/src/lib/combo/combo.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts index c81f51f3e2b..bdbb48f2555 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts @@ -58,7 +58,7 @@ const CSS_CLASS_ITME_CHECKBOX_CHECKED = 'igx-checkbox--checked'; const defaultDropdownItemHeight = 40; const defaultDropdownItemMaxHeight = 400; -fdescribe('igxCombo', () => { +describe('igxCombo', () => { let fixture: ComponentFixture; let combo: IgxComboComponent; let input: DebugElement; From 00e96dcd19c2dfdae34f0e0bf15da67ab9f2a549 Mon Sep 17 00:00:00 2001 From: Stamen Stoychev Date: Mon, 14 Jul 2025 17:03:35 +0300 Subject: [PATCH 03/12] fix(combo): bypassing require logic applied by igxInput --- projects/igniteui-angular/src/lib/combo/combo.common.ts | 1 - projects/igniteui-angular/src/lib/combo/combo.component.html | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/combo/combo.common.ts b/projects/igniteui-angular/src/lib/combo/combo.common.ts index 23f8446813b..6084b0ef637 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.common.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.common.ts @@ -1322,7 +1322,6 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh protected manageRequiredAsterisk(): void { if (this.ngControl) { this.inputGroup.isRequired = this.required; - this.comboInput.nativeElement.setAttribute('aria-required', this.required.toString()); } } diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.html b/projects/igniteui-angular/src/lib/combo/combo.component.html index e9603f47cb4..a4af68dbc05 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.html +++ b/projects/igniteui-angular/src/lib/combo/combo.component.html @@ -9,6 +9,7 @@ Date: Thu, 24 Jul 2025 14:49:31 +0300 Subject: [PATCH 04/12] feat(*): adding external validator logic for input --- .../src/lib/combo/combo.common.ts | 4 ++++ .../src/lib/combo/combo.component.html | 2 +- .../lib/directives/input/input.directive.ts | 19 ++++++++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/combo/combo.common.ts b/projects/igniteui-angular/src/lib/combo/combo.common.ts index e78a67c49b4..7f4af696e54 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.common.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.common.ts @@ -1300,6 +1300,10 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh this.manageRequiredAsterisk(); }; + protected externalValidate(): IgxInputState { + return this._valid; + } + private updateValidity() { if (this.ngControl && this.ngControl.invalid) { this.valid = IgxInputState.INVALID; diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.html b/projects/igniteui-angular/src/lib/combo/combo.component.html index 52422c4a939..7c470190ccd 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.html +++ b/projects/igniteui-angular/src/lib/combo/combo.component.html @@ -9,7 +9,7 @@ IgxInputState = null; constructor( public inputGroup: IgxInputGroupBase, @@ -195,6 +196,20 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { this.nativeElement.required = this.inputGroup.isRequired = value; } + /** + * @hidden @internal + * Sets a function to validate the input externally. + * This function should return an `IgxInputState` value. + */ + @Input() + public set externalValidate(fn: () => IgxInputState) { + this._externalValidate = fn; + } + + public get externalValidate(): () => IgxInputState { + return this._externalValidate; + } + /** * Gets whether the igxInput is required. * @@ -367,7 +382,9 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { * @internal */ protected updateValidityState() { - if (this.ngControl) { + if (this._externalValidate) { + this._valid = this._externalValidate(); + } else if (this.ngControl) { if (!this.disabled && this.isTouchedOrDirty) { if (this.hasValidators) { // Run the validation with empty object to check if required is enabled. From 23ab3e03c3dc585b8a020a82cfbd7593bcf4e134 Mon Sep 17 00:00:00 2001 From: Stamen Stoychev Date: Thu, 24 Jul 2025 15:23:26 +0300 Subject: [PATCH 05/12] fix(*): adding required props for input --- projects/igniteui-angular/src/lib/combo/combo.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.html b/projects/igniteui-angular/src/lib/combo/combo.component.html index 7c470190ccd..b97ae325338 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.html +++ b/projects/igniteui-angular/src/lib/combo/combo.component.html @@ -9,6 +9,7 @@ Date: Fri, 25 Jul 2025 17:59:09 +0300 Subject: [PATCH 06/12] feat(*): aria-required date-picker and simple combo --- projects/igniteui-angular/src/lib/combo/combo.common.ts | 1 + .../src/lib/date-picker/date-picker.component.html | 1 + .../src/lib/date-picker/date-picker.component.spec.ts | 4 ++++ .../src/lib/simple-combo/simple-combo.component.html | 1 + .../src/lib/simple-combo/simple-combo.component.spec.ts | 1 + 5 files changed, 8 insertions(+) diff --git a/projects/igniteui-angular/src/lib/combo/combo.common.ts b/projects/igniteui-angular/src/lib/combo/combo.common.ts index 7f4af696e54..6b8c879a316 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.common.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.common.ts @@ -1300,6 +1300,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh this.manageRequiredAsterisk(); }; + /** @hidden @internal */ protected externalValidate(): IgxInputState { return this._valid; } diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html index 81159a032bd..29a0c74fbbe 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html @@ -15,6 +15,7 @@ } { expect(datePicker).toBeDefined(); expect(inputGroup.isRequired).toBeTruthy(); + expect((datePicker as any).inputDirective.nativeElement.getAttribute('aria-required')).toEqual('true'); }); it('should update inputGroup isRequired correctly', () => { const inputGroup = (datePicker as any).inputGroup; + const inputEl = (datePicker as any).inputDirective.nativeElement; expect(datePicker).toBeDefined(); expect(inputGroup.isRequired).toBeTruthy(); + expect(inputEl.getAttribute('aria-required')).toEqual('true'); (fixture.componentInstance as IgxDatePickerNgModelComponent).isRequired = false; fixture.detectChanges(); expect(inputGroup.isRequired).toBeFalsy(); + expect(inputEl.getAttribute('aria-required')).toEqual('false'); }); it('should set validity to initial when the form is reset', fakeAsync(() => { diff --git a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html index 21252f137c7..af6d8cbdc57 100644 --- a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html +++ b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.html @@ -13,6 +13,7 @@ { expect(combo.valid).toEqual(IgxInputState.INITIAL); expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); + expect(combo.comboInput.nativeElement.attributes['aria-required']).toBeDefined(); // empty string combo.open(); From 534e68f026fdfd4e3b786111a01e648218eb5ced Mon Sep 17 00:00:00 2001 From: Stamen Stoychev Date: Fri, 25 Jul 2025 18:13:14 +0300 Subject: [PATCH 07/12] fix(tests): making the check working --- .../src/lib/date-picker/date-picker.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts index e486a7509ab..a91acf26ad5 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts @@ -246,7 +246,7 @@ describe('IgxDatePicker', () => { fixture.detectChanges(); expect(inputGroup.isRequired).toBeFalsy(); - expect(inputEl.getAttribute('aria-required')).toEqual('false'); + expect(inputEl.getAttribute('aria-required')).toEqual(null); }); it('should set validity to initial when the form is reset', fakeAsync(() => { From e1ee3c42976d55d513810974c193f2d5c2abda9e Mon Sep 17 00:00:00 2001 From: Stamen Stoychev Date: Wed, 6 Aug 2025 16:56:32 +0300 Subject: [PATCH 08/12] refactor(inputs): removing aria-required as it's redundant --- projects/igniteui-angular/src/lib/combo/combo.component.html | 3 +-- .../src/lib/date-picker/date-picker.component.html | 3 +-- .../src/lib/directives/input/input.directive.ts | 3 --- .../src/lib/simple-combo/simple-combo.component.html | 2 +- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.html b/projects/igniteui-angular/src/lib/combo/combo.component.html index b97ae325338..d61e4750354 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.html +++ b/projects/igniteui-angular/src/lib/combo/combo.component.html @@ -9,8 +9,7 @@ Date: Thu, 7 Aug 2025 14:31:50 +0300 Subject: [PATCH 09/12] chore(*): further changes to input req attrs --- .../src/lib/combo/combo.component.spec.ts | 6 +-- .../date-picker/date-picker.component.spec.ts | 6 +-- .../directives/input/input.directive.spec.ts | 8 ++-- .../lib/directives/input/input.directive.ts | 37 ++++++++++--------- .../simple-combo.component.spec.ts | 2 +- 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts index 6ec5985243b..b46fb74627b 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts @@ -3439,7 +3439,7 @@ describe('igxCombo', () => { input = fixture.debugElement.query(By.css(`.${CSS_CLASS_COMBO_INPUTGROUP}`)); expect(asterisk).toBe('"*"'); expect(inputGroupIsRequiredClass).toBeDefined(); - expect(input.nativeElement.getAttribute('aria-required')).toMatch('true'); + expect(input.nativeElement.getAttribute('required')).not.toBeNull(); fixture.componentInstance.reactiveForm.controls.townCombo.clearValidators(); fixture.componentInstance.reactiveForm.controls.townCombo.updateValueAndValidity(); @@ -3448,7 +3448,7 @@ describe('igxCombo', () => { asterisk = window.getComputedStyle(fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUTGROUP_LABEL)).nativeElement, ':after').content; expect(asterisk).toBe('none'); expect(inputGroupIsRequiredClass).toBeNull(); - expect(input.nativeElement.getAttribute('aria-required')).toMatch('false'); + expect(input.nativeElement.getAttribute('required')).toBeNull(); fixture.componentInstance.reactiveForm.controls.townCombo.setValidators(Validators.required); fixture.componentInstance.reactiveForm.controls.townCombo.updateValueAndValidity(); @@ -3457,7 +3457,7 @@ describe('igxCombo', () => { asterisk = window.getComputedStyle(fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUTGROUP_LABEL)).nativeElement, ':after').content; expect(asterisk).toBe('"*"'); expect(inputGroupIsRequiredClass).toBeDefined(); - expect(input.nativeElement.getAttribute('aria-required')).toMatch('true'); + expect(input.nativeElement.getAttribute('required')).not.toBeNull(); }); it('Should update validity state when programmatically setting errors on reactive form controls', fakeAsync(() => { diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts index a91acf26ad5..493bd95ff38 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts @@ -231,7 +231,7 @@ describe('IgxDatePicker', () => { expect(datePicker).toBeDefined(); expect(inputGroup.isRequired).toBeTruthy(); - expect((datePicker as any).inputDirective.nativeElement.getAttribute('aria-required')).toEqual('true'); + expect((datePicker as any).inputDirective.nativeElement.getAttribute('required')).not.toBeNull(); }); it('should update inputGroup isRequired correctly', () => { @@ -240,13 +240,13 @@ describe('IgxDatePicker', () => { expect(datePicker).toBeDefined(); expect(inputGroup.isRequired).toBeTruthy(); - expect(inputEl.getAttribute('aria-required')).toEqual('true'); + expect(inputEl.getAttribute('required')).not.toBeNull(); (fixture.componentInstance as IgxDatePickerNgModelComponent).isRequired = false; fixture.detectChanges(); expect(inputGroup.isRequired).toBeFalsy(); - expect(inputEl.getAttribute('aria-required')).toEqual(null); + expect(inputEl.getAttribute('required')).toBeNull(); }); it('should set validity to initial when the form is reset', fakeAsync(() => { diff --git a/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts index 5142e7030fd..b98f78f14fd 100644 --- a/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts @@ -702,7 +702,7 @@ describe('IgxInput', () => { let asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content; expect(asterisk).toBe('"*"'); expect(inputGroup.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true); - expect(input.nativeElement.attributes.getNamedItem('aria-required').nodeValue).toEqual('true'); + expect(input.nativeElement.getAttribute('required')).not.toBeNull(); // 2) check that input group's --invalid class is NOT applied expect(inputGroup.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false); @@ -718,7 +718,7 @@ describe('IgxInput', () => { expect(inputGroup.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true); expect(inputGroup.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true); - expect(input.nativeElement.attributes.getNamedItem('aria-required').nodeValue).toEqual('true'); + expect(input.nativeElement.getAttribute('required')).not.toBeNull(); // 3) Check if the input group's --invalid and --required classes are removed when validator is dynamically cleared fix.componentInstance.removeValidators(formGroup); @@ -731,7 +731,7 @@ describe('IgxInput', () => { expect(formReference).toBeDefined(); expect(input).toBeDefined(); expect(input.nativeElement.value).toEqual(''); - expect(input.nativeElement.attributes.getNamedItem('aria-required').nodeValue).toEqual('false'); + expect(input.nativeElement.getAttribute('required')).toBeNull(); expect(inputGroup.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toEqual(false); expect(asterisk).toBe('none'); expect(input.valid).toEqual(IgxInputState.INITIAL); @@ -751,7 +751,7 @@ describe('IgxInput', () => { // interaction test - expect actual asterisk asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content; expect(asterisk).toBe('"*"'); - expect(input.nativeElement.attributes.getNamedItem('aria-required').nodeValue).toEqual('true'); + expect(input.nativeElement.getAttribute('required')).not.toBeNull(); })); it('should not hold old file input value in form after clearing the input', () => { diff --git a/projects/igniteui-angular/src/lib/directives/input/input.directive.ts b/projects/igniteui-angular/src/lib/directives/input/input.directive.ts index f269852b736..1a87b9c1dd9 100644 --- a/projects/igniteui-angular/src/lib/directives/input/input.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/input/input.directive.ts @@ -97,11 +97,17 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { @HostBinding('class.igx-input-group__textarea') public isTextArea = false; + @HostBinding('attr.required') + public get requiredAttribute() { + return this.required ? '' : null; + } + private _valid = IgxInputState.INITIAL; private _statusChanges$: Subscription; private _valueChanges$: Subscription; private _fileNames: string; private _disabled = false; + private _defaultRequired: boolean = null; private _externalValidate: () => IgxInputState = null; constructor( @@ -181,21 +187,6 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { return this._disabled; } - /** - * Sets the `required` property. - * - * @example - * ```html - * - * - * - * ``` - */ - @Input({ transform: booleanAttribute }) - public set required(value: boolean) { - this.nativeElement.required = this.inputGroup.isRequired = value; - } - /** * @hidden @internal * Sets a function to validate the input externally. @@ -211,20 +202,30 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { } /** - * Gets whether the igxInput is required. + * Gets/Sets whether the igxInput is required. * * @example * ```typescript - * let isRequired = this.igxInput.required; + * + * + * * ``` */ + @Input({ transform: booleanAttribute }) public get required() { let validation; if (this.ngControl && (this.ngControl.control.validator || this.ngControl.control.asyncValidator)) { validation = this.ngControl.control.validator({} as AbstractControl); } - return validation && validation.required || this.nativeElement.hasAttribute('required'); + return validation && validation.required || !!this._defaultRequired; } + public set required(value: boolean) { + if (this._defaultRequired === null) { + this._defaultRequired = value; + } + this.inputGroup.isRequired = value; + } + /** * @hidden * @internal diff --git a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts index e169e419e9b..ee112e7d67d 100644 --- a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts @@ -2276,7 +2276,7 @@ describe('IgxSimpleCombo', () => { expect(combo.valid).toEqual(IgxInputState.INITIAL); expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); - expect(combo.comboInput.nativeElement.attributes['aria-required']).toBeDefined(); + expect(combo.comboInput.nativeElement.attributes['required']).toBeDefined(); // empty string combo.open(); From ef903c37bbafc3df13059c6c08c612a649df66c6 Mon Sep 17 00:00:00 2001 From: Stamen Stoychev Date: Thu, 7 Aug 2025 15:03:46 +0300 Subject: [PATCH 10/12] chore(*): adding logic to get external validate required --- .../src/lib/directives/input/input.directive.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/directives/input/input.directive.ts b/projects/igniteui-angular/src/lib/directives/input/input.directive.ts index 1a87b9c1dd9..3e390b0710e 100644 --- a/projects/igniteui-angular/src/lib/directives/input/input.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/input/input.directive.ts @@ -217,7 +217,8 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { if (this.ngControl && (this.ngControl.control.validator || this.ngControl.control.asyncValidator)) { validation = this.ngControl.control.validator({} as AbstractControl); } - return validation && validation.required || !!this._defaultRequired; + return validation && validation.required || (this._externalValidate ? + this.inputGroup.isRequired : !!this._defaultRequired); } public set required(value: boolean) { if (this._defaultRequired === null) { From a0d67c50ada304d1a956731e86989e1cb5ed0210 Mon Sep 17 00:00:00 2001 From: Stamen Stoychev Date: Thu, 7 Aug 2025 17:16:05 +0300 Subject: [PATCH 11/12] chore(*): try req logic like this --- .../lib/directives/input/input.directive.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/projects/igniteui-angular/src/lib/directives/input/input.directive.ts b/projects/igniteui-angular/src/lib/directives/input/input.directive.ts index 3e390b0710e..2b532cb02aa 100644 --- a/projects/igniteui-angular/src/lib/directives/input/input.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/input/input.directive.ts @@ -97,11 +97,6 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { @HostBinding('class.igx-input-group__textarea') public isTextArea = false; - @HostBinding('attr.required') - public get requiredAttribute() { - return this.required ? '' : null; - } - private _valid = IgxInputState.INITIAL; private _statusChanges$: Subscription; private _valueChanges$: Subscription; @@ -217,14 +212,21 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy { if (this.ngControl && (this.ngControl.control.validator || this.ngControl.control.asyncValidator)) { validation = this.ngControl.control.validator({} as AbstractControl); } - return validation && validation.required || (this._externalValidate ? - this.inputGroup.isRequired : !!this._defaultRequired); + let required; + if (validation && validation.required !== undefined) { + required = validation.required; + } else { + required = this.nativeElement.required; + } + return required; } public set required(value: boolean) { - if (this._defaultRequired === null) { - this._defaultRequired = value; - } - this.inputGroup.isRequired = value; + this.nativeElement.required = this.inputGroup.isRequired = value; + } + + @HostBinding('attr.required') + public get hostBindingRequired(): string { + return this.required ? '' : null; } /** From 9028e1b68b75234674bacee61eae46ea805df565 Mon Sep 17 00:00:00 2001 From: Stamen Stoychev Date: Mon, 11 Aug 2025 17:45:40 +0300 Subject: [PATCH 12/12] test(*): final fixes for new option --- .../src/lib/directives/input/input.directive.spec.ts | 3 +++ .../src/lib/simple-combo/simple-combo.component.spec.ts | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts index b98f78f14fd..dcb2cbe4a69 100644 --- a/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/input/input.directive.spec.ts @@ -722,6 +722,9 @@ describe('IgxInput', () => { // 3) Check if the input group's --invalid and --required classes are removed when validator is dynamically cleared fix.componentInstance.removeValidators(formGroup); + // the component cannot both take required on the input and validators. If validators write required, their removal + // cannot cause required to be removed as it may have been part of the initial setup + input.nativeElement.removeAttribute('required'); fix.detectChanges(); tick(); diff --git a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts index ee112e7d67d..24d751b251e 100644 --- a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts @@ -2276,7 +2276,6 @@ describe('IgxSimpleCombo', () => { expect(combo.valid).toEqual(IgxInputState.INITIAL); expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); - expect(combo.comboInput.nativeElement.attributes['required']).toBeDefined(); // empty string combo.open();