Skip to content

Commit b1908a7

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

File tree

3 files changed

+26
-11
lines changed

3 files changed

+26
-11
lines changed

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

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,15 +339,30 @@ 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 our host element.
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.
346+
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:
345355

346356
```ts
347-
@HostBinding('attr.aria-describedby') describedBy = '';
357+
@Input('aria-describedby') userAriaDescribedBy: string;
358+
```
348359

360+
The form field will then pick up the user specified `aria-describedby` ids and merge
361+
them with the ids from hints or errors whenever `setDescribedByIds` is invoked.
362+
363+
```ts
349364
setDescribedByIds(ids: string[]) {
350-
this.describedBy = ids.join(' ');
365+
this._elementRef.nativeElement.setAttribute('aria-describedby', ids.join(' '));
351366
}
352367
```
353368

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export class MyTel {
2626
host: {
2727
'[class.example-floating]': 'shouldLabelFloat',
2828
'[id]': 'id',
29-
'[attr.aria-describedby]': 'describedBy',
3029
}
3130
})
3231
export class MyTelInput implements ControlValueAccessor, MatFormFieldControl<MyTel>, OnDestroy {
@@ -38,7 +37,6 @@ export class MyTelInput implements ControlValueAccessor, MatFormFieldControl<MyT
3837
errorState = false;
3938
controlType = 'example-tel-input';
4039
id = `example-tel-input-${MyTelInput.nextId++}`;
41-
describedBy = '';
4240
onChange = (_: any) => {};
4341
onTouched = () => {};
4442

@@ -50,6 +48,8 @@ export class MyTelInput implements ControlValueAccessor, MatFormFieldControl<MyT
5048

5149
get shouldLabelFloat() { return this.focused || !this.empty; }
5250

51+
@Input('aria-describedby') userAriaDescribedBy: string;
52+
5353
@Input()
5454
get placeholder(): string { return this._placeholder; }
5555
set placeholder(value: string) {
@@ -120,7 +120,7 @@ export class MyTelInput implements ControlValueAccessor, MatFormFieldControl<MyT
120120
}
121121

122122
setDescribedByIds(ids: string[]) {
123-
this.describedBy = ids.join(' ');
123+
this._elementRef.nativeElement.setAttribute('aria-describedby', ids.join(' '));
124124
}
125125

126126
onContainerClick(event: MouseEvent) {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ export class MyTel {
5050
host: {
5151
'[class.example-floating]': 'shouldLabelFloat',
5252
'[id]': 'id',
53-
'[attr.aria-describedby]': 'describedBy'
5453
}
5554
})
5655
export class MyTelInput
@@ -66,7 +65,6 @@ export class MyTelInput
6665
errorState = false;
6766
controlType = 'example-tel-input';
6867
id = `example-tel-input-${MyTelInput.nextId++}`;
69-
describedBy = '';
7068
onChange = (_: any) => {};
7169
onTouched = () => {};
7270

@@ -82,6 +80,8 @@ export class MyTelInput
8280
return this.focused || !this.empty;
8381
}
8482

83+
@Input('aria-describedby') userAriaDescribedBy: string;
84+
8585
@Input()
8686
get placeholder(): string {
8787
return this._placeholder;
@@ -181,7 +181,7 @@ export class MyTelInput
181181
}
182182

183183
setDescribedByIds(ids: string[]) {
184-
this.describedBy = ids.join(' ');
184+
this._elementRef.nativeElement.setAttribute('aria-describedby', ids.join(' '));
185185
}
186186

187187
onContainerClick(event: MouseEvent) {

0 commit comments

Comments
 (0)