diff --git a/goldens/public-api/forms/forms.md b/goldens/public-api/forms/forms.md index c4ac69d606b..8b8668b7556 100644 --- a/goldens/public-api/forms/forms.md +++ b/goldens/public-api/forms/forms.md @@ -297,22 +297,27 @@ export class FormBuilder { static ɵprov: i0.ɵɵInjectableDeclaration; } -// @public -export class FormControl extends AbstractControl { - constructor(formState?: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | FormControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null); +// @public (undocumented) +export interface FormControl extends AbstractControl { + // (undocumented) readonly defaultValue: any; + // (undocumented) patchValue(value: any, options?: { onlySelf?: boolean; emitEvent?: boolean; emitModelToViewChange?: boolean; emitViewToModelChange?: boolean; }): void; + // (undocumented) registerOnChange(fn: Function): void; + // (undocumented) registerOnDisabledChange(fn: (isDisabled: boolean) => void): void; + // (undocumented) reset(formState?: any, options?: { onlySelf?: boolean; emitEvent?: boolean; }): void; + // (undocumented) setValue(value: any, options?: { onlySelf?: boolean; emitEvent?: boolean; @@ -321,6 +326,15 @@ export class FormControl extends AbstractControl { }): void; } +// @public +export const FormControl: FormControlCtor; + +// @public +export interface FormControlCtor { + new (): FormControl; + new (value: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | FormControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl; +} + // @public export class FormControlDirective extends NgControl implements OnChanges, OnDestroy { constructor(validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[], valueAccessors: ControlValueAccessor[], _ngModelWarningConfig: string | null); @@ -344,6 +358,30 @@ export class FormControlDirective extends NgControl implements OnChanges, OnDest static ɵfac: i0.ɵɵFactoryDeclaration; } +// @public +export class FormControlImpl extends AbstractControl { + constructor(formState?: any, validatorOrOpts?: ValidatorFn | ValidatorFn[] | FormControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null); + readonly defaultValue: any; + patchValue(value: any, options?: { + onlySelf?: boolean; + emitEvent?: boolean; + emitModelToViewChange?: boolean; + emitViewToModelChange?: boolean; + }): void; + registerOnChange(fn: Function): void; + registerOnDisabledChange(fn: (isDisabled: boolean) => void): void; + reset(formState?: any, options?: { + onlySelf?: boolean; + emitEvent?: boolean; + }): void; + setValue(value: any, options?: { + onlySelf?: boolean; + emitEvent?: boolean; + emitModelToViewChange?: boolean; + emitViewToModelChange?: boolean; + }): void; +} + // @public export class FormControlName extends NgControl implements OnChanges, OnDestroy { constructor(parent: ControlContainer, validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[], valueAccessors: ControlValueAccessor[], _ngModelWarningConfig: string | null); diff --git a/packages/forms/src/forms.ts b/packages/forms/src/forms.ts index 712f8c413f8..27e1133504f 100644 --- a/packages/forms/src/forms.ts +++ b/packages/forms/src/forms.ts @@ -43,7 +43,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} from './form_builder'; -export {AbstractControl, AbstractControlOptions, FormArray, FormControl, FormControlOptions, FormControlStatus, FormGroup} from './model'; +export {AbstractControl, AbstractControlOptions, FormArray, FormControl, FormControlCtor, FormControlImpl, FormControlOptions, FormControlStatus, FormGroup} from './model'; export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './validators'; export {VERSION} from './version'; diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts index 5e2a6b668df..014e57f538e 100644 --- a/packages/forms/src/model.ts +++ b/packages/forms/src/model.ts @@ -161,9 +161,9 @@ export interface AbstractControlOptions { export interface FormControlOptions extends AbstractControlOptions { /** * @description - * Whether to use the initial value used to construct the FormControl as its default value as - * well. If this option is false or not provided, the default value of a FormControl is `null`. - * When a FormControl is {@link FormControl#reset} without an explicit value, its value reverts to + * Whether to use the initial value used to construct the {@link FormControl} as its default value + * as well. If this option is false or not provided, the default value of a FormControl is `null`. + * When a FormControl is reset without an explicit value, its value reverts to * its default value. */ initialValueIsDefault?: boolean; @@ -1177,7 +1177,6 @@ export abstract class AbstractControl { this._updateOn = opts.updateOn!; } } - /** * Check to see if parent has been marked artificially dirty. * @@ -1189,6 +1188,74 @@ export abstract class AbstractControl { } } +export interface FormControl extends AbstractControl { + readonly defaultValue: any; + /** @internal */ + _onChange: Function[]; + /** @internal */ + _pendingValue: any; + /** @internal */ + _pendingChange: boolean; + setValue(value: any, options?: { + onlySelf?: boolean, + emitEvent?: boolean, + emitModelToViewChange?: boolean, + emitViewToModelChange?: boolean + }): void; + patchValue(value: any, options?: { + onlySelf?: boolean, + emitEvent?: boolean, + emitModelToViewChange?: boolean, + emitViewToModelChange?: boolean + }): void; + reset(formState?: any, options?: {onlySelf?: boolean, emitEvent?: boolean}): void; + /** @internal */ + _updateValue(): void; + /** @internal */ + _anyControls(condition: Function): boolean; + /** @internal */ + _allControlsDisabled(): boolean; + registerOnChange(fn: Function): void; + /** @internal */ + _unregisterOnChange(fn: Function): void; + registerOnDisabledChange(fn: (isDisabled: boolean) => void): void; + /** @internal */ + _unregisterOnDisabledChange(fn: (isDisabled: boolean) => void): void; + /** @internal */ + _forEachChild(cb: Function): void; + /** @internal */ + _syncPendingControls(): boolean; +} + +/** + * Various available constructors for `FormControl`. + * Do not use this interface directly. Instead, use `FormControl`: + * ``` + * const fc = new FormControl('foo'); + * ``` + */ +export interface FormControlCtor { + /** + * Construct a FormControl with no initial value or validators. + */ + new(): FormControl; + + /** + * Creates a new `FormControl` instance. + * + * @param formState Initializes the control with an initial value, + * or an object that defines the initial value and disabled state. + * + * @param validatorOrOpts A synchronous validator function, or an array of + * such functions, or an `FormControlOptions` object that contains validation functions + * and a validation trigger. + * + * @param asyncValidator A single async validator or array of async validator functions + */ + new(value: any, validatorOrOpts?: ValidatorFn|ValidatorFn[]|FormControlOptions|null, + asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormControl; +} + /** * Tracks the value and validation status of an individual form control. * @@ -1210,7 +1277,7 @@ export abstract class AbstractControl { * ```ts * const control = new FormControl('some value'); * console.log(control.value); // 'some value' - *``` + * ``` * * The following example initializes the control with a form state object. The `value` * and `disabled` keys are required in this case. @@ -1283,10 +1350,8 @@ export abstract class AbstractControl { * console.log(control.value); // 'Drew' * console.log(control.status); // 'DISABLED' * ``` - * - * @publicApi */ -export class FormControl extends AbstractControl { +export class FormControlImpl extends AbstractControl { /** * The default value of this FormControl, used whenever the control is reset without an explicit * value. See {@link FormControlOptions#initialValueIsDefault} for more information on configuring @@ -1296,14 +1361,14 @@ export class FormControl extends AbstractControl { public readonly defaultValue: any = null; /** @internal */ - _onChange: Array = []; + _onChange: Function[] = []; /** * This field holds a pending value that has not yet been applied to the form's value. * It is `any` because the value is untyped. * @internal */ - _pendingValue: any; + _pendingValue!: any; /** @internal */ _pendingChange: boolean = false; @@ -1315,7 +1380,7 @@ export class FormControl extends AbstractControl { * or an object that defines the initial value and disabled state. * * @param validatorOrOpts A synchronous validator function, or an array of - * such functions, or an `AbstractControlOptions` object that contains validation functions + * such functions, or a `FormControlOptions` object that contains validation functions * and a validation trigger. * * @param asyncValidator A single async validator or array of async validator functions @@ -1526,6 +1591,12 @@ export class FormControl extends AbstractControl { } } +/** + * @overriddenImplementation The constructor for FormControl is decoupled from its implementation. + * This allows us to provide multiple constructor signatures. + */ +export const FormControl: FormControlCtor = FormControlImpl as FormControlCtor; + /** * Tracks the value and validity state of a group of `FormControl` instances. * diff --git a/packages/forms/test/form_control_spec.ts b/packages/forms/test/form_control_spec.ts index 1f880cbacb6..8b684a35a2e 100644 --- a/packages/forms/test/form_control_spec.ts +++ b/packages/forms/test/form_control_spec.ts @@ -1527,5 +1527,24 @@ describe('FormControl', () => { }); }); }); + + describe('can be extended', () => { + // We don't technically support extending Forms classes, but people do it anyway. + // We need to make sure that there is some way to extend them to avoid causing breakage. + + class FCExt extends FormControl { + constructor(formState?: any|{ + value?: any; + disabled?: boolean; + }, ...args: any) { + super(formState, ...args); + } + } + + it('should perform basic FormControl operations', () => { + const nc = new FCExt({value: 'foo'}); + nc.setValue('bar'); + }); + }); }); })();