-
Notifications
You must be signed in to change notification settings - Fork 26.5k
Description
I'm submitting a...
[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
Current behavior
With reactive forms, the validators are not reflected to the input fields. This is problematic for accessibility.
Expected behavior
The common validators should reflect their validation to the input field.
E.g. Validators.required =>
What is the motivation / use case for changing the behavior?
For the project I'm currently working on, we have very strict accessibility rules. It would help, if angular itself could provide the functionality of reflecting the defined validators.
This solves the described issue in #9121
Proposal
The idea is based around the Reflect Metadata API.
I would appreciate feedback.
If there is a consensus, I will implement this for a pull request.
Add interpretable metadata to the validators
Use Reflect to add metadata to the validators. The metadata would be similar to the error object returned, when the validation fails.
Validators.compose (and also Validators.composeAsync) would aggregate the metadata of the given validators and define the metadata of the merged validator.
e.g.:
/**
* Validator that requires controls to have a non-empty value.
*/
@AccessibilityMeta({'required': true})
static required(control: AbstractControl): ValidationErrors|null {
return isEmptyInputValue(control.value) ? {'required': true} : null;
}
...
/**
* Validator that requires controls to have a value of a maximum length.
*/
static maxLength(maxLength: number): ValidatorFn {
const validator = (control: AbstractControl): ValidationErrors | null => {
...
};
defineAccessibilityMeta(validator, {'maxlength': maxLength});
return validator;
}
...
/**
* Compose multiple validators into a single function that returns the union
* of the individual error maps.
*/
static compose(validators: null): null;
static compose(validators: (ValidatorFn|null|undefined)[]): ValidatorFn|null;
static compose(validators: (ValidatorFn|null|undefined)[]|null): ValidatorFn|null {
...
const mergedAccessibilityMeta = aggregateAccessibilityMeta(presentValidators);
const validator = function(control: AbstractControl) {
return _mergeErrors(_executeValidators(control, presentValidators));
};
defineAccessibilityMeta(validator, mergedAccessibilityMeta);
return validator;
}
Create accessibility interpreter service
Implement a service, which maps the metadata to aria and validation attributes and also compares them with a whitelist of allowed attributes, based on input type.
e.g.:
{'required': true} -> required, aria-required
{'maxlength': 15} -> maxlength="15"
input[type=text]
{'required': true, 'min': 3} -> required, aria-required // 'min' has no purpose for text inputs
Create directive for form control accessibility
Implement a new directive to render the appropriate attributes. This directive resolves the FormControl instance and the accessibility interpreter service.
a) Define new selector (formControlAccessibilty) in order to allow an opt-in scenario
e.g.:
@Directive({
selector: 'input[formControlAccessibilty],textarea[formControlAccessibilty]',
})
export class FormAccessiblityDirective {
@Input() type: string;
...
}
b) Reuse existing form control selectors
This could be added to its own module (e.g. FormsAccessiblityModule), together with the service, in order to make it opt-in.
e.g.:
@Directive({
selector: 'input[formControlName],textarea[formControlName],input[formControl],textarea[formControl]',
})
export class FormAccessiblityDirective {
@Input() type: string;
...
}
Open Questions
What happens when the validators are replaced or cleared? One possible solution would be additional EventEmitters for validator and asyncValidator.