Skip to content

Commit af8c35f

Browse files
committed
fixup! fix(material/input): do not override existing aria-describedby value
Add documentation
1 parent aec58a2 commit af8c35f

File tree

5 files changed

+34
-17
lines changed

5 files changed

+34
-17
lines changed

guides/creating-a-custom-form-field-control.md

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -339,20 +339,33 @@ controlType = 'example-tel-input';
339339

340340
#### `setDescribedByIds(ids: string[])`
341341

342-
This method is used by the `<mat-form-field>` to specify the IDs that should be used for the
343-
`aria-describedby` attribute of your component. The method has one parameter, the list of IDs, we
344-
just need to apply the given IDs to the element that represents our control.
342+
This method is used by the `<mat-form-field>` to set element ids that should be used for the
343+
`aria-describedby` attribute of your control. The ids are controlled through the form field
344+
as hints or errors are conditionally displayed and should be reflected in the control's
345+
`aria-describedby` attribute for an improved accessibility experience.
345346

346-
In our concrete example, these IDs would need to be applied to the group element.
347+
The `setDescribedByIds` method is invoked whenever the control's state changes. Custom controls
348+
need to implement this method and update the `aria-describedby` attribute based on the specified
349+
element ids. Below is an example that shows how this can be achieved.
350+
351+
Note that the method by default will not respect element ids that have been set manually on the
352+
control element through the `aria-describedby` attribute. To ensure that your control does not
353+
accidentally override existing element ids specified by consumers of your control, create an
354+
input called `userAriaDescribedby` like followed:
347355

348356
```ts
349-
setDescribedByIds(ids: string[]) {
350-
this.describedBy = ids.join(' ');
351-
}
357+
@Input('aria-describedby') userAriaDescribedBy: string;
352358
```
353359

354-
```html
355-
<div role="group" [formGroup]="parts" [attr.aria-describedby]="describedBy">
360+
The form field will then pick up the user specified `aria-describedby` ids and merge
361+
them with ids for hints or errors whenever `setDescribedByIds` is invoked.
362+
363+
```ts
364+
setDescribedByIds(ids: string[]) {
365+
const controlElement = this._elementRef.nativeElement
366+
.querySelector('.example-tel-input-container')!;
367+
controlElement.setAttribute('aria-describedby', ids.join(' '));
368+
}
356369
```
357370

358371
#### `onContainerClick(event: MouseEvent)`

src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/example-tel-input-example.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<div role="group" class="example-tel-input-container"
22
[formGroup]="parts"
3-
[attr.aria-labelledby]="_formField?.getLabelId()"
4-
[attr.aria-describedby]="describedBy">
3+
[attr.aria-labelledby]="_formField?.getLabelId()">
54
<input
65
class="example-tel-input-element"
76
formControlName="area" size="3"

src/components-examples/material-experimental/mdc-form-field/mdc-form-field-custom-control/form-field-custom-control-example.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export class MyTelInput implements ControlValueAccessor, MatFormFieldControl<MyT
3838
errorState = false;
3939
controlType = 'example-tel-input';
4040
id = `example-tel-input-${MyTelInput.nextId++}`;
41-
describedBy = '';
4241
onChange = (_: any) => {};
4342
onTouched = () => {};
4443

@@ -50,6 +49,8 @@ export class MyTelInput implements ControlValueAccessor, MatFormFieldControl<MyT
5049

5150
get shouldLabelFloat() { return this.focused || !this.empty; }
5251

52+
@Input('aria-describedby') userAriaDescribedBy: string;
53+
5354
@Input()
5455
get placeholder(): string { return this._placeholder; }
5556
set placeholder(value: string) {
@@ -121,7 +122,9 @@ export class MyTelInput implements ControlValueAccessor, MatFormFieldControl<MyT
121122
}
122123

123124
setDescribedByIds(ids: string[]) {
124-
this.describedBy = ids.join(' ');
125+
const controlElement = this._elementRef.nativeElement
126+
.querySelector('.example-tel-input-container')!;
127+
controlElement.setAttribute('aria-describedby', ids.join(' '));
125128
}
126129

127130
onContainerClick(event: MouseEvent) {

src/components-examples/material/form-field/form-field-custom-control/example-tel-input-example.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<div role="group" class="example-tel-input-container"
22
[formGroup]="parts"
3-
[attr.aria-labelledby]="_formField?.getLabelId()"
4-
[attr.aria-describedby]="describedBy">
3+
[attr.aria-labelledby]="_formField?.getLabelId()">
54
<input class="example-tel-input-element"
65
formControlName="area" size="3"
76
maxLength="3"

src/components-examples/material/form-field/form-field-custom-control/form-field-custom-control-example.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ export class MyTelInput
6666
errorState = false;
6767
controlType = 'example-tel-input';
6868
id = `example-tel-input-${MyTelInput.nextId++}`;
69-
describedBy = '';
7069
onChange = (_: any) => {};
7170
onTouched = () => {};
7271

@@ -82,6 +81,8 @@ export class MyTelInput
8281
return this.focused || !this.empty;
8382
}
8483

84+
@Input('aria-describedby') userAriaDescribedBy: string;
85+
8586
@Input()
8687
get placeholder(): string {
8788
return this._placeholder;
@@ -182,7 +183,9 @@ export class MyTelInput
182183
}
183184

184185
setDescribedByIds(ids: string[]) {
185-
this.describedBy = ids.join(' ');
186+
const controlElement = this._elementRef.nativeElement
187+
.querySelector('.example-tel-input-container')!;
188+
controlElement.setAttribute('aria-describedby', ids.join(' '));
186189
}
187190

188191
onContainerClick(event: MouseEvent) {

0 commit comments

Comments
 (0)