Skip to content

Commit 950d125

Browse files
authored
refactor(material/form-field): Remove use of zone onStable to schedule outline updates (#28636)
1 parent fc43fff commit 950d125

File tree

2 files changed

+32
-22
lines changed

2 files changed

+32
-22
lines changed

src/material/form-field/form-field.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
ElementRef,
2020
Inject,
2121
InjectionToken,
22+
Injector,
23+
inject,
2224
Input,
2325
NgZone,
2426
OnDestroy,
@@ -27,6 +29,7 @@ import {
2729
ViewChild,
2830
ViewEncapsulation,
2931
ANIMATION_MODULE_TYPE,
32+
afterRender,
3033
} from '@angular/core';
3134
import {AbstractControlDirective} from '@angular/forms';
3235
import {ThemePalette} from '@angular/material/core';
@@ -245,7 +248,7 @@ export class MatFormField
245248
// If the appearance has been switched to `outline`, the label offset needs to be updated.
246249
// The update can happen once the view has been re-checked, but not immediately because
247250
// the view has not been updated and the notched-outline floating label is not present.
248-
this._needsOutlineLabelOffsetUpdateOnStable = true;
251+
this._needsOutlineLabelOffsetUpdate = true;
249252
}
250253
}
251254
private _appearance: MatFormFieldAppearance = DEFAULT_APPEARANCE;
@@ -300,12 +303,18 @@ export class MatFormField
300303
private _destroyed = new Subject<void>();
301304
private _isFocused: boolean | null = null;
302305
private _explicitFormFieldControl: MatFormFieldControl<any>;
303-
private _needsOutlineLabelOffsetUpdateOnStable = false;
306+
private _needsOutlineLabelOffsetUpdate = false;
307+
308+
private _injector = inject(Injector);
304309

305310
constructor(
306311
public _elementRef: ElementRef,
307312
private _changeDetectorRef: ChangeDetectorRef,
308-
private _ngZone: NgZone,
313+
/**
314+
* @deprecated not needed, to be removed.
315+
* @breaking-change 19.0.0 remove this param
316+
*/
317+
_unusedNgZone: NgZone,
309318
private _dir: Directionality,
310319
private _platform: Platform,
311320
@Optional()
@@ -485,30 +494,30 @@ export class MatFormField
485494
* The floating label in the docked state needs to account for prefixes. The horizontal offset
486495
* is calculated whenever the appearance changes to `outline`, the prefixes change, or when the
487496
* 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.
497+
* trigger the label offset update.
491498
*/
492499
private _initializeOutlineLabelOffsetSubscriptions() {
493500
// 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;
501+
// TODO(mmalerba): Use ResizeObserver to better support dynamically changing prefix content.
502+
this._prefixChildren.changes.subscribe(() => (this._needsOutlineLabelOffsetUpdate = true));
503+
504+
// TODO(mmalerba): Split this into separate `afterRender` calls using the `EarlyRead` and
505+
// `Write` phases.
506+
afterRender(
507+
() => {
508+
if (this._needsOutlineLabelOffsetUpdate) {
509+
this._needsOutlineLabelOffsetUpdate = false;
504510
this._updateOutlineLabelOffset();
505511
}
506-
});
507-
});
512+
},
513+
{
514+
injector: this._injector,
515+
},
516+
);
508517

509518
this._dir.change
510519
.pipe(takeUntil(this._destroyed))
511-
.subscribe(() => (this._needsOutlineLabelOffsetUpdateOnStable = true));
520+
.subscribe(() => (this._needsOutlineLabelOffsetUpdate = true));
512521
}
513522

514523
/** Whether the floating label should always float or not. */
@@ -653,7 +662,7 @@ export class MatFormField
653662
* incorporate the horizontal offset into their default text-field styles.
654663
*/
655664
private _updateOutlineLabelOffset() {
656-
if (!this._platform.isBrowser || !this._hasOutline() || !this._floatingLabel) {
665+
if (!this._hasOutline() || !this._floatingLabel) {
657666
return;
658667
}
659668
const floatingLabel = this._floatingLabel.element;
@@ -666,7 +675,7 @@ export class MatFormField
666675
// If the form field is not attached to the DOM yet (e.g. in a tab), we defer
667676
// the label offset update until the zone stabilizes.
668677
if (!this._isAttachedToDom()) {
669-
this._needsOutlineLabelOffsetUpdateOnStable = true;
678+
this._needsOutlineLabelOffsetUpdate = true;
670679
return;
671680
}
672681
const iconPrefixContainer = this._iconPrefixContainer?.nativeElement;

tools/public_api_guard/material/form-field.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ export class MatError {
6666

6767
// @public
6868
export class MatFormField implements FloatingLabelParent, AfterContentInit, AfterContentChecked, AfterViewInit, OnDestroy {
69-
constructor(_elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef, _ngZone: NgZone, _dir: Directionality, _platform: Platform, _defaults?: MatFormFieldDefaultOptions | undefined, _animationMode?: string | undefined,
69+
constructor(_elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef,
70+
_unusedNgZone: NgZone, _dir: Directionality, _platform: Platform, _defaults?: MatFormFieldDefaultOptions | undefined, _animationMode?: string | undefined,
7071
_unusedDocument?: any);
7172
_animateAndLockLabel(): void;
7273
// (undocumented)

0 commit comments

Comments
 (0)