Skip to content

Commit 8acf5d2

Browse files
leonsenftkirjs
authored andcommitted
fix(forms): allow dynamic type bindings on signal form controls
The type checker will no longer prohibit binding the Signal Forms `[field]` directive to an input with a dynamic `[attr.type]` or `[type]` binding. (cherry picked from commit 3a1eb07)
1 parent c9f50b4 commit 8acf5d2

File tree

3 files changed

+50
-8
lines changed

3 files changed

+50
-8
lines changed

packages/compiler-cli/src/ngtsc/typecheck/src/ops/signal_forms.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ export class TcbNativeFieldDirectiveTypeOp extends TcbOp {
8080
...formControlInputFields,
8181
'value',
8282
'checked',
83-
'type',
8483
'maxlength',
8584
'minlength',
8685
]);
@@ -373,10 +372,7 @@ export function checkUnsupportedFieldBindings(
373372

374373
if (!(node instanceof TmplAstHostElement)) {
375374
for (const attr of node.attributes) {
376-
const name = attr.name.toLowerCase();
377-
378-
// `type` is allowed to be a static attribute.
379-
if (name !== 'type' && unsupportedBindingFields.has(name)) {
375+
if (unsupportedBindingFields.has(attr.name.toLowerCase())) {
380376
tcb.oobRecorder.formFieldUnsupportedBinding(tcb.id, attr);
381377
}
382378
}

packages/compiler-cli/test/ngtsc/signal_forms_spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,20 +305,20 @@ runInEachFileSystem(() => {
305305
import {Field, form} from '@angular/forms/signals';
306306
307307
@Component({
308-
template: '<input [attr.type]="type" [field]="f"/>',
308+
template: '<input [field]="f" [attr.maxlength]="maxLength"/>',
309309
imports: [Field]
310310
})
311311
export class Comp {
312312
f = form(signal(''));
313-
type = 'number';
313+
maxLength = 10;
314314
}
315315
`,
316316
);
317317

318318
const diags = env.driveDiagnostics();
319319
expect(diags.length).toBe(1);
320320
expect(extractMessage(diags[0])).toBe(
321-
`Binding to '[attr.type]' is not allowed on nodes using the '[field]' directive`,
321+
`Binding to '[attr.maxlength]' is not allowed on nodes using the '[field]' directive`,
322322
);
323323
});
324324

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {
1010
Component,
11+
computed,
1112
Directive,
1213
ElementRef,
1314
inject,
@@ -2092,6 +2093,51 @@ describe('field directive', () => {
20922093
});
20932094
expect(cmp.f().value()).toBe('#0000ff');
20942095
});
2096+
2097+
it('should sync string field with dynamically typed input', () => {
2098+
@Component({
2099+
imports: [Field],
2100+
template: `<input [type]="type()" [field]="f">`,
2101+
})
2102+
class TestCmp {
2103+
readonly passwordVisible = signal(false);
2104+
readonly type = computed(() => (this.passwordVisible() ? 'text' : 'password'));
2105+
f = form(signal(''));
2106+
}
2107+
2108+
const fix = act(() => TestBed.createComponent(TestCmp));
2109+
const input = fix.nativeElement.firstChild as HTMLInputElement;
2110+
const cmp = fix.componentInstance as TestCmp;
2111+
2112+
// Initial state
2113+
expect(input.type).toBe('password');
2114+
2115+
// Model -> View as password
2116+
act(() => cmp.f().value.set('123'));
2117+
expect(input.value).toBe('123');
2118+
2119+
// View -> Model as password
2120+
act(() => {
2121+
input.value = 'abc';
2122+
input.dispatchEvent(new Event('input'));
2123+
});
2124+
expect(cmp.f().value()).toBe('abc');
2125+
2126+
// Change input type
2127+
act(() => cmp.passwordVisible.set(true));
2128+
expect(input.type).toBe('text');
2129+
2130+
// Model -> View as text
2131+
act(() => cmp.f().value.set('123'));
2132+
expect(input.value).toBe('123');
2133+
2134+
// View -> Model as text
2135+
act(() => {
2136+
input.value = 'abc';
2137+
input.dispatchEvent(new Event('input'));
2138+
});
2139+
expect(cmp.f().value()).toBe('abc');
2140+
});
20952141
});
20962142

20972143
describe('should be marked dirty by user interaction', () => {

0 commit comments

Comments
 (0)