From e592615c542052edf48186a95e5d1b2e987dbe02 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 24 May 2018 05:42:29 +0200 Subject: [PATCH] fix(form-field): infinite loop when using outline appearance and element isn't in the DOM (#11406) Prevents the form field with an `outline` appearance from going into an infinite change detection loop if the form field is active, but isn't in the DOM. The issue comes from the fact that we've got a `Promise.resolve` inside an `AfterContentChecked` hook, which will kick off another round of change detection, causing the lifecycle hook to be called again. Fixes #11329. --- src/lib/form-field/form-field.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/lib/form-field/form-field.ts b/src/lib/form-field/form-field.ts index 0bd3a5be8f4..fb77d14d5cd 100644 --- a/src/lib/form-field/form-field.ts +++ b/src/lib/form-field/form-field.ts @@ -21,6 +21,7 @@ import { Inject, InjectionToken, Input, + NgZone, Optional, QueryList, ViewChild, @@ -233,8 +234,9 @@ export class MatFormField extends _MatFormFieldMixinBase @Optional() private _dir: Directionality, @Optional() @Inject(MAT_FORM_FIELD_DEFAULT_OPTIONS) private _defaultOptions: MatFormFieldDefaultOptions, - // @deletion-target 7.0.0 _platform to be made required. - private _platform?: Platform) { + // @deletion-target 7.0.0 _platform and _ngZone to be made required. + private _platform?: Platform, + private _ngZone?: NgZone) { super(_elementRef); this._labelOptions = labelOptions ? labelOptions : {}; @@ -283,8 +285,19 @@ export class MatFormField extends _MatFormFieldMixinBase ngAfterContentChecked() { this._validateControlChild(); + if (!this._initialGapCalculated) { - Promise.resolve().then(() => this.updateOutlineGap()); + // @deletion-target 7.0.0 Remove this check and else block once _ngZone is required. + if (this._ngZone) { + // It's important that we run this outside the `_ngZone`, because the `Promise.resolve` + // can kick us into an infinite change detection loop, if the `_initialGapCalculated` + // wasn't flipped on for some reason. + this._ngZone.runOutsideAngular(() => { + Promise.resolve().then(() => this.updateOutlineGap()); + }); + } else { + Promise.resolve().then(() => this.updateOutlineGap()); + } } } @@ -296,8 +309,8 @@ export class MatFormField extends _MatFormFieldMixinBase /** Determines whether a class from the NgControl should be forwarded to the host element. */ _shouldForward(prop: string): boolean { - let ngControl = this._control ? this._control.ngControl : null; - return ngControl && (ngControl as any)[prop]; + const ngControl = this._control ? this._control.ngControl : null; + return ngControl && ngControl[prop]; } _hasPlaceholder() {