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(angular/input): support signal-based input accessor #2394

Merged
merged 1 commit into from
Oct 3, 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
4 changes: 2 additions & 2 deletions src/angular/input/input-value-accessor.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
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 `InputDirective`.
* If none is provided, the native `HTMLInputElement` is used. Directives can provide
* themselves for this token, in order to make `InputDirective` delegate the getting and setting of the
* value to them.
*/
export const SBB_INPUT_VALUE_ACCESSOR = new InjectionToken<{ value: any }>(
export const SBB_INPUT_VALUE_ACCESSOR = new InjectionToken<{ value: any | WritableSignal<any> }>(
'SBB_INPUT_VALUE_ACCESSOR',
);
38 changes: 32 additions & 6 deletions src/angular/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import {
AfterViewInit,
Directive,
DoCheck,
effect,
ElementRef,
HostListener,
inject,
Input,
isSignal,
NgZone,
numberAttribute,
OnChanges,
OnDestroy,
WritableSignal,
} from '@angular/core';
import { FormGroupDirective, NgControl, NgForm } from '@angular/forms';
import { SbbErrorStateMatcher, _ErrorStateTracker } from '@sbb-esta/angular/core';
Expand Down Expand Up @@ -68,6 +71,7 @@ export class SbbInput
private _autofillMonitor = inject(AutofillMonitor);
private _previousNativeValue: any;
private _inputValueAccessor: { value: any };
private _signalBasedValueAccessor?: { value: WritableSignal<any> };
private _errorStateTracker: _ErrorStateTracker;
ngControl: NgControl = inject(NgControl, { optional: true, self: true })!;

Expand Down Expand Up @@ -194,13 +198,19 @@ export class SbbInput
*/
@Input()
get value(): string {
return this._inputValueAccessor.value;
return this._signalBasedValueAccessor
? this._signalBasedValueAccessor.value()
: this._inputValueAccessor.value;
}
// Accept `any` to avoid conflicts with other directives on `<input>` that may
// accept different types.
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 @@ -255,15 +265,23 @@ export class SbbInput
const parentForm = inject(NgForm, { optional: true })!;
const parentFormGroup = inject(FormGroupDirective, { optional: true })!;
const defaultErrorStateMatcher = inject(SbbErrorStateMatcher);
const inputValueAccessor = inject(SBB_INPUT_VALUE_ACCESSOR, { optional: true, self: true })!;
const accessor = inject(SBB_INPUT_VALUE_ACCESSOR, { optional: true, self: true })!;
const ngZone = inject(NgZone);

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 All @@ -290,6 +308,14 @@ export class SbbInput
? 'sbb-native-select-multiple'
: 'sbb-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
Loading