@@ -325,6 +325,7 @@ export class MatFormField
325
325
private _stateChanges : Subscription | undefined ;
326
326
private _valueChanges : Subscription | undefined ;
327
327
private _describedByChanges : Subscription | undefined ;
328
+ private _labelledByChanges : Subscription | undefined ;
328
329
protected readonly _animationsDisabled : boolean ;
329
330
330
331
constructor ( ...args : unknown [ ] ) ;
@@ -384,6 +385,7 @@ export class MatFormField
384
385
this . _stateChanges ?. unsubscribe ( ) ;
385
386
this . _valueChanges ?. unsubscribe ( ) ;
386
387
this . _describedByChanges ?. unsubscribe ( ) ;
388
+ this . _labelledByChanges ?. unsubscribe ( ) ;
387
389
this . _destroyed . next ( ) ;
388
390
this . _destroyed . complete ( ) ;
389
391
}
@@ -449,6 +451,19 @@ export class MatFormField
449
451
)
450
452
. subscribe ( ( ) => this . _syncDescribedByIds ( ) ) ;
451
453
454
+ // Updating the `aria-labelledby` touches the DOM. Only do it if it actually needs to change.
455
+ this . _labelledByChanges ?. unsubscribe ( ) ;
456
+ this . _labelledByChanges = control . stateChanges
457
+ . pipe (
458
+ startWith ( [ undefined , undefined ] as const ) ,
459
+ map ( ( ) => [ control . errorState , control . userAriaLabelledBy ] as const ) ,
460
+ pairwise ( ) ,
461
+ filter ( ( [ [ prevErrorState , prevLabelledBy ] , [ currentErrorState , currentLabelledBy ] ] ) => {
462
+ return prevErrorState !== currentErrorState || prevLabelledBy !== currentLabelledBy ;
463
+ } ) ,
464
+ )
465
+ . subscribe ( ( ) => this . _syncLabelledByIds ( ) ) ;
466
+
452
467
this . _valueChanges ?. unsubscribe ( ) ;
453
468
454
469
// Run change detection if the value changes.
@@ -493,12 +508,14 @@ export class MatFormField
493
508
// Update the aria-described by when the number of errors changes.
494
509
this . _errorChildren . changes . subscribe ( ( ) => {
495
510
this . _syncDescribedByIds ( ) ;
511
+ this . _syncLabelledByIds ( ) ;
496
512
this . _changeDetectorRef . markForCheck ( ) ;
497
513
} ) ;
498
514
499
515
// Initial mat-hint validation and subscript describedByIds sync.
500
516
this . _validateHints ( ) ;
501
517
this . _syncDescribedByIds ( ) ;
518
+ this . _syncLabelledByIds ( ) ;
502
519
}
503
520
504
521
/** Throws an error if the form field's control is missing. */
@@ -622,6 +639,7 @@ export class MatFormField
622
639
private _processHints ( ) {
623
640
this . _validateHints ( ) ;
624
641
this . _syncDescribedByIds ( ) ;
642
+ this . _syncLabelledByIds ( ) ;
625
643
}
626
644
627
645
/**
@@ -691,6 +709,47 @@ export class MatFormField
691
709
}
692
710
}
693
711
712
+ /**
713
+ * Sets the list of element IDs that describe the child control. This allows the control to update
714
+ * its `aria-describedby` attribute accordingly.
715
+ */
716
+ private _syncLabelledByIds ( ) {
717
+ if ( this . _control ) {
718
+ let ids : string [ ] = [ ] ;
719
+
720
+ // TODO(wagnermaciel): Remove the type check when we find the root cause of this bug.
721
+ if (
722
+ this . _control . userAriaLabelledBy &&
723
+ typeof this . _control . userAriaLabelledBy === 'string'
724
+ ) {
725
+ ids . push ( ...this . _control . userAriaLabelledBy . split ( ' ' ) ) ;
726
+ }
727
+
728
+ if ( this . _getDisplayedMessages ( ) === 'hint' ) {
729
+ const startHint = this . _hintChildren
730
+ ? this . _hintChildren . find ( hint => hint . align === 'start' )
731
+ : null ;
732
+ const endHint = this . _hintChildren
733
+ ? this . _hintChildren . find ( hint => hint . align === 'end' )
734
+ : null ;
735
+
736
+ if ( startHint ) {
737
+ ids . push ( startHint . id ) ;
738
+ } else if ( this . _hintLabel ) {
739
+ ids . push ( this . _hintLabelId ) ;
740
+ }
741
+
742
+ if ( endHint ) {
743
+ ids . push ( endHint . id ) ;
744
+ }
745
+ } else if ( this . _errorChildren ) {
746
+ ids . push ( ...this . _errorChildren . map ( error => error . id ) ) ;
747
+ }
748
+
749
+ this . _control . setLabelledByIds ( ids ) ;
750
+ }
751
+ }
752
+
694
753
/**
695
754
* Updates the horizontal offset of the label in the outline appearance. In the outline
696
755
* appearance, the notched-outline and label are not relative to the infix container because
0 commit comments