Skip to content

Commit

Permalink
refactor(material/input): support signal-based input accessor
Browse files Browse the repository at this point in the history
Expands the `MAT_INPUT_VALUE_ACCESSOR` to support a signal-based accessor.
  • Loading branch information
crisbeto committed Sep 30, 2024
1 parent e7eb56f commit 54ae0b2
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/material/input/input-value-accessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
* 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
* provided, the native `HTMLInputElement` is used. Directives like `MatDatepickerInput` can provide
* 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<any>}>(
'MAT_INPUT_VALUE_ACCESSOR',
);
39 changes: 33 additions & 6 deletions src/material/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -104,6 +107,7 @@ export class MatInput
protected _uid = `mat-input-${nextUniqueId++}`;
protected _previousNativeValue: any;
private _inputValueAccessor: {value: any};
private _signalBasedValueAccessor?: {value: WritableSignal<any>};
private _previousPlaceholder: string | null;
private _errorStateTracker: _ErrorStateTracker;
private _webkitBlinkWheelListenerAttached = false;
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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() {
Expand Down
3 changes: 2 additions & 1 deletion tools/public_api_guard/material/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,7 +36,7 @@ export const MAT_INPUT_CONFIG: InjectionToken<MatInputConfig>;

// @public
export const MAT_INPUT_VALUE_ACCESSOR: InjectionToken<{
value: any;
value: any | WritableSignal<any>;
}>;

export { MatError }
Expand Down

0 comments on commit 54ae0b2

Please sign in to comment.