diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index 4f349bcdd11..185bd53349c 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -548,9 +548,6 @@ { "name": "_randomChar" }, - { - "name": "componentDefCount" - }, { "name": "_symbolIterator" }, @@ -656,6 +653,9 @@ { "name": "collectStylingFromTAttrs" }, + { + "name": "componentDefCount" + }, { "name": "compose" }, @@ -1595,4 +1595,4 @@ { "name": "ɵɵtext" } -] +] \ No newline at end of file diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index be9a29f5f1e..4a829dafabb 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -539,9 +539,6 @@ { "name": "_randomChar" }, - { - "name": "componentDefCount" - }, { "name": "_symbolIterator" }, @@ -638,6 +635,9 @@ { "name": "collectStylingFromTAttrs" }, + { + "name": "componentDefCount" + }, { "name": "composeAsyncValidators" }, @@ -1583,4 +1583,4 @@ { "name": "ɵɵtext" } -] +] \ No newline at end of file diff --git a/packages/forms/src/form_builder.ts b/packages/forms/src/form_builder.ts index d27b071772e..e9bce0cab7d 100644 --- a/packages/forms/src/form_builder.ts +++ b/packages/forms/src/form_builder.ts @@ -52,11 +52,26 @@ export type ɵElement = // 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 + // + // Identify FormControl container types. [T] extends [FormControl] ? FormControl : + // Or FormControl containers that are optional in their parent group. + [T] extends [FormControl|undefined] ? FormControl : + // FormGroup containers. [T] extends [FormGroup] ? FormGroup : + // Optional FormGroup containers. + [T] extends [FormGroup|undefined] ? FormGroup : + // FormArray containers. [T] extends [FormArray] ? FormArray : + // Optional FormArray containers. + [T] extends [FormArray|undefined] ? FormArray : + // Otherwise unknown AbstractControl containers. [T] extends [AbstractControl] ? AbstractControl : + // Optional AbstractControl containers. + [T] extends [AbstractControl|undefined] ? AbstractControl : + // FormControlState object container, which produces a nullable control. [T] extends [FormControlState] ? FormControl : + // A ControlConfig tuple, which produces a nullable control. [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 diff --git a/packages/forms/test/typed_integration_spec.ts b/packages/forms/test/typed_integration_spec.ts index b6a3dd24c72..21302856974 100644 --- a/packages/forms/test/typed_integration_spec.ts +++ b/packages/forms/test/typed_integration_spec.ts @@ -1032,6 +1032,14 @@ describe('Typed Class', () => { } }); + it('from objects with optional keys', () => { + const controls = {name: fb.control('')}; + const foo: + FormGroup<{name: FormControl; address?: FormControl;}> = + fb.group<{name: FormControl; address?: FormControl;}>( + controls); + }); + it('from objects with FormControlState', () => { const c = fb.group({foo: {value: 'bar', disabled: false}}); {