diff --git a/packages/forms/src/form_builder.ts b/packages/forms/src/form_builder.ts index 306b3e925a6..86c7e1baa20 100644 --- a/packages/forms/src/form_builder.ts +++ b/packages/forms/src/form_builder.ts @@ -48,16 +48,19 @@ export type ControlConfig = [T|FormControlState, (ValidatorFn|(ValidatorFn * The flag N, if not never, makes the resulting `FormControl` have N in its type. */ export type ɵElement = - T extends FormControl ? FormControl : - T extends FormGroup ? FormGroup : - T extends FormArray ? FormArray : - T extends AbstractControl ? AbstractControl : - T extends FormControlState ? FormControl : - T extends ControlConfig ? FormControl : + // The `extends` checks are wrapped in arrays in order to prevent TypeScript from applying type unions + // through the distributive conditional type. This is the officially recommended solution: + // https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types + [T] extends [FormControl] ? FormControl : + [T] extends [FormGroup] ? FormGroup : + [T] extends [FormArray] ? FormArray : + [T] extends [AbstractControl] ? AbstractControl : + [T] extends [FormControlState] ? FormControl : + [T] extends [ControlConfig] ? FormControl : // 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 ? FormControl : + [T] extends [Array] ? FormControl : // Fallthough case: T is not a container type; use it directly as a value. FormControl; diff --git a/packages/forms/test/typed_integration_spec.ts b/packages/forms/test/typed_integration_spec.ts index 3fd881da828..0092e7ba9e2 100644 --- a/packages/forms/test/typed_integration_spec.ts +++ b/packages/forms/test/typed_integration_spec.ts @@ -1366,6 +1366,18 @@ describe('Typed Class', () => { expect(c.value).toEqual({foo: 'bar'}); }); + it('without distributing union types', () => { + const c = fb.group({foo: 'bar' as string | number}); + { + type ControlsType = {foo: FormControl}; + let t: ControlsType = c.controls; + let t1 = c.controls; + t1 = null as unknown as ControlsType; + } + let fc = c.controls.foo; + fc = new FormControl('', {initialValueIsDefault: true}); + }); + describe('from objects with FormControls', () => { it('from objects with builder FormGroups', () => { const c = fb.group({foo: fb.group({baz: 'bar'})});