Skip to content

Commit 96117bc

Browse files
authored
fix(material/form-field): resolve memory leak (#31643)
Inside the form field we have a `afterRenderEffect` that updates the label offset when in outlined mode. The logic for calculating the label offset depends on reading `Directionality.valueSignal` which ends up triggering a memory leak in the framework (angular/angular#62980). These changes add a temporary workaround that removes the `valueSignal` from the signal graph in order to avoid the leak until it's fixed on the framework side.
1 parent 11f9228 commit 96117bc

File tree

1 file changed

+9
-4
lines changed

1 file changed

+9
-4
lines changed

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88
import {_IdGenerator} from '@angular/cdk/a11y';
9-
import {Directionality} from '@angular/cdk/bidi';
9+
import {Direction, Directionality} from '@angular/cdk/bidi';
1010
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
1111
import {Platform} from '@angular/cdk/platform';
1212
import {NgTemplateOutlet} from '@angular/common';
@@ -30,6 +30,7 @@ import {
3030
afterRenderEffect,
3131
computed,
3232
contentChild,
33+
effect,
3334
inject,
3435
signal,
3536
viewChild,
@@ -190,13 +191,13 @@ export class MatFormField
190191
{
191192
_elementRef = inject(ElementRef);
192193
private _changeDetectorRef = inject(ChangeDetectorRef);
193-
private _dir = inject(Directionality);
194194
private _platform = inject(Platform);
195195
private _idGenerator = inject(_IdGenerator);
196196
private _ngZone = inject(NgZone);
197197
private _defaults = inject<MatFormFieldDefaultOptions>(MAT_FORM_FIELD_DEFAULT_OPTIONS, {
198198
optional: true,
199199
});
200+
private _currentDirection: Direction;
200201

201202
@ViewChild('textField') _textField: ElementRef<HTMLElement>;
202203
@ViewChild('iconPrefixContainer') _iconPrefixContainer: ElementRef<HTMLElement>;
@@ -346,6 +347,7 @@ export class MatFormField
346347

347348
constructor() {
348349
const defaults = this._defaults;
350+
const dir = inject(Directionality);
349351

350352
if (defaults) {
351353
if (defaults.appearance) {
@@ -357,6 +359,10 @@ export class MatFormField
357359
}
358360
}
359361

362+
// We need this value inside a `afterRenderEffect`, however at the time of writing, reading the
363+
// signal directly causes a memory leak (see https://github.com/angular/angular/issues/62980).
364+
// TODO(crisbeto): clean this up once the framework issue is resolved.
365+
effect(() => (this._currentDirection = dir.valueSignal()));
360366
this._syncOutlineLabelOffset();
361367
}
362368

@@ -752,7 +758,6 @@ export class MatFormField
752758
* incorporate the horizontal offset into their default text-field styles.
753759
*/
754760
private _getOutlinedLabelOffset(): OutlinedLabelStyles {
755-
const dir = this._dir.valueSignal();
756761
if (!this._hasOutline() || !this._floatingLabel) {
757762
return null;
758763
}
@@ -776,7 +781,7 @@ export class MatFormField
776781
const textSuffixContainerWidth = textSuffixContainer?.getBoundingClientRect().width ?? 0;
777782
// If the directionality is RTL, the x-axis transform needs to be inverted. This
778783
// is because `transformX` does not change based on the page directionality.
779-
const negate = dir === 'rtl' ? '-1' : '1';
784+
const negate = this._currentDirection === 'rtl' ? '-1' : '1';
780785
const prefixWidth = `${iconPrefixContainerWidth + textPrefixContainerWidth}px`;
781786
const labelOffset = `var(--mat-mdc-form-field-label-offset-x, 0px)`;
782787
const labelHorizontalOffset = `calc(${negate} * (${prefixWidth} + ${labelOffset}))`;

0 commit comments

Comments
 (0)