fix(form): set select value after option renders (#63340)

This commit is contained in:
Miles Malerba
2025-08-25 09:24:29 -07:00
committed by GitHub
parent 0b00039a45
commit d416beee7b
2 changed files with 44 additions and 2 deletions
+14 -2
View File
@@ -7,6 +7,7 @@
*/
import {
afterNextRender,
computed,
DestroyRef,
Directive,
@@ -177,9 +178,11 @@ export class Control<T> {
input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,
): void {
const inputType =
input instanceof HTMLTextAreaElement || input instanceof HTMLSelectElement
input instanceof HTMLTextAreaElement
? 'text'
: input.type;
: input instanceof HTMLSelectElement
? 'select'
: input.type;
input.addEventListener('input', () => {
switch (inputType) {
@@ -227,6 +230,15 @@ export class Control<T> {
},
);
break;
case 'select':
this.maybeSynchronize(
() => this.state().value(),
(value) => {
// A select will not take a value unil the value's option has rendered.
afterNextRender(() => (input.value = value as string), {injector: this.injector});
},
);
break;
default:
this.maybeSynchronize(
() => this.state().value(),
@@ -22,6 +22,7 @@ import {
Control,
disabled,
form,
hidden,
max,
MAX,
maxLength,
@@ -196,6 +197,35 @@ describe('control directive', () => {
expect(cmp.f().value()).toBe('two');
});
it('should assign correct value when unhiding select', () => {
@Component({
imports: [Control],
template: `
@if (!f().hidden()) {
<select #select [control]="f">
@for(opt of options; track opt) {
<option [value]="opt">{{opt}}</option>
}
</select>
}
`,
})
class TestCmp {
f = form(signal(''), (p) => hidden(p, ({value}) => value() === ''));
select = viewChild<ElementRef<HTMLSelectElement>>('select');
options = ['one', 'two', 'three'];
}
const fix = act(() => TestBed.createComponent(TestCmp));
const cmp = fix.componentInstance as TestCmp;
expect(fix.componentInstance.select()).toBeUndefined();
act(() => cmp.f().value.set('two'));
expect(fix.componentInstance.select()).not.toBeUndefined();
expect(fix.componentInstance.select()!.nativeElement.value).toEqual('two');
});
it('synchronizes with a custom value control', () => {
@Component({
selector: 'my-input',