@@ -26,6 +26,7 @@ import {
2626 QueryList ,
2727 ViewChild ,
2828 ViewEncapsulation ,
29+ OnDestroy ,
2930} from '@angular/core' ;
3031import {
3132 CanColor , CanColorCtor ,
@@ -34,8 +35,8 @@ import {
3435 MAT_LABEL_GLOBAL_OPTIONS ,
3536 mixinColor ,
3637} from '@angular/material/core' ;
37- import { fromEvent , merge } from 'rxjs' ;
38- import { startWith , take } from 'rxjs/operators' ;
38+ import { fromEvent , merge , Subject } from 'rxjs' ;
39+ import { startWith , take , takeUntil } from 'rxjs/operators' ;
3940import { MatError } from './error' ;
4041import { matFormFieldAnimations } from './form-field-animations' ;
4142import { MatFormFieldControl } from './form-field-control' ;
@@ -141,9 +142,18 @@ export const MAT_FORM_FIELD_DEFAULT_OPTIONS =
141142} )
142143
143144export class MatFormField extends _MatFormFieldMixinBase
144- implements AfterContentInit , AfterContentChecked , AfterViewInit , CanColor {
145+ implements AfterContentInit , AfterContentChecked , AfterViewInit , OnDestroy , CanColor {
145146 private _labelOptions : LabelOptions ;
146- private _outlineGapCalculationNeeded = false ;
147+
148+ /**
149+ * Whether the outline gap needs to be calculated
150+ * immediately on the next change detection run.
151+ */
152+ private _outlineGapCalculationNeededImmediately = false ;
153+
154+ /** Whether the outline gap needs to be calculated next time the zone has stabilized. */
155+ private _outlineGapCalculationNeededOnStable = false ;
156+ private _destroyed = new Subject < void > ( ) ;
147157
148158 /** The form-field appearance style. */
149159 @Input ( )
@@ -283,7 +293,19 @@ export class MatFormField extends _MatFormFieldMixinBase
283293
284294 // Run change detection if the value changes.
285295 if ( control . ngControl && control . ngControl . valueChanges ) {
286- control . ngControl . valueChanges . subscribe ( ( ) => this . _changeDetectorRef . markForCheck ( ) ) ;
296+ control . ngControl . valueChanges
297+ . pipe ( takeUntil ( this . _destroyed ) )
298+ . subscribe ( ( ) => this . _changeDetectorRef . markForCheck ( ) ) ;
299+ }
300+
301+ // @breaking -change 7.0.0 Remove this check once _ngZone is required. Also reconsider
302+ // whether the `ngAfterContentChecked` below is still necessary.
303+ if ( this . _ngZone ) {
304+ this . _ngZone . onStable . asObservable ( ) . pipe ( takeUntil ( this . _destroyed ) ) . subscribe ( ( ) => {
305+ if ( this . _outlineGapCalculationNeededOnStable ) {
306+ this . updateOutlineGap ( ) ;
307+ }
308+ } ) ;
287309 }
288310
289311 // Run change detection and update the outline if the suffix or prefix changes.
@@ -307,7 +329,7 @@ export class MatFormField extends _MatFormFieldMixinBase
307329
308330 ngAfterContentChecked ( ) {
309331 this . _validateControlChild ( ) ;
310- if ( this . _outlineGapCalculationNeeded ) {
332+ if ( this . _outlineGapCalculationNeededImmediately ) {
311333 this . updateOutlineGap ( ) ;
312334 }
313335 }
@@ -318,6 +340,11 @@ export class MatFormField extends _MatFormFieldMixinBase
318340 this . _changeDetectorRef . detectChanges ( ) ;
319341 }
320342
343+ ngOnDestroy ( ) {
344+ this . _destroyed . next ( ) ;
345+ this . _destroyed . complete ( ) ;
346+ }
347+
321348 /** Determines whether a class from the NgControl should be forwarded to the host element. */
322349 _shouldForward ( prop : keyof NgControl ) : boolean {
323350 const ngControl = this . _control ? this . _control . ngControl : null ;
@@ -468,19 +495,33 @@ export class MatFormField extends _MatFormFieldMixinBase
468495 // If the element is not present in the DOM, the outline gap will need to be calculated
469496 // the next time it is checked and in the DOM.
470497 if ( ! document . documentElement ! . contains ( this . _elementRef . nativeElement ) ) {
471- this . _outlineGapCalculationNeeded = true ;
498+ this . _outlineGapCalculationNeededImmediately = true ;
472499 return ;
473500 }
474501
475502 let startWidth = 0 ;
476503 let gapWidth = 0 ;
477- const startEls = this . _connectionContainerRef . nativeElement . querySelectorAll (
478- '.mat-form-field-outline-start' ) ;
479- const gapEls = this . _connectionContainerRef . nativeElement . querySelectorAll (
480- '.mat-form-field-outline-gap' ) ;
504+
505+ const container = this . _connectionContainerRef . nativeElement ;
506+ const startEls = container . querySelectorAll ( '.mat-form-field-outline-start' ) ;
507+ const gapEls = container . querySelectorAll ( '.mat-form-field-outline-gap' ) ;
508+
481509 if ( this . _label && this . _label . nativeElement . children . length ) {
482- const containerStart = this . _getStartEnd (
483- this . _connectionContainerRef . nativeElement . getBoundingClientRect ( ) ) ;
510+ const containerRect = container . getBoundingClientRect ( ) ;
511+
512+ // If the container's width and height are zero, it means that the element is
513+ // invisible and we can't calculate the outline gap. Mark the element as needing
514+ // to be checked the next time the zone stabilizes. We can't do this immediately
515+ // on the next change detection, because even if the element becomes visible,
516+ // the `ClientRect` won't be reclaculated immediately. We reset the
517+ // `_outlineGapCalculationNeededImmediately` flag some we don't run the checks twice.
518+ if ( containerRect . width === 0 && containerRect . height === 0 ) {
519+ this . _outlineGapCalculationNeededOnStable = true ;
520+ this . _outlineGapCalculationNeededImmediately = false ;
521+ return ;
522+ }
523+
524+ const containerStart = this . _getStartEnd ( containerRect ) ;
484525 const labelStart = this . _getStartEnd ( labelEl . children [ 0 ] . getBoundingClientRect ( ) ) ;
485526 let labelWidth = 0 ;
486527
@@ -498,19 +539,23 @@ export class MatFormField extends _MatFormFieldMixinBase
498539 gapEls . item ( i ) . style . width = `${ gapWidth } px` ;
499540 }
500541
501- this . _outlineGapCalculationNeeded = false ;
542+ this . _outlineGapCalculationNeededOnStable =
543+ this . _outlineGapCalculationNeededImmediately = false ;
502544 }
503545
504546 /** Gets the start end of the rect considering the current directionality. */
505547 private _getStartEnd ( rect : ClientRect ) : number {
506548 return this . _dir && this . _dir . value === 'rtl' ? rect . right : rect . left ;
507549 }
508550
509- /** Updates the outline gap the new time the zone stabilizes. */
551+ /**
552+ * Updates the outline gap the new time the zone stabilizes.
553+ * @breaking -change 7.0.0 Remove this method and only set the property once `_ngZone` is required.
554+ */
510555 private _updateOutlineGapOnStable ( ) {
511556 // @breaking -change 8.0.0 Remove this check and else block once _ngZone is required.
512557 if ( this . _ngZone ) {
513- this . _ngZone . onStable . pipe ( take ( 1 ) ) . subscribe ( ( ) => this . updateOutlineGap ( ) ) ;
558+ this . _outlineGapCalculationNeededOnStable = true ;
514559 } else {
515560 Promise . resolve ( ) . then ( ( ) => this . updateOutlineGap ( ) ) ;
516561 }
0 commit comments