Skip to content

Commit dba6a60

Browse files
dylhunnthePunderWoman
authored andcommitted
fix(forms): Value and RawValue should be part of the public API. (#45978)
Consider a typed group for storing contact information: ``` declare interface ContactControls { name: FormControl<string|null>; } contactForm: FormGroup<ContactControls> = ...; saveForm(form: FormGroup<ContactControls>) { service.newContact(contactForm.value); } ``` What should be the type of `newContact`? The answer, of course, is the value type: ``` declare interface Contact { name: string|null; } class ContactService { newContact(c: Contact) {} } ``` This is quite redundant, and therefore, we should allow the value type to be generated automatically. We already have the helper types to do this -- we just need to document and export them. Then, this becomes possible: ``` class ContactService { newContact(c: RawValue<FormGroup<ContactControls>>) {} } ``` PR Close #45978
1 parent 4070049 commit dba6a60

File tree

5 files changed

+96
-23
lines changed

5 files changed

+96
-23
lines changed

goldens/public-api/forms/index.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ export class FormGroupName extends AbstractFormGroupDirective implements OnInit,
494494
}
495495

496496
// @public (undocumented)
497-
export class FormRecord<TControl extends AbstractControl<ɵValue<TControl>, ɵRawValue<TControl>> = AbstractControl> extends FormGroup<{
497+
export class FormRecord<TControl extends AbstractControl<Value<TControl>, RawValue<TControl>> = AbstractControl> extends FormGroup<{
498498
[key: string]: TControl;
499499
}> {
500500
}
@@ -506,10 +506,10 @@ export interface FormRecord<TControl> {
506506
}): void;
507507
contains(controlName: string): boolean;
508508
getRawValue(): {
509-
[key: string]: ɵRawValue<TControl>;
509+
[key: string]: RawValue<TControl>;
510510
};
511511
patchValue(value: {
512-
[key: string]: ɵValue<TControl>;
512+
[key: string]: Value<TControl>;
513513
}, options?: {
514514
onlySelf?: boolean;
515515
emitEvent?: boolean;
@@ -519,7 +519,7 @@ export interface FormRecord<TControl> {
519519
emitEvent?: boolean;
520520
}): void;
521521
reset(value?: {
522-
[key: string]: ɵValue<TControl>;
522+
[key: string]: Value<TControl>;
523523
}, options?: {
524524
onlySelf?: boolean;
525525
emitEvent?: boolean;
@@ -528,7 +528,7 @@ export interface FormRecord<TControl> {
528528
emitEvent?: boolean;
529529
}): void;
530530
setValue(value: {
531-
[key: string]: ɵValue<TControl>;
531+
[key: string]: Value<TControl>;
532532
}, options?: {
533533
onlySelf?: boolean;
534534
emitEvent?: boolean;
@@ -767,6 +767,9 @@ export class RangeValueAccessor extends BuiltInControlValueAccessor implements C
767767
static ɵfac: i0.ɵɵFactoryDeclaration<RangeValueAccessor, never>;
768768
}
769769

770+
// @public
771+
export type RawValue<T extends AbstractControl | undefined> = T extends AbstractControl<any, any> ? (T['setValue'] extends ((v: infer R) => void) ? R : never) : never;
772+
770773
// @public
771774
export class ReactiveFormsModule {
772775
static withConfig(opts: {
@@ -887,6 +890,9 @@ export class Validators {
887890
static requiredTrue(control: AbstractControl): ValidationErrors | null;
888891
}
889892

893+
// @public
894+
export type Value<T extends AbstractControl | undefined> = T extends AbstractControl<any, any> ? T['value'] : never;
895+
890896
// @public (undocumented)
891897
export const VERSION: Version;
892898

packages/forms/src/forms.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export {NgSelectOption, SelectControlValueAccessor} from './directives/select_co
4242
export {SelectMultipleControlValueAccessor, ɵNgSelectMultipleOption} from './directives/select_multiple_control_value_accessor';
4343
export {AsyncValidator, AsyncValidatorFn, CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MaxValidator, MinLengthValidator, MinValidator, PatternValidator, RequiredValidator, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
4444
export {FormBuilder, NonNullableFormBuilder, UntypedFormBuilder, ɵElement} from './form_builder';
45-
export {AbstractControl, AbstractControlOptions, FormControlStatus, ɵCoerceStrArrToNumArr, ɵGetProperty, ɵNavigate, ɵRawValue, ɵTokenize, ɵTypedOrUntyped, ɵValue, ɵWriteable} from './model/abstract_model';
45+
export {AbstractControl, AbstractControlOptions, FormControlStatus, RawValue, Value, ɵCoerceStrArrToNumArr, ɵGetProperty, ɵNavigate, ɵTokenize, ɵTypedOrUntyped, ɵWriteable} from './model/abstract_model';
4646
export {FormArray, UntypedFormArray, ɵFormArrayRawValue, ɵFormArrayValue} from './model/form_array';
4747
export {FormControl, FormControlOptions, FormControlState, UntypedFormControl, ɵFormControlCtor} from './model/form_control';
4848
export {FormGroup, FormRecord, UntypedFormGroup, ɵFormGroupRawValue, ɵFormGroupValue, ɵOptionalKeys} from './model/form_group';

packages/forms/src/model/abstract_model.ts

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,19 +166,86 @@ export type ɵIsAny<T, Y, N> = 0 extends(1&T) ? Y : N;
166166
export type ɵTypedOrUntyped<T, Typed, Untyped> = ɵIsAny<T, Untyped, Typed>;
167167

168168
/**
169-
* Value gives the type of `.value` in an `AbstractControl`.
169+
* Value gives the value type corresponding to a control type.
170170
*
171-
* For internal use only.
171+
* Note that the resulting type will follow the same rules as `.value` on your control, group, or
172+
* array, including `undefined` for each group element which might be disabled.
173+
*
174+
* If you are trying to extract a value type for a data model, you probably want {@see RawValue},
175+
* which will not have `undefined` in group keys.
176+
*
177+
* @usageNotes
178+
*
179+
* ### `FormControl` value type
180+
*
181+
* You can extract the value type of a single control:
182+
*
183+
* ```ts
184+
* type NameControl = FormControl<string>;
185+
* type NameValue = Value<NameControl>;
186+
* ```
187+
*
188+
* The resulting type is `string`.
189+
*
190+
* ### `FormGroup` value type
191+
*
192+
* Imagine you have an interface defining the controls in your group. You can extract the shape of
193+
* the values as follows:
194+
*
195+
* ```ts
196+
* interface PartyFormControls {
197+
* address: FormControl<string>;
198+
* }
199+
*
200+
* // Value operates on controls; the object must be wrapped in a FormGroup.
201+
* type PartyFormValues = Value<FormGroup<PartyFormControls>>;
202+
* ```
203+
*
204+
* The resulting type is `{address: string|undefined}`.
205+
*
206+
* ### `FormArray` value type
207+
*
208+
* You can extract values from FormArrays as well:
209+
*
210+
* ```ts
211+
* type GuestNamesControls = FormArray<FormControl<string>>;
212+
*
213+
* type NamesValues = Value<GuestNamesControls>;
214+
* ```
215+
*
216+
* The resulting type is `string[]`.
172217
*/
173-
export type ɵValue<T extends AbstractControl|undefined> =
218+
export type Value<T extends AbstractControl|undefined> =
174219
T extends AbstractControl<any, any>? T['value'] : never;
175220

176221
/**
177-
* RawValue gives the type of `.getRawValue()` in an `AbstractControl`.
222+
* RawValue gives the raw value type corresponding to a control type.
178223
*
179-
* For internal use only.
224+
* Note that the resulting type will follow the same rules as `.getRawValue()` on your control,
225+
* group, or array. This means that all controls inside a group will be required, not optional,
226+
* regardless of their disabled state.
227+
*
228+
* You may also wish to use {@see Value}, which will have `undefined` in group keys (which can be disabled).
229+
*
230+
* @usageNotes
231+
*
232+
* ### `FormGroup` raw value type
233+
*
234+
* Imagine you have an interface defining the controls in your group. You can extract the shape of
235+
* the raw values as follows:
236+
*
237+
* ```ts
238+
* interface PartyFormControls {
239+
* address: FormControl<string>;
240+
* }
241+
*
242+
* // RawValue operates on controls; the object must be wrapped in a FormGroup.
243+
* type PartyFormValues = RawValue<FormGroup<PartyFormControls>>;
244+
* ```
245+
*
246+
* The resulting type is `{address: string}`. (Note the absence of `undefined`.)
180247
*/
181-
export type ɵRawValue<T extends AbstractControl|undefined> = T extends AbstractControl<any, any>?
248+
export type RawValue<T extends AbstractControl|undefined> = T extends AbstractControl<any, any>?
182249
(T['setValue'] extends((v: infer R) => void) ? R : never) :
183250
never;
184251

packages/forms/src/model/form_array.ts

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

99
import {AsyncValidatorFn, ValidatorFn} from '../directives/validators';
1010

11-
import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertControlPresent, pickAsyncValidators, pickValidators, ɵRawValue, ɵTypedOrUntyped, ɵValue} from './abstract_model';
11+
import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertControlPresent, pickAsyncValidators, pickValidators, RawValue, Value, ɵTypedOrUntyped} from './abstract_model';
1212

1313
/**
1414
* FormArrayValue extracts the type of `.value` from a FormArray's element type, and wraps it in an
@@ -18,7 +18,7 @@ import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertC
1818
* case falls back to any[].
1919
*/
2020
export type ɵFormArrayValue<T extends AbstractControl<any>> =
21-
ɵTypedOrUntyped<T, Array<ɵValue<T>>, any[]>;
21+
ɵTypedOrUntyped<T, Array<Value<T>>, any[]>;
2222

2323
/**
2424
* FormArrayRawValue extracts the type of `.getRawValue()` from a FormArray's element type, and
@@ -27,7 +27,7 @@ export type ɵFormArrayValue<T extends AbstractControl<any>> =
2727
* Angular uses this type internally to support Typed Forms; do not use it directly.
2828
*/
2929
export type ɵFormArrayRawValue<T extends AbstractControl<any>> =
30-
ɵTypedOrUntyped<T, Array<ɵRawValue<T>>, any[]>;
30+
ɵTypedOrUntyped<T, Array<RawValue<T>>, any[]>;
3131

3232
/**
3333
* Tracks the value and validity state of an array of `FormControl`,

packages/forms/src/model/form_group.ts

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

99
import {AsyncValidatorFn, ValidatorFn} from '../directives/validators';
1010

11-
import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertControlPresent, pickAsyncValidators, pickValidators, ɵRawValue, ɵTypedOrUntyped, ɵValue} from './abstract_model';
11+
import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertControlPresent, pickAsyncValidators, pickValidators, RawValue, Value, ɵTypedOrUntyped} from './abstract_model';
1212

1313
/**
1414
* FormGroupValue extracts the type of `.value` from a FormGroup's inner object type. The untyped
@@ -19,7 +19,7 @@ import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertC
1919
* For internal use only.
2020
*/
2121
export type ɵFormGroupValue<T extends {[K in keyof T]?: AbstractControl<any>}> =
22-
ɵTypedOrUntyped<T, Partial<{[K in keyof T]: ɵValue<T[K]>}>, {[key: string]: any}>;
22+
ɵTypedOrUntyped<T, Partial<{[K in keyof T]: Value<T[K]>}>, {[key: string]: any}>;
2323

2424
/**
2525
* FormGroupRawValue extracts the type of `.getRawValue()` from a FormGroup's inner object type. The
@@ -30,7 +30,7 @@ export type ɵFormGroupValue<T extends {[K in keyof T]?: AbstractControl<any>}>
3030
* For internal use only.
3131
*/
3232
export type ɵFormGroupRawValue<T extends {[K in keyof T]?: AbstractControl<any>}> =
33-
ɵTypedOrUntyped<T, {[K in keyof T]: ɵRawValue<T[K]>}, {[key: string]: any}>;
33+
ɵTypedOrUntyped<T, {[K in keyof T]: RawValue<T[K]>}, {[key: string]: any}>;
3434

3535
/**
3636
* OptionalKeys returns the union of all optional keys in the object.
@@ -606,7 +606,7 @@ export const UntypedFormGroup: UntypedFormGroupCtor = FormGroup;
606606

607607
export const isFormGroup = (control: unknown): control is FormGroup => control instanceof FormGroup;
608608

609-
export class FormRecord<TControl extends AbstractControl<ɵValue<TControl>, ɵRawValue<TControl>> =
609+
export class FormRecord<TControl extends AbstractControl<Value<TControl>, RawValue<TControl>> =
610610
AbstractControl> extends
611611
FormGroup<{[key: string]: TControl}> {}
612612

@@ -671,7 +671,7 @@ export interface FormRecord<TControl> {
671671
*
672672
* {@see FormGroup#setValue}
673673
*/
674-
setValue(value: {[key: string]: ɵValue<TControl>}, options?: {
674+
setValue(value: {[key: string]: Value<TControl>}, options?: {
675675
onlySelf?: boolean,
676676
emitEvent?: boolean
677677
}): void;
@@ -683,7 +683,7 @@ export interface FormRecord<TControl> {
683683
*
684684
* {@see FormGroup#patchValue}
685685
*/
686-
patchValue(value: {[key: string]: ɵValue<TControl>}, options?: {
686+
patchValue(value: {[key: string]: Value<TControl>}, options?: {
687687
onlySelf?: boolean,
688688
emitEvent?: boolean
689689
}): void;
@@ -694,7 +694,7 @@ export interface FormRecord<TControl> {
694694
*
695695
* {@see FormGroup#reset}
696696
*/
697-
reset(value?: {[key: string]: ɵValue<TControl>}, options?: {
697+
reset(value?: {[key: string]: Value<TControl>}, options?: {
698698
onlySelf?: boolean,
699699
emitEvent?: boolean
700700
}): void;
@@ -704,7 +704,7 @@ export interface FormRecord<TControl> {
704704
*
705705
* {@see FormGroup#getRawValue}
706706
*/
707-
getRawValue(): {[key: string]: ɵRawValue<TControl>};
707+
getRawValue(): {[key: string]: RawValue<TControl>};
708708
}
709709

710710
export const isFormRecord = (control: unknown): control is FormRecord =>

0 commit comments

Comments
 (0)