@@ -27,6 +27,9 @@ import {
27
27
ViewChild ,
28
28
ViewEncapsulation ,
29
29
ANIMATION_MODULE_TYPE ,
30
+ afterNextRender ,
31
+ inject ,
32
+ Injector ,
30
33
} from '@angular/core' ;
31
34
import { AbstractControlDirective } from '@angular/forms' ;
32
35
import { ThemePalette } from '@angular/material/core' ;
@@ -245,7 +248,7 @@ export class MatFormField
245
248
// If the appearance has been switched to `outline`, the label offset needs to be updated.
246
249
// The update can happen once the view has been re-checked, but not immediately because
247
250
// the view has not been updated and the notched-outline floating label is not present.
248
- this . _needsOutlineLabelOffsetUpdateOnStable = true ;
251
+ this . _updateOutlineLabelOffsetAfterNextRender ( ) ;
249
252
}
250
253
}
251
254
private _appearance : MatFormFieldAppearance = DEFAULT_APPEARANCE ;
@@ -300,12 +303,17 @@ export class MatFormField
300
303
private _destroyed = new Subject < void > ( ) ;
301
304
private _isFocused : boolean | null = null ;
302
305
private _explicitFormFieldControl : MatFormFieldControl < any > ;
303
- private _needsOutlineLabelOffsetUpdateOnStable = false ;
306
+
307
+ private _injector = inject ( Injector ) ;
304
308
305
309
constructor (
306
310
public _elementRef : ElementRef ,
307
311
private _changeDetectorRef : ChangeDetectorRef ,
308
- private _ngZone : NgZone ,
312
+ /**
313
+ * @deprecated not needed, to be removed.
314
+ * @breaking -change 19.0.0 remove this param
315
+ */
316
+ _unusedNgZone : NgZone ,
309
317
private _dir : Directionality ,
310
318
private _platform : Platform ,
311
319
@Optional ( )
@@ -485,30 +493,13 @@ export class MatFormField
485
493
* The floating label in the docked state needs to account for prefixes. The horizontal offset
486
494
* is calculated whenever the appearance changes to `outline`, the prefixes change, or when the
487
495
* form field is added to the DOM. This method sets up all subscriptions which are needed to
488
- * trigger the label offset update. In general, we want to avoid performing measurements often,
489
- * so we rely on the `NgZone` as indicator when the offset should be recalculated, instead of
490
- * checking every change detection cycle.
496
+ * trigger the label offset update.
491
497
*/
492
498
private _initializeOutlineLabelOffsetSubscriptions ( ) {
493
- // Whenever the prefix changes, schedule an update of the label offset.
494
- this . _prefixChildren . changes . subscribe (
495
- ( ) => ( this . _needsOutlineLabelOffsetUpdateOnStable = true ) ,
496
- ) ;
497
-
498
- // Note that we have to run outside of the `NgZone` explicitly, in order to avoid
499
- // throwing users into an infinite loop if `zone-patch-rxjs` is included.
500
- this . _ngZone . runOutsideAngular ( ( ) => {
501
- this . _ngZone . onStable . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => {
502
- if ( this . _needsOutlineLabelOffsetUpdateOnStable ) {
503
- this . _needsOutlineLabelOffsetUpdateOnStable = false ;
504
- this . _updateOutlineLabelOffset ( ) ;
505
- }
506
- } ) ;
507
- } ) ;
508
-
499
+ this . _prefixChildren . changes . subscribe ( ( ) => this . _updateOutlineLabelOffsetAfterNextRender ( ) ) ;
509
500
this . _dir . change
510
501
. pipe ( takeUntil ( this . _destroyed ) )
511
- . subscribe ( ( ) => ( this . _needsOutlineLabelOffsetUpdateOnStable = true ) ) ;
502
+ . subscribe ( ( ) => this . _updateOutlineLabelOffsetAfterNextRender ( ) ) ;
512
503
}
513
504
514
505
/** Whether the floating label should always float or not. */
@@ -652,41 +643,50 @@ export class MatFormField
652
643
* not need to do this because they use a fixed width for prefixes. Hence, they can simply
653
644
* incorporate the horizontal offset into their default text-field styles.
654
645
*/
655
- private _updateOutlineLabelOffset ( ) {
656
- if ( ! this . _platform . isBrowser || ! this . _hasOutline ( ) || ! this . _floatingLabel ) {
657
- return ;
658
- }
659
- const floatingLabel = this . _floatingLabel . element ;
660
- // If no prefix is displayed, reset the outline label offset from potential
661
- // previous label offset updates.
662
- if ( ! ( this . _iconPrefixContainer || this . _textPrefixContainer ) ) {
663
- floatingLabel . style . transform = '' ;
664
- return ;
665
- }
666
- // If the form field is not attached to the DOM yet (e.g. in a tab), we defer
667
- // the label offset update until the zone stabilizes.
668
- if ( ! this . _isAttachedToDom ( ) ) {
669
- this . _needsOutlineLabelOffsetUpdateOnStable = true ;
670
- return ;
671
- }
672
- const iconPrefixContainer = this . _iconPrefixContainer ?. nativeElement ;
673
- const textPrefixContainer = this . _textPrefixContainer ?. nativeElement ;
674
- const iconPrefixContainerWidth = iconPrefixContainer ?. getBoundingClientRect ( ) . width ?? 0 ;
675
- const textPrefixContainerWidth = textPrefixContainer ?. getBoundingClientRect ( ) . width ?? 0 ;
676
- // If the directionality is RTL, the x-axis transform needs to be inverted. This
677
- // is because `transformX` does not change based on the page directionality.
678
- const negate = this . _dir . value === 'rtl' ? '-1' : '1' ;
679
- const prefixWidth = `${ iconPrefixContainerWidth + textPrefixContainerWidth } px` ;
680
- const labelOffset = `var(--mat-mdc-form-field-label-offset-x, 0px)` ;
681
- const labelHorizontalOffset = `calc(${ negate } * (${ prefixWidth } + ${ labelOffset } ))` ;
682
-
683
- // Update the translateX of the floating label to account for the prefix container,
684
- // but allow the CSS to override this setting via a CSS variable when the label is
685
- // floating.
686
- floatingLabel . style . transform = `var(
687
- --mat-mdc-form-field-label-transform,
688
- ${ FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM } translateX(${ labelHorizontalOffset } )
689
- )` ;
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
+ ) ;
690
690
}
691
691
692
692
/** Checks whether the form field is attached to the DOM. */
0 commit comments