@@ -26,6 +26,7 @@ import {
26
26
QueryList ,
27
27
ViewChild ,
28
28
ViewEncapsulation ,
29
+ OnDestroy ,
29
30
} from '@angular/core' ;
30
31
import {
31
32
CanColor , CanColorCtor ,
@@ -34,8 +35,8 @@ import {
34
35
MAT_LABEL_GLOBAL_OPTIONS ,
35
36
mixinColor ,
36
37
} 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' ;
39
40
import { MatError } from './error' ;
40
41
import { matFormFieldAnimations } from './form-field-animations' ;
41
42
import { MatFormFieldControl } from './form-field-control' ;
@@ -141,9 +142,18 @@ export const MAT_FORM_FIELD_DEFAULT_OPTIONS =
141
142
} )
142
143
143
144
export class MatFormField extends _MatFormFieldMixinBase
144
- implements AfterContentInit , AfterContentChecked , AfterViewInit , CanColor {
145
+ implements AfterContentInit , AfterContentChecked , AfterViewInit , OnDestroy , CanColor {
145
146
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 > ( ) ;
147
157
148
158
/** The form-field appearance style. */
149
159
@Input ( )
@@ -283,7 +293,19 @@ export class MatFormField extends _MatFormFieldMixinBase
283
293
284
294
// Run change detection if the value changes.
285
295
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
+ } ) ;
287
309
}
288
310
289
311
// Run change detection and update the outline if the suffix or prefix changes.
@@ -307,7 +329,7 @@ export class MatFormField extends _MatFormFieldMixinBase
307
329
308
330
ngAfterContentChecked ( ) {
309
331
this . _validateControlChild ( ) ;
310
- if ( this . _outlineGapCalculationNeeded ) {
332
+ if ( this . _outlineGapCalculationNeededImmediately ) {
311
333
this . updateOutlineGap ( ) ;
312
334
}
313
335
}
@@ -318,6 +340,11 @@ export class MatFormField extends _MatFormFieldMixinBase
318
340
this . _changeDetectorRef . detectChanges ( ) ;
319
341
}
320
342
343
+ ngOnDestroy ( ) {
344
+ this . _destroyed . next ( ) ;
345
+ this . _destroyed . complete ( ) ;
346
+ }
347
+
321
348
/** Determines whether a class from the NgControl should be forwarded to the host element. */
322
349
_shouldForward ( prop : keyof NgControl ) : boolean {
323
350
const ngControl = this . _control ? this . _control . ngControl : null ;
@@ -468,19 +495,33 @@ export class MatFormField extends _MatFormFieldMixinBase
468
495
// If the element is not present in the DOM, the outline gap will need to be calculated
469
496
// the next time it is checked and in the DOM.
470
497
if ( ! document . documentElement ! . contains ( this . _elementRef . nativeElement ) ) {
471
- this . _outlineGapCalculationNeeded = true ;
498
+ this . _outlineGapCalculationNeededImmediately = true ;
472
499
return ;
473
500
}
474
501
475
502
let startWidth = 0 ;
476
503
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
+
481
509
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 ) ;
484
525
const labelStart = this . _getStartEnd ( labelEl . children [ 0 ] . getBoundingClientRect ( ) ) ;
485
526
let labelWidth = 0 ;
486
527
@@ -498,19 +539,23 @@ export class MatFormField extends _MatFormFieldMixinBase
498
539
gapEls . item ( i ) . style . width = `${ gapWidth } px` ;
499
540
}
500
541
501
- this . _outlineGapCalculationNeeded = false ;
542
+ this . _outlineGapCalculationNeededOnStable =
543
+ this . _outlineGapCalculationNeededImmediately = false ;
502
544
}
503
545
504
546
/** Gets the start end of the rect considering the current directionality. */
505
547
private _getStartEnd ( rect : ClientRect ) : number {
506
548
return this . _dir && this . _dir . value === 'rtl' ? rect . right : rect . left ;
507
549
}
508
550
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
+ */
510
555
private _updateOutlineGapOnStable ( ) {
511
556
// @breaking -change 8.0.0 Remove this check and else block once _ngZone is required.
512
557
if ( this . _ngZone ) {
513
- this . _ngZone . onStable . pipe ( take ( 1 ) ) . subscribe ( ( ) => this . updateOutlineGap ( ) ) ;
558
+ this . _outlineGapCalculationNeededOnStable = true ;
514
559
} else {
515
560
Promise . resolve ( ) . then ( ( ) => this . updateOutlineGap ( ) ) ;
516
561
}
0 commit comments