Skip to content

Commit a5c5ec0

Browse files
committed
fix(form): set select value after option renders
1 parent 0b00039 commit a5c5ec0

File tree

2 files changed

+45
-2
lines changed

2 files changed

+45
-2
lines changed

packages/forms/signals/src/controls/control.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {
10+
afterNextRender,
1011
computed,
1112
DestroyRef,
1213
Directive,
@@ -177,9 +178,11 @@ export class Control<T> {
177178
input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,
178179
): void {
179180
const inputType =
180-
input instanceof HTMLTextAreaElement || input instanceof HTMLSelectElement
181+
input instanceof HTMLTextAreaElement
181182
? 'text'
182-
: input.type;
183+
: input instanceof HTMLSelectElement
184+
? 'select'
185+
: input.type;
183186

184187
input.addEventListener('input', () => {
185188
switch (inputType) {
@@ -227,6 +230,15 @@ export class Control<T> {
227230
},
228231
);
229232
break;
233+
case 'select':
234+
this.maybeSynchronize(
235+
() => this.state().value(),
236+
(value) => {
237+
// A select will not take a value unil the value's option has rendered.
238+
afterNextRender(() => (input.value = value as string), {injector: this.injector});
239+
},
240+
);
241+
break;
230242
default:
231243
this.maybeSynchronize(
232244
() => this.state().value(),

packages/forms/signals/test/web/control_directive.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
Control,
2323
disabled,
2424
form,
25+
hidden,
2526
max,
2627
MAX,
2728
maxLength,
@@ -196,6 +197,36 @@ describe('control directive', () => {
196197
expect(cmp.f().value()).toBe('two');
197198
});
198199

200+
it('should assign correct value when unhiding select', async () => {
201+
@Component({
202+
imports: [Control],
203+
template: `
204+
@if (!f().hidden()) {
205+
<select #select [control]="f">
206+
@for(opt of options; track opt) {
207+
<option [value]="opt">{{opt}}</option>
208+
}
209+
</select>
210+
}
211+
`,
212+
})
213+
class TestCmp {
214+
f = form(signal(''), (p) => hidden(p, ({value}) => value() === ''));
215+
select = viewChild<ElementRef<HTMLSelectElement>>('select');
216+
options = ['one', 'two', 'three'];
217+
}
218+
219+
const fix = act(() => TestBed.createComponent(TestCmp));
220+
const cmp = fix.componentInstance as TestCmp;
221+
222+
expect(fix.componentInstance.select()).toBeUndefined();
223+
224+
cmp.f().value.set('two');
225+
await fix.whenStable();
226+
expect(fix.componentInstance.select()).not.toBeUndefined();
227+
expect(fix.componentInstance.select()!.nativeElement.value).toEqual('two');
228+
});
229+
199230
it('synchronizes with a custom value control', () => {
200231
@Component({
201232
selector: 'my-input',

0 commit comments

Comments
 (0)