diff --git a/src/material/input/input-value-accessor.ts b/src/material/input/input-value-accessor.ts index e010053e51b5..4d00b96a48ec 100644 --- a/src/material/input/input-value-accessor.ts +++ b/src/material/input/input-value-accessor.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {InjectionToken} from '@angular/core'; +import {InjectionToken, WritableSignal} from '@angular/core'; /** * This token is used to inject the object whose value should be set into `MatInput`. If none is @@ -14,6 +14,6 @@ import {InjectionToken} from '@angular/core'; * themselves for this token, in order to make `MatInput` delegate the getting and setting of the * value to them. */ -export const MAT_INPUT_VALUE_ACCESSOR = new InjectionToken<{value: any}>( +export const MAT_INPUT_VALUE_ACCESSOR = new InjectionToken<{value: any | WritableSignal}>( 'MAT_INPUT_VALUE_ACCESSOR', ); diff --git a/src/material/input/input.ts b/src/material/input/input.ts index c12eac1b537f..9b334064e90a 100644 --- a/src/material/input/input.ts +++ b/src/material/input/input.ts @@ -14,13 +14,16 @@ import { booleanAttribute, Directive, DoCheck, + effect, ElementRef, inject, InjectionToken, Input, + isSignal, NgZone, OnChanges, OnDestroy, + WritableSignal, } from '@angular/core'; import {FormGroupDirective, NgControl, NgForm, Validators} from '@angular/forms'; import {ErrorStateMatcher, _ErrorStateTracker} from '@angular/material/core'; @@ -104,6 +107,7 @@ export class MatInput protected _uid = `mat-input-${nextUniqueId++}`; protected _previousNativeValue: any; private _inputValueAccessor: {value: any}; + private _signalBasedValueAccessor?: {value: WritableSignal}; private _previousPlaceholder: string | null; private _errorStateTracker: _ErrorStateTracker; private _webkitBlinkWheelListenerAttached = false; @@ -244,11 +248,18 @@ export class MatInput */ @Input() get value(): string { - return this._inputValueAccessor.value; + return this._signalBasedValueAccessor + ? this._signalBasedValueAccessor.value() + : this._inputValueAccessor.value; } set value(value: any) { if (value !== this.value) { - this._inputValueAccessor.value = value; + if (this._signalBasedValueAccessor) { + this._signalBasedValueAccessor.value.set(value); + } else { + this._inputValueAccessor.value = value; + } + this.stateChanges.next(); } } @@ -290,14 +301,22 @@ export class MatInput const parentForm = inject(NgForm, {optional: true}); const parentFormGroup = inject(FormGroupDirective, {optional: true}); const defaultErrorStateMatcher = inject(ErrorStateMatcher); - const inputValueAccessor = inject(MAT_INPUT_VALUE_ACCESSOR, {optional: true, self: true}); + const accessor = inject(MAT_INPUT_VALUE_ACCESSOR, {optional: true, self: true}); const element = this._elementRef.nativeElement; const nodeName = element.nodeName.toLowerCase(); - // If no input value accessor was explicitly specified, use the element as the input value - // accessor. - this._inputValueAccessor = inputValueAccessor || element; + if (accessor) { + if (isSignal(accessor.value)) { + this._signalBasedValueAccessor = accessor; + } else { + this._inputValueAccessor = accessor; + } + } else { + // If no input value accessor was explicitly specified, use the element as the input value + // accessor. + this._inputValueAccessor = element; + } this._previousNativeValue = this.value; @@ -331,6 +350,14 @@ export class MatInput ? 'mat-native-select-multiple' : 'mat-native-select'; } + + if (this._signalBasedValueAccessor) { + effect(() => { + // Read the value so the effect can register the dependency. + this._signalBasedValueAccessor!.value(); + this.stateChanges.next(); + }); + } } ngAfterViewInit() { diff --git a/tools/public_api_guard/material/input.md b/tools/public_api_guard/material/input.md index 7ca0e414552a..98fc6612dd51 100644 --- a/tools/public_api_guard/material/input.md +++ b/tools/public_api_guard/material/input.md @@ -26,6 +26,7 @@ import { OnChanges } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { Platform } from '@angular/cdk/platform'; import { Subject } from 'rxjs'; +import { WritableSignal } from '@angular/core'; // @public export function getMatInputUnsupportedTypeError(type: string): Error; @@ -35,7 +36,7 @@ export const MAT_INPUT_CONFIG: InjectionToken; // @public export const MAT_INPUT_VALUE_ACCESSOR: InjectionToken<{ - value: any; + value: any | WritableSignal; }>; export { MatError }