mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
fix(compiler-cli): expand type for native controls with a dynamic type
We recently allowed users to have a dynamic input `type` with signal forms, but the logic that infers the value type falls back to `string` even though in theory it can be any of the other types.
These changes expand the inferred type to `string | number | boolean | Date | null` if we detect a dynamic `type` binding.
(cherry picked from commit 8a3f3a91cf)
This commit is contained in:
parent
6d7475582f
commit
65297c6201
3 changed files with 60 additions and 0 deletions
|
|
@ -157,6 +157,25 @@ export class TcbNativeFieldOp extends TcbOp {
|
|||
]);
|
||||
}
|
||||
|
||||
const hasDynamicType =
|
||||
this.inputType === null &&
|
||||
this.node.inputs.some(
|
||||
(input) =>
|
||||
(input.type === BindingType.Property || input.type === BindingType.Attribute) &&
|
||||
input.name === 'type',
|
||||
);
|
||||
|
||||
// If the type is dynamic, check it as if it can be any of the types above.
|
||||
if (hasDynamicType) {
|
||||
return ts.factory.createUnionTypeNode([
|
||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
|
||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword),
|
||||
ts.factory.createTypeReferenceNode('Date'),
|
||||
ts.factory.createLiteralTypeNode(ts.factory.createNull()),
|
||||
]);
|
||||
}
|
||||
|
||||
// Fall back to string if we couldn't map the type.
|
||||
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2603,6 +2603,22 @@ describe('type check blocks', () => {
|
|||
expect(block).toContain('_t2.field = (((this).f));');
|
||||
});
|
||||
|
||||
it('should generate all possible type for input with a dynamic `type`', () => {
|
||||
const block = tcb('<input [type]="expr" [field]="f"/>', [FieldMock]);
|
||||
expect(block).toContain('var _t1 = null! as string | number | boolean | Date | null;');
|
||||
expect(block).toContain('_t1 = ((this).f)().value();');
|
||||
expect(block).toContain('var _t2 = null! as i0.Field;');
|
||||
expect(block).toContain('_t2.field = (((this).f));');
|
||||
});
|
||||
|
||||
it('should generate all possible type for input with a dynamic attribute `type` binding', () => {
|
||||
const block = tcb('<input [attr.type]="expr" [field]="f"/>', [FieldMock]);
|
||||
expect(block).toContain('var _t1 = null! as string | number | boolean | Date | null;');
|
||||
expect(block).toContain('_t1 = ((this).f)().value();');
|
||||
expect(block).toContain('var _t2 = null! as i0.Field;');
|
||||
expect(block).toContain('_t2.field = (((this).f));');
|
||||
});
|
||||
|
||||
[
|
||||
{inputType: 'text', expectedType: 'string'},
|
||||
{inputType: 'radio', expectedType: 'string'},
|
||||
|
|
|
|||
|
|
@ -601,5 +601,30 @@ runInEachFileSystem(() => {
|
|||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should infer an input with a dynamic `type` as being any of the other types', () => {
|
||||
env.write(
|
||||
'test.ts',
|
||||
`
|
||||
import {Component, signal} from '@angular/core';
|
||||
import {Field, form} from '@angular/forms/signals';
|
||||
|
||||
@Component({
|
||||
template: '<input [type]="type" [field]="f"/>',
|
||||
imports: [Field]
|
||||
})
|
||||
export class Comp {
|
||||
type = '';
|
||||
f = form(signal({test: true}));
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
expect(diags.length).toBe(1);
|
||||
expect(extractMessage(diags[0])).toBe(
|
||||
`Type '{ test: boolean; }' is not assignable to type 'string | number | boolean | Date | null'.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue