Skip to content

Commit 3b037d5

Browse files
crisbetommalerba
authored andcommitted
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.
1 parent 152f14e commit 3b037d5

File tree

1 file changed

+18
-5
lines changed

1 file changed

+18
-5
lines changed

src/lib/form-field/form-field.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
Inject,
2222
InjectionToken,
2323
Input,
24+
NgZone,
2425
Optional,
2526
QueryList,
2627
ViewChild,
@@ -233,8 +234,9 @@ export class MatFormField extends _MatFormFieldMixinBase
233234
@Optional() private _dir: Directionality,
234235
@Optional() @Inject(MAT_FORM_FIELD_DEFAULT_OPTIONS) private _defaultOptions:
235236
MatFormFieldDefaultOptions,
236-
// @deletion-target 7.0.0 _platform to be made required.
237-
private _platform?: Platform) {
237+
// @deletion-target 7.0.0 _platform and _ngZone to be made required.
238+
private _platform?: Platform,
239+
private _ngZone?: NgZone) {
238240
super(_elementRef);
239241

240242
this._labelOptions = labelOptions ? labelOptions : {};
@@ -283,8 +285,19 @@ export class MatFormField extends _MatFormFieldMixinBase
283285

284286
ngAfterContentChecked() {
285287
this._validateControlChild();
288+
286289
if (!this._initialGapCalculated) {
287-
Promise.resolve().then(() => this.updateOutlineGap());
290+
// @deletion-target 7.0.0 Remove this check and else block once _ngZone is required.
291+
if (this._ngZone) {
292+
// It's important that we run this outside the `_ngZone`, because the `Promise.resolve`
293+
// can kick us into an infinite change detection loop, if the `_initialGapCalculated`
294+
// wasn't flipped on for some reason.
295+
this._ngZone.runOutsideAngular(() => {
296+
Promise.resolve().then(() => this.updateOutlineGap());
297+
});
298+
} else {
299+
Promise.resolve().then(() => this.updateOutlineGap());
300+
}
288301
}
289302
}
290303

@@ -296,8 +309,8 @@ export class MatFormField extends _MatFormFieldMixinBase
296309

297310
/** Determines whether a class from the NgControl should be forwarded to the host element. */
298311
_shouldForward(prop: string): boolean {
299-
let ngControl = this._control ? this._control.ngControl : null;
300-
return ngControl && (ngControl as any)[prop];
312+
const ngControl = this._control ? this._control.ngControl : null;
313+
return ngControl && ngControl[prop];
301314
}
302315

303316
_hasPlaceholder() {

0 commit comments

Comments
 (0)