diff --git a/projects/igniteui-angular/src/lib/combo/combo.common.ts b/projects/igniteui-angular/src/lib/combo/combo.common.ts
index e78a67c49b4..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,11 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
this.manageRequiredAsterisk();
};
+ /** @hidden @internal */
+ 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 9e795a6f3d3..d61e4750354 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 @@
{
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('required')).not.toBeNull();
fixture.componentInstance.reactiveForm.controls.townCombo.clearValidators();
fixture.componentInstance.reactiveForm.controls.townCombo.updateValueAndValidity();
@@ -3446,6 +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('required')).toBeNull();
fixture.componentInstance.reactiveForm.controls.townCombo.setValidators(Validators.required);
fixture.componentInstance.reactiveForm.controls.townCombo.updateValueAndValidity();
@@ -3454,6 +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('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.html b/projects/igniteui-angular/src/lib/date-picker/date-picker.component.html
index 81159a032bd..3771ae42017 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,7 +15,7 @@
}
{
expect(datePicker).toBeDefined();
expect(inputGroup.isRequired).toBeTruthy();
+ expect((datePicker as any).inputDirective.nativeElement.getAttribute('required')).not.toBeNull();
});
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('required')).not.toBeNull();
(fixture.componentInstance as IgxDatePickerNgModelComponent).isRequired = false;
fixture.detectChanges();
expect(inputGroup.isRequired).toBeFalsy();
+ 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..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
@@ -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,10 +718,13 @@ 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);
+ // 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();
@@ -731,7 +734,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 +754,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 daf01c89904..2b532cb02aa 100644
--- a/projects/igniteui-angular/src/lib/directives/input/input.directive.ts
+++ b/projects/igniteui-angular/src/lib/directives/input/input.directive.ts
@@ -102,6 +102,8 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
private _valueChanges$: Subscription;
private _fileNames: string;
private _disabled = false;
+ private _defaultRequired: boolean = null;
+ private _externalValidate: () => IgxInputState = null;
constructor(
public inputGroup: IgxInputGroupBase,
@@ -181,35 +183,52 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
}
/**
- * 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.
+ * 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.
+ * 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');
+ let required;
+ if (validation && validation.required !== undefined) {
+ required = validation.required;
+ } else {
+ required = this.nativeElement.required;
+ }
+ return required;
+ }
+ public set required(value: boolean) {
+ this.nativeElement.required = this.inputGroup.isRequired = value;
+ }
+
+ @HostBinding('attr.required')
+ public get hostBindingRequired(): string {
+ return this.required ? '' : null;
}
+
/**
* @hidden
* @internal
@@ -293,8 +312,6 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
this.inputGroup.isRequired = this.required;
}
- this.renderer.setAttribute(this.nativeElement, 'aria-required', this.required.toString());
-
const elTag = this.nativeElement.tagName.toLowerCase();
if (elTag === 'textarea') {
this.isTextArea = true;
@@ -367,7 +384,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.
@@ -386,7 +405,6 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
} else {
this._valid = IgxInputState.INITIAL;
}
- this.renderer.setAttribute(this.nativeElement, 'aria-required', this.required.toString());
const ariaInvalid = this.valid === IgxInputState.INVALID;
this.renderer.setAttribute(this.nativeElement, 'aria-invalid', ariaInvalid.toString());
} else {
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..69a72363d52 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 @@