@@ -36,7 +36,7 @@ import {AbstractControlDirective} from '@angular/forms';
36
36
import { ThemePalette } from '@angular/material/core' ;
37
37
import { _IdGenerator } from '@angular/cdk/a11y' ;
38
38
import { Subject , Subscription , merge } from 'rxjs' ;
39
- import { takeUntil } from 'rxjs/operators' ;
39
+ import { map , pairwise , takeUntil , filter , startWith } from 'rxjs/operators' ;
40
40
import { MAT_ERROR , MatError } from './directives/error' ;
41
41
import {
42
42
FLOATING_LABEL_PARENT ,
@@ -328,6 +328,7 @@ export class MatFormField
328
328
private _previousControl : MatFormFieldControl < unknown > | null = null ;
329
329
private _stateChanges : Subscription | undefined ;
330
330
private _valueChanges : Subscription | undefined ;
331
+ private _describedByChanges : Subscription | undefined ;
331
332
332
333
private _injector = inject ( Injector ) ;
333
334
@@ -377,6 +378,7 @@ export class MatFormField
377
378
ngOnDestroy ( ) {
378
379
this . _stateChanges ?. unsubscribe ( ) ;
379
380
this . _valueChanges ?. unsubscribe ( ) ;
381
+ this . _describedByChanges ?. unsubscribe ( ) ;
380
382
this . _destroyed . next ( ) ;
381
383
this . _destroyed . complete ( ) ;
382
384
}
@@ -426,10 +428,22 @@ export class MatFormField
426
428
this . _stateChanges ?. unsubscribe ( ) ;
427
429
this . _stateChanges = control . stateChanges . subscribe ( ( ) => {
428
430
this . _updateFocusState ( ) ;
429
- this . _syncDescribedByIds ( ) ;
430
431
this . _changeDetectorRef . markForCheck ( ) ;
431
432
} ) ;
432
433
434
+ // Updating the `aria-describedby` touches the DOM. Only do it if it actually needs to change.
435
+ this . _describedByChanges ?. unsubscribe ( ) ;
436
+ this . _describedByChanges = control . stateChanges
437
+ . pipe (
438
+ startWith ( [ undefined , undefined ] as const ) ,
439
+ map ( ( ) => [ control . errorState , control . userAriaDescribedBy ] as const ) ,
440
+ pairwise ( ) ,
441
+ filter ( ( [ [ prevErrorState , prevDescribedBy ] , [ currentErrorState , currentDescribedBy ] ] ) => {
442
+ return prevErrorState !== currentErrorState || prevDescribedBy !== currentDescribedBy ;
443
+ } ) ,
444
+ )
445
+ . subscribe ( ( ) => this . _syncDescribedByIds ( ) ) ;
446
+
433
447
this . _valueChanges ?. unsubscribe ( ) ;
434
448
435
449
// Run change detection if the value changes.
0 commit comments