Skip to content

Commit 53dcf53

Browse files
committed
refactor(material/input): support signal-based input accessor
Expands the `MAT_INPUT_VALUE_ACCESSOR` to support a signal-based accessor.
1 parent 76d718c commit 53dcf53

File tree

3 files changed

+37
-9
lines changed

3 files changed

+37
-9
lines changed

src/material/input/input-value-accessor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {InjectionToken} from '@angular/core';
9+
import {InjectionToken, WritableSignal} from '@angular/core';
1010

1111
/**
1212
* This token is used to inject the object whose value should be set into `MatInput`. If none is
1313
* provided, the native `HTMLInputElement` is used. Directives like `MatDatepickerInput` can provide
1414
* themselves for this token, in order to make `MatInput` delegate the getting and setting of the
1515
* value to them.
1616
*/
17-
export const MAT_INPUT_VALUE_ACCESSOR = new InjectionToken<{value: any}>(
17+
export const MAT_INPUT_VALUE_ACCESSOR = new InjectionToken<{value: any | WritableSignal<any>}>(
1818
'MAT_INPUT_VALUE_ACCESSOR',
1919
);

src/material/input/input.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ import {
1414
booleanAttribute,
1515
Directive,
1616
DoCheck,
17+
effect,
1718
ElementRef,
1819
inject,
1920
InjectionToken,
2021
Input,
22+
isSignal,
2123
NgZone,
2224
OnChanges,
2325
OnDestroy,
26+
WritableSignal,
2427
} from '@angular/core';
2528
import {FormGroupDirective, NgControl, NgForm, Validators} from '@angular/forms';
2629
import {ErrorStateMatcher, _ErrorStateTracker} from '@angular/material/core';
@@ -104,6 +107,7 @@ export class MatInput
104107
protected _uid = `mat-input-${nextUniqueId++}`;
105108
protected _previousNativeValue: any;
106109
private _inputValueAccessor: {value: any};
110+
private _signalBasedValueAccessor?: {value: WritableSignal<any>};
107111
private _previousPlaceholder: string | null;
108112
private _errorStateTracker: _ErrorStateTracker;
109113
private _webkitBlinkWheelListenerAttached = false;
@@ -244,11 +248,18 @@ export class MatInput
244248
*/
245249
@Input()
246250
get value(): string {
247-
return this._inputValueAccessor.value;
251+
return this._signalBasedValueAccessor
252+
? this._signalBasedValueAccessor.value()
253+
: this._inputValueAccessor.value;
248254
}
249255
set value(value: any) {
250256
if (value !== this.value) {
251-
this._inputValueAccessor.value = value;
257+
if (this._signalBasedValueAccessor) {
258+
this._signalBasedValueAccessor.value.set(value);
259+
} else {
260+
this._inputValueAccessor.value = value;
261+
}
262+
252263
this.stateChanges.next();
253264
}
254265
}
@@ -290,14 +301,22 @@ export class MatInput
290301
const parentForm = inject(NgForm, {optional: true});
291302
const parentFormGroup = inject(FormGroupDirective, {optional: true});
292303
const defaultErrorStateMatcher = inject(ErrorStateMatcher);
293-
const inputValueAccessor = inject(MAT_INPUT_VALUE_ACCESSOR, {optional: true, self: true});
304+
const accessor = inject(MAT_INPUT_VALUE_ACCESSOR, {optional: true, self: true});
294305

295306
const element = this._elementRef.nativeElement;
296307
const nodeName = element.nodeName.toLowerCase();
297308

298-
// If no input value accessor was explicitly specified, use the element as the input value
299-
// accessor.
300-
this._inputValueAccessor = inputValueAccessor || element;
309+
if (accessor) {
310+
if (isSignal(accessor.value)) {
311+
this._signalBasedValueAccessor = accessor;
312+
} else {
313+
this._inputValueAccessor = accessor;
314+
}
315+
} else {
316+
// If no input value accessor was explicitly specified, use the element as the input value
317+
// accessor.
318+
this._inputValueAccessor = element;
319+
}
301320

302321
this._previousNativeValue = this.value;
303322

@@ -331,6 +350,14 @@ export class MatInput
331350
? 'mat-native-select-multiple'
332351
: 'mat-native-select';
333352
}
353+
354+
if (this._signalBasedValueAccessor) {
355+
effect(() => {
356+
// Read the value so the effect can register the dependency.
357+
this._signalBasedValueAccessor!.value();
358+
this.stateChanges.next();
359+
});
360+
}
334361
}
335362

336363
ngAfterViewInit() {

tools/public_api_guard/material/input.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { OnChanges } from '@angular/core';
2626
import { OnDestroy } from '@angular/core';
2727
import { Platform } from '@angular/cdk/platform';
2828
import { Subject } from 'rxjs';
29+
import { WritableSignal } from '@angular/core';
2930

3031
// @public
3132
export function getMatInputUnsupportedTypeError(type: string): Error;
@@ -35,7 +36,7 @@ export const MAT_INPUT_CONFIG: InjectionToken<MatInputConfig>;
3536

3637
// @public
3738
export const MAT_INPUT_VALUE_ACCESSOR: InjectionToken<{
38-
value: any;
39+
value: any | WritableSignal<any>;
3940
}>;
4041

4142
export { MatError }

0 commit comments

Comments
 (0)