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
This commit is contained in:
Dylan Hunn
2022-05-12 15:29:03 -07:00
committed by Jessica Janiuk
parent 4070049d8a
commit dba6a60861
5 changed files with 96 additions and 23 deletions
+11 -5
View File
@@ -494,7 +494,7 @@ export class FormGroupName extends AbstractFormGroupDirective implements OnInit,
}
// @public (undocumented)
export class FormRecord<TControl extends AbstractControl<ɵValue<TControl>, ɵRawValue<TControl>> = AbstractControl> extends FormGroup<{
export class FormRecord<TControl extends AbstractControl<Value<TControl>, RawValue<TControl>> = AbstractControl> extends FormGroup<{
[key: string]: TControl;
}> {
}
@@ -506,10 +506,10 @@ export interface FormRecord<TControl> {
}): void;
contains(controlName: string): boolean;
getRawValue(): {
[key: string]: ɵRawValue<TControl>;
[key: string]: RawValue<TControl>;
};
patchValue(value: {
[key: string]: ɵValue<TControl>;
[key: string]: Value<TControl>;
}, options?: {
onlySelf?: boolean;
emitEvent?: boolean;
@@ -519,7 +519,7 @@ export interface FormRecord<TControl> {
emitEvent?: boolean;
}): void;
reset(value?: {
[key: string]: ɵValue<TControl>;
[key: string]: Value<TControl>;
}, options?: {
onlySelf?: boolean;
emitEvent?: boolean;
@@ -528,7 +528,7 @@ export interface FormRecord<TControl> {
emitEvent?: boolean;
}): void;
setValue(value: {
[key: string]: ɵValue<TControl>;
[key: string]: Value<TControl>;
}, options?: {
onlySelf?: boolean;
emitEvent?: boolean;
@@ -767,6 +767,9 @@ export class RangeValueAccessor extends BuiltInControlValueAccessor implements C
static ɵfac: i0.ɵɵFactoryDeclaration<RangeValueAccessor, never>;
}
// @public
export type RawValue<T extends AbstractControl | undefined> = T extends AbstractControl<any, any> ? (T['setValue'] extends ((v: infer R) => void) ? R : never) : never;
// @public
export class ReactiveFormsModule {
static withConfig(opts: {
@@ -887,6 +890,9 @@ export class Validators {
static requiredTrue(control: AbstractControl): ValidationErrors | null;
}
// @public
export type Value<T extends AbstractControl | undefined> = T extends AbstractControl<any, any> ? T['value'] : never;
// @public (undocumented)
export const VERSION: Version;
+1 -1
View File
@@ -42,7 +42,7 @@ export {NgSelectOption, SelectControlValueAccessor} from './directives/select_co
export {SelectMultipleControlValueAccessor, ɵNgSelectMultipleOption} from './directives/select_multiple_control_value_accessor';
export {AsyncValidator, AsyncValidatorFn, CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MaxValidator, MinLengthValidator, MinValidator, PatternValidator, RequiredValidator, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
export {FormBuilder, NonNullableFormBuilder, UntypedFormBuilder, ɵElement} from './form_builder';
export {AbstractControl, AbstractControlOptions, FormControlStatus, ɵCoerceStrArrToNumArr, ɵGetProperty, ɵNavigate, ɵRawValue, ɵTokenize, ɵTypedOrUntyped, ɵValue, ɵWriteable} from './model/abstract_model';
export {AbstractControl, AbstractControlOptions, FormControlStatus, RawValue, Value, ɵCoerceStrArrToNumArr, ɵGetProperty, ɵNavigate, ɵTokenize, ɵTypedOrUntyped, ɵWriteable} from './model/abstract_model';
export {FormArray, UntypedFormArray, ɵFormArrayRawValue, ɵFormArrayValue} from './model/form_array';
export {FormControl, FormControlOptions, FormControlState, UntypedFormControl, ɵFormControlCtor} from './model/form_control';
export {FormGroup, FormRecord, UntypedFormGroup, ɵFormGroupRawValue, ɵFormGroupValue, ɵOptionalKeys} from './model/form_group';
+73 -6
View File
@@ -166,19 +166,86 @@ export type ɵIsAny<T, Y, N> = 0 extends(1&T) ? Y : N;
export type ɵTypedOrUntyped<T, Typed, Untyped> = ɵIsAny<T, Untyped, Typed>;
/**
* Value gives the type of `.value` in an `AbstractControl`.
* Value gives the value type corresponding to a control type.
*
* For internal use only.
* Note that the resulting type will follow the same rules as `.value` on your control, group, or
* array, including `undefined` for each group element which might be disabled.
*
* If you are trying to extract a value type for a data model, you probably want {@see RawValue},
* which will not have `undefined` in group keys.
*
* @usageNotes
*
* ### `FormControl` value type
*
* You can extract the value type of a single control:
*
* ```ts
* type NameControl = FormControl<string>;
* type NameValue = Value<NameControl>;
* ```
*
* The resulting type is `string`.
*
* ### `FormGroup` value type
*
* Imagine you have an interface defining the controls in your group. You can extract the shape of
* the values as follows:
*
* ```ts
* interface PartyFormControls {
* address: FormControl<string>;
* }
*
* // Value operates on controls; the object must be wrapped in a FormGroup.
* type PartyFormValues = Value<FormGroup<PartyFormControls>>;
* ```
*
* The resulting type is `{address: string|undefined}`.
*
* ### `FormArray` value type
*
* You can extract values from FormArrays as well:
*
* ```ts
* type GuestNamesControls = FormArray<FormControl<string>>;
*
* type NamesValues = Value<GuestNamesControls>;
* ```
*
* The resulting type is `string[]`.
*/
export type ɵValue<T extends AbstractControl|undefined> =
export type Value<T extends AbstractControl|undefined> =
T extends AbstractControl<any, any>? T['value'] : never;
/**
* RawValue gives the type of `.getRawValue()` in an `AbstractControl`.
* RawValue gives the raw value type corresponding to a control type.
*
* For internal use only.
* Note that the resulting type will follow the same rules as `.getRawValue()` on your control,
* group, or array. This means that all controls inside a group will be required, not optional,
* regardless of their disabled state.
*
* You may also wish to use {@see Value}, which will have `undefined` in group keys (which can be disabled).
*
* @usageNotes
*
* ### `FormGroup` raw value type
*
* Imagine you have an interface defining the controls in your group. You can extract the shape of
* the raw values as follows:
*
* ```ts
* interface PartyFormControls {
* address: FormControl<string>;
* }
*
* // RawValue operates on controls; the object must be wrapped in a FormGroup.
* type PartyFormValues = RawValue<FormGroup<PartyFormControls>>;
* ```
*
* The resulting type is `{address: string}`. (Note the absence of `undefined`.)
*/
export type ɵRawValue<T extends AbstractControl|undefined> = T extends AbstractControl<any, any>?
export type RawValue<T extends AbstractControl|undefined> = T extends AbstractControl<any, any>?
(T['setValue'] extends((v: infer R) => void) ? R : never) :
never;
+3 -3
View File
@@ -8,7 +8,7 @@
import {AsyncValidatorFn, ValidatorFn} from '../directives/validators';
import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertControlPresent, pickAsyncValidators, pickValidators, ɵRawValue, ɵTypedOrUntyped, ɵValue} from './abstract_model';
import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertControlPresent, pickAsyncValidators, pickValidators, RawValue, Value, ɵTypedOrUntyped} from './abstract_model';
/**
* 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
* case falls back to any[].
*/
export type ɵFormArrayValue<T extends AbstractControl<any>> =
ɵTypedOrUntyped<T, Array<ɵValue<T>>, any[]>;
ɵTypedOrUntyped<T, Array<Value<T>>, any[]>;
/**
* FormArrayRawValue extracts the type of `.getRawValue()` from a FormArray's element type, and
@@ -27,7 +27,7 @@ export type ɵFormArrayValue<T extends AbstractControl<any>> =
* Angular uses this type internally to support Typed Forms; do not use it directly.
*/
export type ɵFormArrayRawValue<T extends AbstractControl<any>> =
ɵTypedOrUntyped<T, Array<ɵRawValue<T>>, any[]>;
ɵTypedOrUntyped<T, Array<RawValue<T>>, any[]>;
/**
* Tracks the value and validity state of an array of `FormControl`,
+8 -8
View File
@@ -8,7 +8,7 @@
import {AsyncValidatorFn, ValidatorFn} from '../directives/validators';
import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertControlPresent, pickAsyncValidators, pickValidators, ɵRawValue, ɵTypedOrUntyped, ɵValue} from './abstract_model';
import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertControlPresent, pickAsyncValidators, pickValidators, RawValue, Value, ɵTypedOrUntyped} from './abstract_model';
/**
* FormGroupValue extracts the type of `.value` from a FormGroup's inner object type. The untyped
@@ -19,7 +19,7 @@ import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertC
* For internal use only.
*/
export type ɵFormGroupValue<T extends {[K in keyof T]?: AbstractControl<any>}> =
ɵTypedOrUntyped<T, Partial<{[K in keyof T]: ɵValue<T[K]>}>, {[key: string]: any}>;
ɵTypedOrUntyped<T, Partial<{[K in keyof T]: Value<T[K]>}>, {[key: string]: any}>;
/**
* 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>}>
* For internal use only.
*/
export type ɵFormGroupRawValue<T extends {[K in keyof T]?: AbstractControl<any>}> =
ɵTypedOrUntyped<T, {[K in keyof T]: ɵRawValue<T[K]>}, {[key: string]: any}>;
ɵTypedOrUntyped<T, {[K in keyof T]: RawValue<T[K]>}, {[key: string]: any}>;
/**
* OptionalKeys returns the union of all optional keys in the object.
@@ -606,7 +606,7 @@ export const UntypedFormGroup: UntypedFormGroupCtor = FormGroup;
export const isFormGroup = (control: unknown): control is FormGroup => control instanceof FormGroup;
export class FormRecord<TControl extends AbstractControl<ɵValue<TControl>, ɵRawValue<TControl>> =
export class FormRecord<TControl extends AbstractControl<Value<TControl>, RawValue<TControl>> =
AbstractControl> extends
FormGroup<{[key: string]: TControl}> {}
@@ -671,7 +671,7 @@ export interface FormRecord<TControl> {
*
* {@see FormGroup#setValue}
*/
setValue(value: {[key: string]: ɵValue<TControl>}, options?: {
setValue(value: {[key: string]: Value<TControl>}, options?: {
onlySelf?: boolean,
emitEvent?: boolean
}): void;
@@ -683,7 +683,7 @@ export interface FormRecord<TControl> {
*
* {@see FormGroup#patchValue}
*/
patchValue(value: {[key: string]: ɵValue<TControl>}, options?: {
patchValue(value: {[key: string]: Value<TControl>}, options?: {
onlySelf?: boolean,
emitEvent?: boolean
}): void;
@@ -694,7 +694,7 @@ export interface FormRecord<TControl> {
*
* {@see FormGroup#reset}
*/
reset(value?: {[key: string]: ɵValue<TControl>}, options?: {
reset(value?: {[key: string]: Value<TControl>}, options?: {
onlySelf?: boolean,
emitEvent?: boolean
}): void;
@@ -704,7 +704,7 @@ export interface FormRecord<TControl> {
*
* {@see FormGroup#getRawValue}
*/
getRawValue(): {[key: string]: ɵRawValue<TControl>};
getRawValue(): {[key: string]: RawValue<TControl>};
}
export const isFormRecord = (control: unknown): control is FormRecord =>