Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental: Strictly typed reactive forms #38406

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
109 changes: 55 additions & 54 deletions goldens/public-api/forms/forms.d.ts
@@ -1,12 +1,12 @@
export declare abstract class AbstractControl {
export declare abstract class AbstractControl<T = any> {
get asyncValidator(): AsyncValidatorFn | null;
set asyncValidator(asyncValidatorFn: AsyncValidatorFn | null);
get dirty(): boolean;
get disabled(): boolean;
get enabled(): boolean;
readonly errors: ValidationErrors | null;
get invalid(): boolean;
get parent(): FormGroup | FormArray | null;
get parent(): FormGroup<any> | FormArray<any> | null;
get pending(): boolean;
readonly pristine: boolean;
get root(): AbstractControl;
Expand All @@ -18,8 +18,8 @@ export declare abstract class AbstractControl {
get valid(): boolean;
get validator(): ValidatorFn | null;
set validator(validatorFn: ValidatorFn | null);
readonly value: any;
readonly valueChanges: Observable<any>;
readonly value: T | null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could also think of using Partial<T> | null as return type, as some values might have a null value if they weren't initialized and we provide a useful hint to the consumer of the object.

readonly valueChanges: Observable<T | null>;
constructor(validators: ValidatorFn | ValidatorFn[] | null, asyncValidators: AsyncValidatorFn | AsyncValidatorFn[] | null);
clearAsyncValidators(): void;
clearValidators(): void;
Expand All @@ -31,7 +31,7 @@ export declare abstract class AbstractControl {
onlySelf?: boolean;
emitEvent?: boolean;
}): void;
get(path: Array<string | number> | string): AbstractControl | null;
get<T = any>(path: Array<string | number> | string): AbstractControl<T> | null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we use something like:
get<S extends keyof T>(path: Array<string | number> | S): AbstractControl<T[S]> | null {

This way we can achieve that we can only access FormControls wich are defined in the type.

getError(errorCode: string, path?: Array<string | number> | string): any;
hasError(errorCode: string, path?: Array<string | number> | string): boolean;
markAllAsTouched(): void;
Expand All @@ -57,7 +57,7 @@ export declare abstract class AbstractControl {
setErrors(errors: ValidationErrors | null, opts?: {
emitEvent?: boolean;
}): void;
setParent(parent: FormGroup | FormArray): void;
setParent(parent: FormGroup<any> | FormArray<any>): void;
setValidators(newValidator: ValidatorFn | ValidatorFn[] | null): void;
abstract setValue(value: any, options?: Object): void;
updateValueAndValidity(opts?: {
Expand Down Expand Up @@ -167,20 +167,21 @@ export declare interface Form {
updateModel(dir: NgControl, value: any): void;
}

export declare class FormArray extends AbstractControl {
controls: AbstractControl[];
get length(): number;
constructor(controls: AbstractControl[], validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
at(index: number): AbstractControl;
export declare interface FormArray<T = any> extends AbstractControl<T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export declare interface FormArray<T = any> extends AbstractControl<T[]> {

its AbstractControl should have T[] as value type. AbstractControl<T[]>

formArray = new FormArray([ new FormControl(1), new FormControl(2) ]);
formArray.value // [ 1, 2 ] 

controls: AbstractControl<T>[];
length: number;
at(index: number): AbstractControl<T>;
clear(options?: {
emitEvent?: boolean;
}): void;
getRawValue(): any[];
getRawValue(): T[];
insert(index: number, control: AbstractControl<T>, options?: {
emitEvent?: boolean;
}): void;
insert(index: number, control: AbstractControl, options?: {
emitEvent?: boolean;
}): void;
patchValue(value: any[], options?: {
onlySelf?: boolean;
push(control: AbstractControl<T>, options?: {
emitEvent?: boolean;
}): void;
push(control: AbstractControl, options?: {
Expand All @@ -189,19 +190,13 @@ export declare class FormArray extends AbstractControl {
removeAt(index: number, options?: {
emitEvent?: boolean;
}): void;
reset(value?: any, options?: {
onlySelf?: boolean;
emitEvent?: boolean;
}): void;
setControl(index: number, control: AbstractControl, options?: {
emitEvent?: boolean;
}): void;
setValue(value: any[], options?: {
onlySelf?: boolean;
setControl(index: number, control: AbstractControl<T>, options?: {
emitEvent?: boolean;
}): void;
}

export declare const FormArray: FormArrayCtor;

export declare class FormArrayName extends ControlContainer implements OnInit, OnDestroy {
get control(): FormArray;
get formDirective(): FormGroupDirective | null;
Expand All @@ -213,40 +208,49 @@ export declare class FormArrayName extends ControlContainer implements OnInit, O
}

export declare class FormBuilder {
array(controlsConfig: any[], validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormArray;
control(formState: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl;
array<Item = any>(controlsConfig: FormControlConfig<Item>[], validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormArray;
control<T = any>(formState: FormControlState<T>, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl;
group(controlsConfig: {
[key: string]: any;
}, options?: AbstractControlOptions | null): FormGroup;
/** @deprecated */ group(controlsConfig: {
[key: string]: any;
/** @deprecated */ group<T extends object = any>(controlsConfig: {
[key in keyof T]: FormControlConfig<T[key]>;
}, options: {
[key: string]: any;
}): FormGroup;
}

export declare class FormControl extends AbstractControl {
constructor(formState?: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
patchValue(value: any, options?: {
export declare class FormControl<T = any> extends AbstractControl<T> {
constructor(formState: FormControlState<T> | null, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
constructor(formState: FormControlState<T>, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
constructor(formState?: T, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
constructor(formState?: null, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
patchValue(value: null | T, options?: {
sonukapoor marked this conversation as resolved.
Show resolved Hide resolved
onlySelf?: boolean;
emitEvent?: boolean;
emitModelToViewChange?: boolean;
emitViewToModelChange?: boolean;
}): void;
registerOnChange(fn: Function): void;
registerOnDisabledChange(fn: (isDisabled: boolean) => void): void;
reset(formState?: any, options?: {
reset(formState?: FormControlState<T>, options?: {
onlySelf?: boolean;
emitEvent?: boolean;
}): void;
setValue(value: any, options?: {
setValue(value: null | T, options?: {
onlySelf?: boolean;
emitEvent?: boolean;
emitModelToViewChange?: boolean;
emitViewToModelChange?: boolean;
}): void;
}

export declare type FormControlConfig<T> = AbstractControl<T> | FormControlState<T> | [
FormControlState<T>,
(ValidatorFn | ValidatorFn[] | AbstractControlOptions)?,
(AsyncValidatorFn | AsyncValidatorFn[])?
];

export declare class FormControlDirective extends NgControl implements OnChanges, OnDestroy {
get control(): FormControl;
form: FormControl;
Expand Down Expand Up @@ -275,43 +279,40 @@ export declare class FormControlName extends NgControl implements OnChanges, OnD
viewToModelUpdate(newValue: any): void;
}

export declare class FormGroup extends AbstractControl {
export declare type FormControlState<T> = null | T | {
value: null | T;
disabled: boolean;
};

export declare interface FormGroup<T extends object = any> extends AbstractControl<T> {
controls: {
[key: string]: AbstractControl;
[key in keyof T]: AbstractControl<T[key]>;
};
constructor(controls: {
[key: string]: AbstractControl;
}, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null);
addControl(name: string, control: AbstractControl, options?: {
emitEvent?: boolean;
}): void;
contains(controlName: string): boolean;
getRawValue(): any;
patchValue(value: {
[key: string]: any;
}, options?: {
onlySelf?: boolean;
addControl<K extends keyof T>(name: K, control: AbstractControl<T[K]>, options?: {
emitEvent?: boolean;
}): void;
registerControl(name: string, control: AbstractControl): AbstractControl;
removeControl(name: string, options?: {
addControl<K extends string>(name: NotAKey<K, T>, control: AbstractControl<any>, options?: {
emitEvent?: boolean;
}): void;
reset(value?: any, options?: {
contains(controlName: keyof T): boolean;
getRawValue(): T;
patchValue<K extends keyof T>(value: Partial<T>, options?: {
onlySelf?: boolean;
emitEvent?: boolean;
}): void;
setControl(name: string, control: AbstractControl, options?: {
registerControl<K extends keyof T>(name: K, control: AbstractControl<T[K]>, options?: {
emitEvent?: boolean;
}): AbstractControl;
removeControl(name: keyof T, options?: {
emitEvent?: boolean;
}): void;
setValue(value: {
[key: string]: any;
}, options?: {
onlySelf?: boolean;
setControl<K extends keyof T>(name: K, control: AbstractControl<T[K]>, options?: {
emitEvent?: boolean;
}): void;
}

export declare const FormGroup: FormGroupCtor;

export declare class FormGroupDirective extends ControlContainer implements Form, OnChanges, OnDestroy {
get control(): FormGroup;
directives: FormControlName[];
Expand Down
31 changes: 22 additions & 9 deletions packages/forms/src/form_builder.ts
Expand Up @@ -10,7 +10,7 @@ import {Injectable} from '@angular/core';

import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
import {ReactiveFormsModule} from './form_providers';
import {AbstractControl, AbstractControlOptions, FormArray, FormControl, FormGroup, FormHooks} from './model';
import {AbstractControl, AbstractControlOptions, FormArray, FormControl, FormControlState, FormGroup, FormHooks} from './model';

function isAbstractControlOptions(options: AbstractControlOptions|
{[key: string]: any}): options is AbstractControlOptions {
Expand All @@ -19,6 +19,18 @@ function isAbstractControlOptions(options: AbstractControlOptions|
(<AbstractControlOptions>options).updateOn !== undefined;
}

/**
* @publicApi
*/
export type FormControlConfig<T> =
| AbstractControl<T>
| FormControlState<T>
| [
FormControlState<T>,
(ValidatorFn | ValidatorFn[] | AbstractControlOptions)?,
(AsyncValidatorFn | AsyncValidatorFn[])?
];

/**
* @description
* Creates an `AbstractControl` from a user-specified configuration.
Expand Down Expand Up @@ -74,12 +86,12 @@ export class FormBuilder {
* Note: the legacy format is deprecated and might be removed in one of the next major versions
* of Angular.
*/
group(
controlsConfig: {[key: string]: any},
group<T extends object = any>(
controlsConfig: {[key in keyof T]: FormControlConfig<T[key]>},
options: {[key: string]: any},
): FormGroup;
group(
controlsConfig: {[key: string]: any},
group<T extends object = any>(
controlsConfig: {[key in keyof T]: FormControlConfig<T[key]>},
options: AbstractControlOptions|{[key: string]: any}|null = null): FormGroup {
const controls = this._reduceControls(controlsConfig);

Expand Down Expand Up @@ -126,8 +138,9 @@ export class FormBuilder {
* <code-example path="forms/ts/formBuilder/form_builder_example.ts" region="disabled-control">
* </code-example>
*/
control(
formState: any, validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
control<T = any>(
formState: FormControlState<T>,
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormControl {
return new FormControl(formState, validatorOrOpts, asyncValidator);
}
Expand All @@ -146,8 +159,8 @@ export class FormBuilder {
* @param asyncValidator A single async validator or array of async validator
* functions.
*/
array(
controlsConfig: any[],
array<Item = any>(
controlsConfig: FormControlConfig<Item>[],
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormArray {
const controls = controlsConfig.map(c => this._createControl(c));
Expand Down
4 changes: 2 additions & 2 deletions packages/forms/src/forms.ts
Expand Up @@ -44,8 +44,8 @@ export {NgSelectOption, SelectControlValueAccessor} from './directives/select_co
export {SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
export {ɵ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} from './form_builder';
export {AbstractControl, AbstractControlOptions, FormArray, FormControl, FormGroup} from './model';
export {FormBuilder, FormControlConfig} from './form_builder';
export {AbstractControl, AbstractControlOptions, FormArray, FormControl, FormControlState, FormGroup} from './model';
export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './validators';
export {VERSION} from './version';

Expand Down