Skip to content

Commit 610bebf

Browse files
bougwalthePunderWoman
authored andcommitted
fix(forms): Allow ControlState as reset arguments for FormGroup/FormRecord (angular#55860)
This change also decorelate the `reset` type argument from `TValue` by adding a 3rd generic parameter to `AbstractControl`. This improves the typings overall. PR Close angular#55860
1 parent b37fd97 commit 610bebf

4 files changed

Lines changed: 97 additions & 14 deletions

File tree

goldens/public-api/forms/index.api.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { SimpleChanges } from '@angular/core';
2121
import { Version } from '@angular/core';
2222

2323
// @public
24-
export abstract class AbstractControl<TValue = any, TRawValue extends TValue = TValue> {
24+
export abstract class AbstractControl<TValue = any, TRawValue extends TValue = TValue, TValueWithOptionalControlStates = any> {
2525
constructor(validators: ValidatorFn | ValidatorFn[] | null, asyncValidators: AsyncValidatorFn | AsyncValidatorFn[] | null);
2626
addAsyncValidators(validators: AsyncValidatorFn | AsyncValidatorFn[]): void;
2727
addValidators(validators: ValidatorFn | ValidatorFn[]): void;
@@ -82,7 +82,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
8282
get pristine(): boolean;
8383
removeAsyncValidators(validators: AsyncValidatorFn | AsyncValidatorFn[]): void;
8484
removeValidators(validators: ValidatorFn | ValidatorFn[]): void;
85-
abstract reset(value?: TValue, options?: Object): void;
85+
abstract reset(value?: TValueWithOptionalControlStates, options?: Object): void;
8686
get root(): AbstractControl;
8787
setAsyncValidators(validators: AsyncValidatorFn | AsyncValidatorFn[] | null): void;
8888
setErrors(errors: ValidationErrors | null, opts?: {
@@ -415,7 +415,7 @@ export type FormControlStatus = 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED';
415415
// @public
416416
export class FormGroup<TControl extends {
417417
[K in keyof TControl]: AbstractControl<any>;
418-
} = any> extends AbstractControlTypedOrUntyped<TControl, ɵFormGroupValue<TControl>, any>, ɵTypedOrUntyped<TControl, ɵFormGroupRawValue<TControl>, any>> {
418+
} = any> extends AbstractControlTypedOrUntyped<TControl, ɵFormGroupValue<TControl>, any>, ɵTypedOrUntyped<TControl, ɵFormGroupRawValue<TControl>, any>, ɵTypedOrUntyped<TControl, ɵFormGroupArgumentValue<TControl>, any>> {
419419
constructor(controls: TControl, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
420420
addControl(this: FormGroup<{
421421
[key: string]: AbstractControl<any>;
@@ -455,7 +455,7 @@ export class FormGroup<TControl extends {
455455
removeControl<S extends string>(name: ɵOptionalKeys<TControl> & S, options?: {
456456
emitEvent?: boolean;
457457
}): void;
458-
reset(value?: ɵTypedOrUntyped<TControl, ɵFormGroupValue<TControl>, any>, options?: {
458+
reset(value?: ɵTypedOrUntyped<TControl, ɵFormGroupArgumentValue<TControl>, any>, options?: {
459459
onlySelf?: boolean;
460460
emitEvent?: boolean;
461461
}): void;
@@ -544,7 +544,7 @@ export interface FormRecord<TControl> {
544544
emitEvent?: boolean;
545545
}): void;
546546
reset(value?: {
547-
[key: string]: ɵValue<TControl>;
547+
[key: string]: ɵValue<TControl> | FormControlStateValue<TControl>>;
548548
}, options?: {
549549
onlySelf?: boolean;
550550
emitEvent?: boolean;

packages/forms/src/model/abstract_model.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,11 @@ export type ɵGetProperty<T, K> =
465465
*
466466
* @publicApi
467467
*/
468-
export abstract class AbstractControl<TValue = any, TRawValue extends TValue = TValue> {
468+
export abstract class AbstractControl<
469+
TValue = any,
470+
TRawValue extends TValue = TValue,
471+
TValueWithOptionalControlStates = any,
472+
> {
469473
/** @internal */
470474
_pendingDirty = false;
471475

@@ -1321,7 +1325,7 @@ export abstract class AbstractControl<TValue = any, TRawValue extends TValue = T
13211325
/**
13221326
* Resets the control. Abstract method (implemented in sub-classes).
13231327
*/
1324-
abstract reset(value?: TValue, options?: Object): void;
1328+
abstract reset(value?: TValueWithOptionalControlStates, options?: Object): void;
13251329

13261330
/**
13271331
* The raw value of this control. For most control implementations, the raw value will include

packages/forms/src/model/form_group.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
ɵTypedOrUntyped,
2222
ɵValue,
2323
} from './abstract_model';
24+
import {FormControlState} from './form_control';
2425

2526
/**
2627
* FormGroupValue extracts the type of `.value` from a FormGroup's inner object type. The untyped
@@ -30,6 +31,14 @@ import {
3031
*
3132
* For internal use only.
3233
*/
34+
35+
export type ɵFormGroupArgumentValue<T extends {[K in keyof T]?: AbstractControl<any>}> =
36+
ɵTypedOrUntyped<
37+
T,
38+
Partial<{[K in keyof T]: ɵValue<T[K]> | FormControlState<ɵValue<T[K]>>}>,
39+
{[key: string]: any}
40+
>;
41+
3342
export type ɵFormGroupValue<T extends {[K in keyof T]?: AbstractControl<any>}> = ɵTypedOrUntyped<
3443
T,
3544
Partial<{[K in keyof T]: ɵValue<T[K]>}>,
@@ -176,7 +185,8 @@ export class FormGroup<
176185
TControl extends {[K in keyof TControl]: AbstractControl<any>} = any,
177186
> extends AbstractControl<
178187
ɵTypedOrUntyped<TControl, ɵFormGroupValue<TControl>, any>,
179-
ɵTypedOrUntyped<TControl, ɵFormGroupRawValue<TControl>, any>
188+
ɵTypedOrUntyped<TControl, ɵFormGroupRawValue<TControl>, any>,
189+
ɵTypedOrUntyped<TControl, ɵFormGroupArgumentValue<TControl>, any>
180190
> {
181191
/**
182192
* Creates a new `FormGroup` instance.
@@ -544,11 +554,7 @@ export class FormGroup<
544554
* ```
545555
*/
546556
override reset(
547-
value: ɵTypedOrUntyped<
548-
TControl,
549-
ɵFormGroupValue<TControl>,
550-
any
551-
> = {} as unknown as ɵFormGroupValue<TControl>,
557+
value: ɵTypedOrUntyped<TControl, ɵFormGroupArgumentValue<TControl>, any> = {},
552558
options: {onlySelf?: boolean; emitEvent?: boolean} = {},
553559
): void {
554560
this._forEachChild((control: AbstractControl, name) => {
@@ -798,7 +804,7 @@ export interface FormRecord<TControl> {
798804
* See `FormGroup#reset` for additional information.
799805
*/
800806
reset(
801-
value?: {[key: string]: ɵValue<TControl>},
807+
value?: {[key: string]: ɵValue<TControl> | FormControlState<ɵValue<TControl>>},
802808
options?: {
803809
onlySelf?: boolean;
804810
emitEvent?: boolean;

packages/forms/test/typed_integration_spec.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,17 @@ describe('Typed Class', () => {
158158
let ufc: UntypedFormControl;
159159
ufc = c;
160160
});
161+
162+
it('should infer value type correctly', () => {
163+
function valueFn<T, U extends T, V>(ctrl: AbstractControl<T, U, V>) {
164+
return ctrl.value;
165+
}
166+
167+
const c = new FormControl<{foo: string}>({foo: ''});
168+
let val: {foo: string} | null;
169+
val = valueFn(c);
170+
val = valueFn(c)!;
171+
});
161172
});
162173

163174
describe('FormGroup', () => {
@@ -676,6 +687,39 @@ describe('Typed Class', () => {
676687
});
677688
ufg = fg;
678689
});
690+
691+
it('should support ControlState as reset argument', () => {
692+
const fg = new FormGroup({
693+
name: new FormControl({foo: 'foo'}),
694+
});
695+
fg.reset({name: {foo: 'bar'}});
696+
697+
fg.reset({name: {value: {foo: 'bar'}, disabled: true}});
698+
});
699+
700+
it('should infer value type correctly', () => {
701+
function valueFn<T, U extends T, V>(ctrl: AbstractControl<T, U, V>) {
702+
return ctrl.value;
703+
}
704+
const fg = new FormGroup({
705+
name: new FormControl('bob'),
706+
});
707+
708+
let val: Partial<{name: string | null}>;
709+
val = valueFn(fg);
710+
});
711+
712+
it('should reset inferred formGroup', () => {
713+
function ctrlFn<T, U extends T, V>(ctrl: AbstractControl<T, U, V>) {
714+
return ctrl;
715+
}
716+
717+
const fg = new FormGroup({
718+
name: new FormControl('bob'),
719+
});
720+
ctrlFn(fg).reset({name: 'matt'});
721+
ctrlFn(fg).reset({name: {value: 'matt', disabled: true}});
722+
});
679723
});
680724

681725
describe('FormRecord', () => {
@@ -767,6 +811,16 @@ describe('Typed Class', () => {
767811
}),
768812
).toThrowError(/NG01002: Must supply a value for form control/);
769813
});
814+
815+
it('should reset inferred formarray', () => {
816+
function ctrlFn<T, U extends T, V>(ctrl: AbstractControl<T, U, V>) {
817+
return ctrl;
818+
}
819+
820+
let c = new FormRecord<FormControl<number>>({a: new FormControl(42, {nonNullable: true})});
821+
ctrlFn(c).reset({a: 99});
822+
ctrlFn(c).reset({a: {value: 99, disabled: true}});
823+
});
770824
});
771825

772826
describe('FormArray', () => {
@@ -942,6 +996,25 @@ describe('Typed Class', () => {
942996
const fa = new FormArray([new FormControl('bob')]);
943997
ufa = fa;
944998
});
999+
1000+
it('should infer value type correctly', () => {
1001+
function valueFn<T, U extends T, V>(ctrl: AbstractControl<T, U, V>) {
1002+
return ctrl.value;
1003+
}
1004+
1005+
const fa = new FormArray([new FormControl('bob')]);
1006+
let val: (string | null)[];
1007+
val = valueFn(fa);
1008+
});
1009+
1010+
it('should reset inferred formarray', () => {
1011+
function ctrlFn<T, U extends T, V>(ctrl: AbstractControl<T, U, V>) {
1012+
return ctrl;
1013+
}
1014+
1015+
const fa = new FormArray([new FormControl('bob')]);
1016+
ctrlFn(fa).reset(['jim', 'jam', 'joe']);
1017+
});
9451018
});
9461019

9471020
it('model classes support a complex, deeply nested case', () => {

0 commit comments

Comments
 (0)