-
Notifications
You must be signed in to change notification settings - Fork 27.1k
Closed
Description
Which @angular/* package(s) are the source of the bug?
forms
Is this a regression?
No
Description
The group function expands passed-in union types, which it shouldn't do:
const groupControl = formBuilder.group({
value: '' as string | number,
});
// Expected type:
// FormControl<string | number>
// Actual type:
// FormControl<string> | FormControl<number>
const valueControl = group.controls.value;Please provide a link to a minimal reproduction of the bug
https://stackblitz.com/edit/angular-ivy-ao4sne?file=src%2Fapp%2Fapp.component.ts
Please provide the exception or error you saw
n/a
Please provide the environment you discovered this bug in (run ng version)
Unable to run ng version in stackblitz
Happens in 14.0.0-next.16
Anything else?
This is caused by the type mapping in
angular/packages/forms/src/form_builder.ts
Lines 45 to 62 in 3f3812e
| /** | |
| * FormBuilder accepts values in various container shapes, as well as raw values. | |
| * Element returns the appropriate corresponding model class, given the container T. | |
| * The flag N, if not never, makes the resulting `FormControl` have N in its type. | |
| */ | |
| export type ɵElement<T, N extends null> = | |
| T extends FormControl<infer U> ? FormControl<U> : | |
| T extends FormGroup<infer U> ? FormGroup<U> : | |
| T extends FormArray<infer U> ? FormArray<U> : | |
| T extends AbstractControl<infer U> ? AbstractControl<U> : | |
| T extends FormControlState<infer U> ? FormControl<U|N> : | |
| T extends ControlConfig<infer U> ? FormControl<U|N> : | |
| // ControlConfig can be too much for the compiler to infer in the wrapped case. This is | |
| // not surprising, since it's practically death-by-polymorphism (e.g. the optional validators | |
| // members that might be arrays). Watch for ControlConfigs that might fall through. | |
| T extends Array<infer U|ValidatorFn|ValidatorFn[]|AsyncValidatorFn|AsyncValidatorFn[]> ? FormControl<U|N> : | |
| // Fallthough case: T is not a container type; use it directly as a value. | |
| FormControl<T|N>; |
Typescript expands the type union in the type map, because different parts of the type union might end up in different branches of the type map.
Here's a minimal example (playground)
type IsNumber<T> = T extends number ? true : false;
// false
type Test1 = IsNumber<string>;
// true
type Test2 = IsNumber<number>;
// boolean
type Test3 = IsNumber<string | number>;This can be solved by wrapping the type in the map with [] (playground):
type IsExactlyNumber<T> = [T] extends [number] ? true : false;
// false
type Test1 = IsExactlyNumber<string>;
// true
type Test2 = IsExactlyNumber<number>;
// false
type Test3 = IsExactlyNumber<string | number>;Reactions are currently unavailable
