@@ -309,7 +309,7 @@ export class MatFormField
309309 readonly _hintLabelId = this . _idGenerator . getId ( 'mat-mdc-hint-' ) ;
310310
311311 // Ids obtained from the fields
312- private _describedByIds : Array < String > = [ ] ;
312+ private _describedByIds : string [ ] | undefined ;
313313
314314 /** Gets the current form field control */
315315 get _control ( ) : MatFormFieldControl < any > {
@@ -708,16 +708,21 @@ export class MatFormField
708708 ids . push ( ...this . _errorChildren . map ( error => error . id ) ) ;
709709 }
710710
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+ }
719724
720- this . _control . setDescribedByIds ( combinedIds ) ;
725+ this . _control . setDescribedByIds ( toAssign ) ;
721726 this . _describedByIds = ids ;
722727 }
723728 }
0 commit comments