From 11135b265c275da28e147da0316816312ee3230f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20H=C3=A4rtwig?= Date: Thu, 3 Oct 2024 13:38:32 +0200 Subject: [PATCH] refactor(angular/input): support signal-based input accessor (#2394) Expands the `SBB_INPUT_VALUE_ACCESSOR` to support a signal-based accessor. --- src/angular/input/input-value-accessor.ts | 4 +-- src/angular/input/input.ts | 38 +++++++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/angular/input/input-value-accessor.ts b/src/angular/input/input-value-accessor.ts index aafa6aa988..f409d47f15 100644 --- a/src/angular/input/input-value-accessor.ts +++ b/src/angular/input/input-value-accessor.ts @@ -1,4 +1,4 @@ -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`. @@ -6,6 +6,6 @@ import { InjectionToken } from '@angular/core'; * 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 }>( 'SBB_INPUT_VALUE_ACCESSOR', ); diff --git a/src/angular/input/input.ts b/src/angular/input/input.ts index 03c340ac1d..6c06e4959f 100644 --- a/src/angular/input/input.ts +++ b/src/angular/input/input.ts @@ -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'; @@ -68,6 +71,7 @@ export class SbbInput private _autofillMonitor = inject(AutofillMonitor); private _previousNativeValue: any; private _inputValueAccessor: { value: any }; + private _signalBasedValueAccessor?: { value: WritableSignal }; private _errorStateTracker: _ErrorStateTracker; ngControl: NgControl = inject(NgControl, { optional: true, self: true })!; @@ -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 `` 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(); } } @@ -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; @@ -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() {