Skip to content

Proposal: Reactive Forms Accessiblity  #18114

@kyubisation

Description

@kyubisation

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions