@@ -309,7 +309,7 @@ export class MatFormField
309
309
readonly _hintLabelId = this . _idGenerator . getId ( 'mat-mdc-hint-' ) ;
310
310
311
311
// Ids obtained from the fields
312
- private _describedByIds : Array < String > = [ ] ;
312
+ private _describedByIds : string [ ] | undefined ;
313
313
314
314
/** Gets the current form field control */
315
315
get _control ( ) : MatFormFieldControl < any > {
@@ -708,16 +708,21 @@ export class MatFormField
708
708
ids . push ( ...this . _errorChildren . map ( error => error . id ) ) ;
709
709
}
710
710
711
- let currentIds = this . _control . describedByIds ?? [ ] ;
712
- let combinedIds : Array < string > = [ ] ;
713
- currentIds . forEach ( id => {
714
- if ( ! this . _describedByIds . includes ( id ) ) {
715
- combinedIds . push ( id ) ;
716
- }
717
- } ) ;
718
- combinedIds . concat ( ...ids ) ;
711
+ const existingDescribedBy = this . _control . describedByIds ;
712
+ let toAssign : string [ ] ;
713
+
714
+ // In some cases there might be some `aria-describedby` IDs that were assigned directly,
715
+ // like by the `AriaDescriber` (see #30011). Attempt to preserve them by taking the previous
716
+ // attribute value and filtering out the IDs that came from the previous `setDescribedByIds`
717
+ // call. Note the `|| ids` here allows us to avoid duplicating IDs on the first render.
718
+ if ( existingDescribedBy ) {
719
+ const exclude = this . _describedByIds || ids ;
720
+ toAssign = ids . concat ( existingDescribedBy . filter ( id => id && ! exclude . includes ( id ) ) ) ;
721
+ } else {
722
+ toAssign = ids ;
723
+ }
719
724
720
- this . _control . setDescribedByIds ( combinedIds ) ;
725
+ this . _control . setDescribedByIds ( toAssign ) ;
721
726
this . _describedByIds = ids ;
722
727
}
723
728
}
0 commit comments