Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(material/form-field): Remove use of zone onStable to schedule outline updates #28636

Merged
merged 1 commit into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions src/material/form-field/form-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
ElementRef,
Inject,
InjectionToken,
Injector,
inject,
Input,
NgZone,
OnDestroy,
Expand All @@ -27,6 +29,7 @@ import {
ViewChild,
ViewEncapsulation,
ANIMATION_MODULE_TYPE,
afterRender,
} from '@angular/core';
import {AbstractControlDirective} from '@angular/forms';
import {ThemePalette} from '@angular/material/core';
Expand Down Expand Up @@ -245,7 +248,7 @@ export class MatFormField
// If the appearance has been switched to `outline`, the label offset needs to be updated.
// The update can happen once the view has been re-checked, but not immediately because
// the view has not been updated and the notched-outline floating label is not present.
this._needsOutlineLabelOffsetUpdateOnStable = true;
this._needsOutlineLabelOffsetUpdate = true;
}
}
private _appearance: MatFormFieldAppearance = DEFAULT_APPEARANCE;
Expand Down Expand Up @@ -300,12 +303,18 @@ export class MatFormField
private _destroyed = new Subject<void>();
private _isFocused: boolean | null = null;
private _explicitFormFieldControl: MatFormFieldControl<any>;
private _needsOutlineLabelOffsetUpdateOnStable = false;
private _needsOutlineLabelOffsetUpdate = false;

private _injector = inject(Injector);

constructor(
public _elementRef: ElementRef,
private _changeDetectorRef: ChangeDetectorRef,
private _ngZone: NgZone,
/**
* @deprecated not needed, to be removed.
* @breaking-change 19.0.0 remove this param
*/
_unusedNgZone: NgZone,
private _dir: Directionality,
private _platform: Platform,
@Optional()
Expand Down Expand Up @@ -485,30 +494,30 @@ export class MatFormField
* The floating label in the docked state needs to account for prefixes. The horizontal offset
* is calculated whenever the appearance changes to `outline`, the prefixes change, or when the
* form field is added to the DOM. This method sets up all subscriptions which are needed to
* trigger the label offset update. In general, we want to avoid performing measurements often,
* so we rely on the `NgZone` as indicator when the offset should be recalculated, instead of
* checking every change detection cycle.
* trigger the label offset update.
*/
private _initializeOutlineLabelOffsetSubscriptions() {
// Whenever the prefix changes, schedule an update of the label offset.
this._prefixChildren.changes.subscribe(
() => (this._needsOutlineLabelOffsetUpdateOnStable = true),
);

// Note that we have to run outside of the `NgZone` explicitly, in order to avoid
// throwing users into an infinite loop if `zone-patch-rxjs` is included.
this._ngZone.runOutsideAngular(() => {
this._ngZone.onStable.pipe(takeUntil(this._destroyed)).subscribe(() => {
if (this._needsOutlineLabelOffsetUpdateOnStable) {
this._needsOutlineLabelOffsetUpdateOnStable = false;
// TODO(mmalerba): Use ResizeObserver to better support dynamically changing prefix content.
this._prefixChildren.changes.subscribe(() => (this._needsOutlineLabelOffsetUpdate = true));

// TODO(mmalerba): Split this into separate `afterRender` calls using the `EarlyRead` and
// `Write` phases.
afterRender(
() => {
if (this._needsOutlineLabelOffsetUpdate) {
this._needsOutlineLabelOffsetUpdate = false;
this._updateOutlineLabelOffset();
}
});
});
},
{
injector: this._injector,
},
);

this._dir.change
.pipe(takeUntil(this._destroyed))
.subscribe(() => (this._needsOutlineLabelOffsetUpdateOnStable = true));
.subscribe(() => (this._needsOutlineLabelOffsetUpdate = true));
}

/** Whether the floating label should always float or not. */
Expand Down Expand Up @@ -653,7 +662,7 @@ export class MatFormField
* incorporate the horizontal offset into their default text-field styles.
*/
private _updateOutlineLabelOffset() {
if (!this._platform.isBrowser || !this._hasOutline() || !this._floatingLabel) {
if (!this._hasOutline() || !this._floatingLabel) {
return;
}
const floatingLabel = this._floatingLabel.element;
Expand All @@ -666,7 +675,7 @@ export class MatFormField
// If the form field is not attached to the DOM yet (e.g. in a tab), we defer
// the label offset update until the zone stabilizes.
if (!this._isAttachedToDom()) {
this._needsOutlineLabelOffsetUpdateOnStable = true;
this._needsOutlineLabelOffsetUpdate = true;
return;
}
const iconPrefixContainer = this._iconPrefixContainer?.nativeElement;
Expand Down
3 changes: 2 additions & 1 deletion tools/public_api_guard/material/form-field.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ export class MatError {

// @public
export class MatFormField implements FloatingLabelParent, AfterContentInit, AfterContentChecked, AfterViewInit, OnDestroy {
constructor(_elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef, _ngZone: NgZone, _dir: Directionality, _platform: Platform, _defaults?: MatFormFieldDefaultOptions | undefined, _animationMode?: string | undefined,
constructor(_elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef,
_unusedNgZone: NgZone, _dir: Directionality, _platform: Platform, _defaults?: MatFormFieldDefaultOptions | undefined, _animationMode?: string | undefined,
_unusedDocument?: any);
_animateAndLockLabel(): void;
// (undocumented)
Expand Down
Loading