@@ -19,6 +19,8 @@ import {
19
19
ElementRef ,
20
20
Inject ,
21
21
InjectionToken ,
22
+ Injector ,
23
+ inject ,
22
24
Input ,
23
25
NgZone ,
24
26
OnDestroy ,
@@ -27,9 +29,7 @@ import {
27
29
ViewChild ,
28
30
ViewEncapsulation ,
29
31
ANIMATION_MODULE_TYPE ,
30
- afterNextRender ,
31
- inject ,
32
- Injector ,
32
+ afterRender ,
33
33
} from '@angular/core' ;
34
34
import { AbstractControlDirective } from '@angular/forms' ;
35
35
import { ThemePalette } from '@angular/material/core' ;
@@ -248,7 +248,7 @@ export class MatFormField
248
248
// If the appearance has been switched to `outline`, the label offset needs to be updated.
249
249
// The update can happen once the view has been re-checked, but not immediately because
250
250
// the view has not been updated and the notched-outline floating label is not present.
251
- this . _updateOutlineLabelOffsetAfterNextRender ( ) ;
251
+ this . _needsOutlineLabelOffsetUpdate = true ;
252
252
}
253
253
}
254
254
private _appearance : MatFormFieldAppearance = DEFAULT_APPEARANCE ;
@@ -303,6 +303,7 @@ export class MatFormField
303
303
private _destroyed = new Subject < void > ( ) ;
304
304
private _isFocused : boolean | null = null ;
305
305
private _explicitFormFieldControl : MatFormFieldControl < any > ;
306
+ private _needsOutlineLabelOffsetUpdate = false ;
306
307
307
308
private _injector = inject ( Injector ) ;
308
309
@@ -496,10 +497,26 @@ export class MatFormField
496
497
* trigger the label offset update.
497
498
*/
498
499
private _initializeOutlineLabelOffsetSubscriptions ( ) {
499
- this . _prefixChildren . changes . subscribe ( ( ) => this . _updateOutlineLabelOffsetAfterNextRender ( ) ) ;
500
+ // Whenever the prefix changes, schedule an update of the label offset.
501
+ this . _prefixChildren . changes . subscribe ( ( ) => ( this . _needsOutlineLabelOffsetUpdate = true ) ) ;
502
+
503
+ // TODO(mmalerba): Split this into separate `afterRender` calls using the
504
+ // `EarlyRead` and `Write` phases.
505
+ afterRender (
506
+ ( ) => {
507
+ if ( this . _needsOutlineLabelOffsetUpdate ) {
508
+ this . _needsOutlineLabelOffsetUpdate = false ;
509
+ this . _updateOutlineLabelOffset ( ) ;
510
+ }
511
+ } ,
512
+ {
513
+ injector : this . _injector ,
514
+ } ,
515
+ ) ;
516
+
500
517
this . _dir . change
501
518
. pipe ( takeUntil ( this . _destroyed ) )
502
- . subscribe ( ( ) => this . _updateOutlineLabelOffsetAfterNextRender ( ) ) ;
519
+ . subscribe ( ( ) => ( this . _needsOutlineLabelOffsetUpdate = true ) ) ;
503
520
}
504
521
505
522
/** Whether the floating label should always float or not. */
@@ -643,50 +660,41 @@ export class MatFormField
643
660
* not need to do this because they use a fixed width for prefixes. Hence, they can simply
644
661
* incorporate the horizontal offset into their default text-field styles.
645
662
*/
646
- private _updateOutlineLabelOffsetAfterNextRender ( ) {
647
- // TODO(mmalerba): Split this into separate `afterNextRender` calls using the `EarlyRead` and
648
- // `Write` phases.
649
- afterNextRender (
650
- ( ) => {
651
- if ( ! this . _hasOutline ( ) || ! this . _floatingLabel ) {
652
- return ;
653
- }
654
- const floatingLabel = this . _floatingLabel . element ;
655
- // If no prefix is displayed, reset the outline label offset from potential
656
- // previous label offset updates.
657
- if ( ! ( this . _iconPrefixContainer || this . _textPrefixContainer ) ) {
658
- floatingLabel . style . transform = '' ;
659
- return ;
660
- }
661
- // If the form field is not attached to the DOM yet (e.g. in a tab), we defer
662
- // the label offset update until after the next render.
663
- if ( ! this . _isAttachedToDom ( ) ) {
664
- this . _updateOutlineLabelOffsetAfterNextRender ( ) ;
665
- return ;
666
- }
667
- const iconPrefixContainer = this . _iconPrefixContainer ?. nativeElement ;
668
- const textPrefixContainer = this . _textPrefixContainer ?. nativeElement ;
669
- const iconPrefixContainerWidth = iconPrefixContainer ?. getBoundingClientRect ( ) . width ?? 0 ;
670
- const textPrefixContainerWidth = textPrefixContainer ?. getBoundingClientRect ( ) . width ?? 0 ;
671
- // If the directionality is RTL, the x-axis transform needs to be inverted. This
672
- // is because `transformX` does not change based on the page directionality.
673
- const negate = this . _dir . value === 'rtl' ? '-1' : '1' ;
674
- const prefixWidth = `${ iconPrefixContainerWidth + textPrefixContainerWidth } px` ;
675
- const labelOffset = `var(--mat-mdc-form-field-label-offset-x, 0px)` ;
676
- const labelHorizontalOffset = `calc(${ negate } * (${ prefixWidth } + ${ labelOffset } ))` ;
677
-
678
- // Update the translateX of the floating label to account for the prefix container,
679
- // but allow the CSS to override this setting via a CSS variable when the label is
680
- // floating.
681
- floatingLabel . style . transform = `var(
682
- --mat-mdc-form-field-label-transform,
683
- ${ FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM } translateX(${ labelHorizontalOffset } )
684
- )` ;
685
- } ,
686
- {
687
- injector : this . _injector ,
688
- } ,
689
- ) ;
663
+ private _updateOutlineLabelOffset ( ) {
664
+ if ( ! this . _hasOutline ( ) || ! this . _floatingLabel ) {
665
+ return ;
666
+ }
667
+ const floatingLabel = this . _floatingLabel . element ;
668
+ // If no prefix is displayed, reset the outline label offset from potential
669
+ // previous label offset updates.
670
+ if ( ! ( this . _iconPrefixContainer || this . _textPrefixContainer ) ) {
671
+ floatingLabel . style . transform = '' ;
672
+ return ;
673
+ }
674
+ // If the form field is not attached to the DOM yet (e.g. in a tab), we defer
675
+ // the label offset update until the zone stabilizes.
676
+ if ( ! this . _isAttachedToDom ( ) ) {
677
+ this . _needsOutlineLabelOffsetUpdate = true ;
678
+ return ;
679
+ }
680
+ const iconPrefixContainer = this . _iconPrefixContainer ?. nativeElement ;
681
+ const textPrefixContainer = this . _textPrefixContainer ?. nativeElement ;
682
+ const iconPrefixContainerWidth = iconPrefixContainer ?. getBoundingClientRect ( ) . width ?? 0 ;
683
+ const textPrefixContainerWidth = textPrefixContainer ?. getBoundingClientRect ( ) . width ?? 0 ;
684
+ // If the directionality is RTL, the x-axis transform needs to be inverted. This
685
+ // is because `transformX` does not change based on the page directionality.
686
+ const negate = this . _dir . value === 'rtl' ? '-1' : '1' ;
687
+ const prefixWidth = `${ iconPrefixContainerWidth + textPrefixContainerWidth } px` ;
688
+ const labelOffset = `var(--mat-mdc-form-field-label-offset-x, 0px)` ;
689
+ const labelHorizontalOffset = `calc(${ negate } * (${ prefixWidth } + ${ labelOffset } ))` ;
690
+
691
+ // Update the translateX of the floating label to account for the prefix container,
692
+ // but allow the CSS to override this setting via a CSS variable when the label is
693
+ // floating.
694
+ floatingLabel . style . transform = `var(
695
+ --mat-mdc-form-field-label-transform,
696
+ ${ FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM } translateX(${ labelHorizontalOffset } )
697
+ )` ;
690
698
}
691
699
692
700
/** Checks whether the form field is attached to the DOM. */
0 commit comments