Skip to content

Commit 4c64359

Browse files
authored
fix(module:input): support Signal Forms state (#9891)
* fix(module:input): support Signal Forms state * refactor(module:input): use Signal Forms token
1 parent 86bd6b6 commit 4c64359

4 files changed

Lines changed: 87 additions & 21 deletions

File tree

components/input/input.directive.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from '@angular/core';
2323
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
2424
import { NgControl } from '@angular/forms';
25+
import { FORM_FIELD } from '@angular/forms/signals';
2526
import { EMPTY } from 'rxjs';
2627
import { map, startWith } from 'rxjs/operators';
2728

@@ -56,6 +57,7 @@ const PREFIX_CLS = 'ant-input';
5657
'[class.ant-input-sm]': `finalSize() === 'small'`,
5758
'[attr.disabled]': 'finalDisabled() || null',
5859
'[attr.readonly]': 'readonly() || null',
60+
'(input)': 'onInput($event)',
5961
'[class.ant-input-rtl]': `dir() === 'rtl'`,
6062
'[class.ant-input-focused]': 'focused()'
6163
},
@@ -72,9 +74,16 @@ export class NzInputDirective implements OnInit {
7274
private hostView = inject(ViewContainerRef);
7375
private readonly inputPasswordDir = inject(NzInputPasswordDirective, { host: true, optional: true });
7476
private readonly inputSearchDir = inject(NZ_INPUT_SEARCH, { host: true, optional: true });
77+
private readonly formField = inject(FORM_FIELD, { self: true, optional: true });
7578

7679
readonly ngControl = inject(NgControl, { self: true, optional: true });
77-
readonly value = signal<string>(this.elementRef.nativeElement.value);
80+
private readonly nativeValue = signal(this.elementRef.nativeElement.value);
81+
readonly value = computed(() => {
82+
if (this.formField) {
83+
return String(this.formField.state().value() ?? '');
84+
}
85+
return this.nativeValue();
86+
});
7887

7988
readonly nzVariant = input<NzVariant>();
8089
readonly nzSize = input<NzSizeLDSType>('default');
@@ -83,7 +92,12 @@ export class NzInputDirective implements OnInit {
8392
readonly readonly = input(false, { transform: booleanAttribute });
8493

8594
readonly controlDisabled = signal(false);
86-
readonly finalDisabled = this.ngControl ? this.controlDisabled : this.disabled;
95+
readonly finalDisabled = computed(() => {
96+
if (this.formField) {
97+
return this.formField.state().disabled();
98+
}
99+
return this.ngControl ? this.controlDisabled() : this.disabled();
100+
});
87101
readonly dir = inject(Directionality).valueSignal;
88102
// TODO: When the input group is removed, we can remove this.
89103
readonly size = linkedSignal(this.nzSize);
@@ -150,10 +164,14 @@ export class NzInputDirective implements OnInit {
150164
this.ngControl?.valueChanges
151165
?.pipe(startWith(this.ngControl?.control?.value), takeUntilDestroyed(this.destroyRef))
152166
.subscribe(value => {
153-
this.value.set(value ?? '');
167+
this.nativeValue.set(value ?? '');
154168
});
155169
}
156170

171+
onInput(event: Event): void {
172+
this.nativeValue.set((event.target as HTMLInputElement | HTMLTextAreaElement).value);
173+
}
174+
157175
private renderFeedbackIcon(): void {
158176
if (!this.status() || !this.hasFeedback() || this.inputWrapper) {
159177
// remove feedback

components/input/input.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { Component, DebugElement, signal, viewChild, WritableSignal } from '@angular/core';
77
import { ComponentFixture, TestBed } from '@angular/core/testing';
88
import { FormControl, ReactiveFormsModule } from '@angular/forms';
9+
import { disabled, form, FormField } from '@angular/forms/signals';
910
import { By } from '@angular/platform-browser';
1011

1112
import { NZ_FORM_SIZE, NZ_FORM_VARIANT } from 'ng-zorro-antd/core/form';
@@ -158,6 +159,24 @@ describe('input', () => {
158159
});
159160
});
160161

162+
describe('input with Signal Forms', () => {
163+
it('should reflect the disabled field state', async () => {
164+
const fixture = TestBed.createComponent(NzTestInputSignalFormComponent);
165+
fixture.autoDetectChanges();
166+
await fixture.whenStable();
167+
168+
const input = fixture.debugElement.query(By.directive(NzInputDirective)).nativeElement as HTMLInputElement;
169+
expect(input.disabled).toBe(false);
170+
expect(input.classList).not.toContain('ant-input-disabled');
171+
172+
fixture.componentInstance.isDisabled.set(true);
173+
await fixture.whenStable();
174+
175+
expect(input.disabled).toBe(true);
176+
expect(input.classList).toContain('ant-input-disabled');
177+
});
178+
});
179+
161180
testDirectionality(() => NzTestInputWithInputComponent, By.directive(NzInputDirective), 'ant-input');
162181

163182
describe('input with status', () => {
@@ -423,6 +442,18 @@ export class NzTestInputFormComponent {
423442
}
424443
}
425444

445+
@Component({
446+
imports: [FormField, NzInputModule],
447+
template: `<input nz-input [formField]="form.name" />`
448+
})
449+
export class NzTestInputSignalFormComponent {
450+
readonly model = signal({ name: '' });
451+
readonly isDisabled = signal(false);
452+
readonly form = form(this.model, path => {
453+
disabled(path.name, () => this.isDisabled());
454+
});
455+
}
456+
426457
// status
427458
@Component({
428459
imports: [NzInputModule],

components/input/textarea-count.component.ts

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@ import {
77
AfterContentInit,
88
Component,
99
ContentChild,
10-
DestroyRef,
1110
ElementRef,
11+
effect,
1212
inject,
13+
Injector,
1314
Input,
1415
isDevMode,
1516
numberAttribute,
1617
Renderer2
1718
} from '@angular/core';
18-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
19-
import { EMPTY } from 'rxjs';
20-
import { map, startWith } from 'rxjs/operators';
2119

2220
import { isNotNil } from 'ng-zorro-antd/core/util';
2321

@@ -32,7 +30,7 @@ import { NzInputDirective } from './input.directive';
3230
})
3331
export class NzTextareaCountComponent implements AfterContentInit {
3432
private renderer = inject(Renderer2);
35-
private destroyRef = inject(DestroyRef);
33+
private injector = inject(Injector);
3634
private elementRef: ElementRef<HTMLElement> = inject(ElementRef);
3735

3836
@ContentChild(NzInputDirective, { static: true }) nzInputDirective!: NzInputDirective;
@@ -45,18 +43,7 @@ export class NzTextareaCountComponent implements AfterContentInit {
4543
throw new Error('[nz-textarea-count]: Could not find matching textarea[nz-input] child.');
4644
}
4745

48-
if (this.nzInputDirective.ngControl) {
49-
const valueChanges = this.nzInputDirective.ngControl.valueChanges || EMPTY;
50-
valueChanges
51-
.pipe(
52-
takeUntilDestroyed(this.destroyRef),
53-
map(() => this.nzInputDirective.ngControl!.value),
54-
startWith(this.nzInputDirective.ngControl.value as string)
55-
)
56-
.subscribe(value => {
57-
this.setDataCount(value);
58-
});
59-
}
46+
effect(() => this.setDataCount(this.nzInputDirective.value()), { injector: this.injector });
6047
}
6148

6249
setDataCount(value: string): void {

components/input/textarea-count.spec.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
import { Component, signal } from '@angular/core';
77
import { ComponentFixture, TestBed } from '@angular/core/testing';
88
import { FormsModule } from '@angular/forms';
9+
import { form, FormField } from '@angular/forms/signals';
910
import { By } from '@angular/platform-browser';
1011

11-
import { updateNonSignalsInput } from 'ng-zorro-antd/core/testing';
12+
import { dispatchFakeEvent, updateNonSignalsInput } from 'ng-zorro-antd/core/testing';
1213
import { NzInputModule } from 'ng-zorro-antd/input/input.module';
1314
import { NzTextareaCountComponent } from 'ng-zorro-antd/input/textarea-count.component';
1415

@@ -62,6 +63,22 @@ describe('textarea-count', () => {
6263
expect(textareaCountElement.getAttribute('data-count')).toBe('4/100');
6364
});
6465
});
66+
67+
describe('with Signal Forms', () => {
68+
it('should update the count when the field value changes', async () => {
69+
const fixture = TestBed.createComponent(NzTestInputTextareaCountWithSignalFormComponent);
70+
fixture.autoDetectChanges();
71+
await fixture.whenStable();
72+
73+
const textarea = fixture.nativeElement.querySelector('textarea') as HTMLTextAreaElement;
74+
const textareaCountElement = fixture.debugElement.query(By.directive(NzTextareaCountComponent)).nativeElement;
75+
textarea.value = 'Signal Forms';
76+
dispatchFakeEvent(textarea, 'input');
77+
await fixture.whenStable();
78+
79+
expect(textareaCountElement.getAttribute('data-count')).toBe('12/50');
80+
});
81+
});
6582
});
6683

6784
@Component({
@@ -87,3 +104,16 @@ export class NzTestInputTextareaCountWithoutMaxComponent {
87104
export class NzTestInputTextareaCountWithMaxComponent {
88105
readonly inputValue = signal('');
89106
}
107+
108+
@Component({
109+
imports: [FormField, NzInputModule],
110+
template: `
111+
<nz-textarea-count [nzMaxCharacterCount]="50">
112+
<textarea rows="4" nz-input [formField]="form.bio"></textarea>
113+
</nz-textarea-count>
114+
`
115+
})
116+
export class NzTestInputTextareaCountWithSignalFormComponent {
117+
readonly model = signal({ bio: '' });
118+
readonly form = form(this.model);
119+
}

0 commit comments

Comments
 (0)