From 0723331b2a58b31fa5a4dc5ad88ca1051bb70297 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Mon, 27 Jul 2020 19:31:40 -0700 Subject: [PATCH] refactor(forms): move common validators-related logic to the `AbstractControlDirective` class (#38280) This commit refactors validators-related logic that is common across most of the directives. A couple notes on this refactoring: * common logic was moved to the `AbstractControlDirective` class (including `validator` and `asyncValidator` getters) * sync/async validators are now composed in `AbstractControlDirective` class eagerly when validators are set with `_setValidators` and `_setAsyncValidators` calls and the result is stored in directive instance (thus getters return cached versions of validator fn). This is needed to make sure composed validator function remains the same (retains its identity) for a given directive instance, so that this function can be added and later removed from an instance of an AbstractControl-based class (like `FormControl`). Preserving validator function is required to perform proper cleanup (in followup PRs) of the AbstractControl-based classes when a directive is destroyed. PR Close #38280 --- goldens/circular-deps/packages.json | 122 ++++++++---------- goldens/public-api/forms/forms.d.ts | 16 +-- .../directives/abstract_control_directive.ts | 67 +++++++++- .../abstract_form_group_directive.ts | 37 +----- packages/forms/src/directives/ng_control.ts | 43 ------ packages/forms/src/directives/ng_form.ts | 4 +- packages/forms/src/directives/ng_model.ts | 24 +--- .../forms/src/directives/ng_model_group.ts | 4 +- .../form_control_directive.ts | 25 +--- .../reactive_directives/form_control_name.ts | 24 +--- .../form_group_directive.ts | 16 +-- .../reactive_directives/form_group_name.ts | 33 +---- packages/forms/src/directives/shared.ts | 14 +- packages/forms/src/model.ts | 4 +- packages/forms/src/validators.ts | 20 +++ packages/forms/test/directives_spec.ts | 3 +- 16 files changed, 177 insertions(+), 279 deletions(-) diff --git a/goldens/circular-deps/packages.json b/goldens/circular-deps/packages.json index 8c26c0a489..2a042db5e6 100644 --- a/goldens/circular-deps/packages.json +++ b/goldens/circular-deps/packages.json @@ -1002,27 +1002,6 @@ "packages/core/testing/src/r3_test_bed.ts", "packages/core/testing/src/test_bed.ts" ], - [ - "packages/forms/src/directives/abstract_control_directive.ts", - "packages/forms/src/model.ts", - "packages/forms/src/directives/shared.ts" - ], - [ - "packages/forms/src/directives/abstract_control_directive.ts", - "packages/forms/src/model.ts", - "packages/forms/src/directives/shared.ts", - "packages/forms/src/directives/abstract_form_group_directive.ts", - "packages/forms/src/directives/control_container.ts" - ], - [ - "packages/forms/src/directives/abstract_control_directive.ts", - "packages/forms/src/model.ts", - "packages/forms/src/directives/shared.ts", - "packages/forms/src/directives/abstract_form_group_directive.ts", - "packages/forms/src/directives/control_container.ts", - "packages/forms/src/directives/form_interface.ts", - "packages/forms/src/directives/ng_control.ts" - ], [ "packages/forms/src/directives/abstract_form_group_directive.ts", "packages/forms/src/directives/control_container.ts", @@ -1030,10 +1009,7 @@ ], [ "packages/forms/src/directives/abstract_form_group_directive.ts", - "packages/forms/src/directives/control_container.ts", - "packages/forms/src/directives/form_interface.ts", - "packages/forms/src/model.ts", - "packages/forms/src/directives/shared.ts" + "packages/forms/src/directives/form_interface.ts" ], [ "packages/forms/src/directives/abstract_form_group_directive.ts", @@ -1041,13 +1017,59 @@ ], [ "packages/forms/src/directives/abstract_form_group_directive.ts", - "packages/forms/src/model.ts", - "packages/forms/src/directives/shared.ts" + "packages/forms/src/directives/shared.ts", + "packages/forms/src/directives/control_container.ts", + "packages/forms/src/directives/form_interface.ts" ], [ + "packages/forms/src/directives/abstract_form_group_directive.ts", + "packages/forms/src/directives/shared.ts", + "packages/forms/src/directives/ng_control.ts", "packages/forms/src/directives/control_container.ts", - "packages/forms/src/directives/form_interface.ts", - "packages/forms/src/directives/ng_control.ts" + "packages/forms/src/directives/form_interface.ts" + ], + [ + "packages/forms/src/directives/abstract_form_group_directive.ts", + "packages/forms/src/directives/shared.ts", + "packages/forms/src/directives/reactive_directives/form_group_name.ts" + ], + [ + "packages/forms/src/directives/abstract_form_group_directive.ts", + "packages/forms/src/directives/shared.ts", + "packages/forms/src/directives/reactive_directives/form_group_name.ts", + "packages/forms/src/directives/control_container.ts", + "packages/forms/src/directives/form_interface.ts" + ], + [ + "packages/forms/src/directives/abstract_form_group_directive.ts", + "packages/forms/src/directives/shared.ts", + "packages/forms/src/directives/reactive_directives/form_group_name.ts", + "packages/forms/src/directives/reactive_directives/form_group_directive.ts", + "packages/forms/src/directives/control_container.ts", + "packages/forms/src/directives/form_interface.ts" + ], + [ + "packages/forms/src/directives/abstract_form_group_directive.ts", + "packages/forms/src/directives/shared.ts", + "packages/forms/src/directives/reactive_directives/form_group_name.ts", + "packages/forms/src/directives/reactive_directives/form_group_directive.ts", + "packages/forms/src/directives/form_interface.ts" + ], + [ + "packages/forms/src/directives/abstract_form_group_directive.ts", + "packages/forms/src/directives/shared.ts", + "packages/forms/src/directives/reactive_directives/form_group_name.ts", + "packages/forms/src/directives/reactive_directives/form_group_directive.ts", + "packages/forms/src/directives/reactive_directives/form_control_name.ts" + ], + [ + "packages/forms/src/directives/abstract_form_group_directive.ts", + "packages/forms/src/directives/shared.ts", + "packages/forms/src/directives/reactive_directives/form_group_name.ts", + "packages/forms/src/directives/reactive_directives/form_group_directive.ts", + "packages/forms/src/directives/reactive_directives/form_control_name.ts", + "packages/forms/src/directives/control_container.ts", + "packages/forms/src/directives/form_interface.ts" ], [ "packages/forms/src/directives/ng_form.ts", @@ -1065,14 +1087,6 @@ "packages/forms/src/directives/reactive_directives/form_group_directive.ts", "packages/forms/src/directives/reactive_directives/form_control_name.ts" ], - [ - "packages/forms/src/directives/reactive_directives/form_control_directive.ts", - "packages/forms/src/model.ts", - "packages/forms/src/directives/shared.ts", - "packages/forms/src/directives/reactive_directives/form_group_name.ts", - "packages/forms/src/directives/reactive_directives/form_group_directive.ts", - "packages/forms/src/directives/reactive_directives/form_control_name.ts" - ], [ "packages/forms/src/directives/reactive_directives/form_control_name.ts", "packages/forms/src/directives/reactive_directives/form_group_directive.ts" @@ -1088,13 +1102,6 @@ "packages/forms/src/directives/reactive_directives/form_group_name.ts", "packages/forms/src/directives/reactive_directives/form_group_directive.ts" ], - [ - "packages/forms/src/directives/reactive_directives/form_control_name.ts", - "packages/forms/src/model.ts", - "packages/forms/src/directives/shared.ts", - "packages/forms/src/directives/reactive_directives/form_group_name.ts", - "packages/forms/src/directives/reactive_directives/form_group_directive.ts" - ], [ "packages/forms/src/directives/reactive_directives/form_group_directive.ts", "packages/forms/src/directives/reactive_directives/form_group_name.ts" @@ -1104,40 +1111,23 @@ "packages/forms/src/directives/shared.ts", "packages/forms/src/directives/reactive_directives/form_group_name.ts" ], - [ - "packages/forms/src/directives/reactive_directives/form_group_directive.ts", - "packages/forms/src/model.ts", - "packages/forms/src/directives/shared.ts", - "packages/forms/src/directives/reactive_directives/form_group_name.ts" - ], [ "packages/forms/src/directives/reactive_directives/form_group_name.ts", "packages/forms/src/directives/shared.ts" ], [ - "packages/forms/src/directives/reactive_directives/form_group_name.ts", - "packages/forms/src/model.ts", - "packages/forms/src/directives/shared.ts" - ], - [ - "packages/forms/src/directives/shared.ts", - "packages/forms/src/model.ts" - ], - [ - "packages/forms/src/directives/shared.ts", - "packages/forms/src/validators.ts", "packages/forms/src/directives/validators.ts", "packages/forms/src/model.ts" ], - [ - "packages/forms/src/directives/shared.ts", - "packages/forms/src/validators.ts", - "packages/forms/src/model.ts" - ], [ "packages/forms/src/directives/validators.ts", "packages/forms/src/validators.ts" ], + [ + "packages/forms/src/directives/validators.ts", + "packages/forms/src/validators.ts", + "packages/forms/src/model.ts" + ], [ "packages/language-service/src/completions.ts", "packages/language-service/src/template.ts", diff --git a/goldens/public-api/forms/forms.d.ts b/goldens/public-api/forms/forms.d.ts index 87df5fcc74..24129a6157 100644 --- a/goldens/public-api/forms/forms.d.ts +++ b/goldens/public-api/forms/forms.d.ts @@ -67,6 +67,7 @@ export declare abstract class AbstractControl { } export declare abstract class AbstractControlDirective { + get asyncValidator(): AsyncValidatorFn | null; abstract get control(): AbstractControl | null; get dirty(): boolean | null; get disabled(): boolean | null; @@ -81,6 +82,7 @@ export declare abstract class AbstractControlDirective { get touched(): boolean | null; get untouched(): boolean | null; get valid(): boolean | null; + get validator(): ValidatorFn | null; get value(): any; get valueChanges(): Observable | null; getError(errorCode: string, path?: Array | string): any; @@ -95,11 +97,9 @@ export declare interface AbstractControlOptions { } export declare class AbstractFormGroupDirective extends ControlContainer implements OnInit, OnDestroy { - get asyncValidator(): AsyncValidatorFn | null; get control(): FormGroup; get formDirective(): Form | null; get path(): string[]; - get validator(): ValidatorFn | null; ngOnDestroy(): void; ngOnInit(): void; } @@ -193,12 +193,10 @@ export declare class FormArray extends AbstractControl { } export declare class FormArrayName extends ControlContainer implements OnInit, OnDestroy { - get asyncValidator(): AsyncValidatorFn | null; get control(): FormArray; get formDirective(): FormGroupDirective | null; name: string | number | null; get path(): string[]; - get validator(): ValidatorFn | null; constructor(parent: ControlContainer, validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[]); ngOnDestroy(): void; ngOnInit(): void; @@ -237,14 +235,12 @@ export declare class FormControl extends AbstractControl { } export declare class FormControlDirective extends NgControl implements OnChanges { - get asyncValidator(): AsyncValidatorFn | null; get control(): FormControl; form: FormControl; set isDisabled(isDisabled: boolean); /** @deprecated */ model: any; get path(): string[]; /** @deprecated */ update: EventEmitter; - get validator(): ValidatorFn | null; viewModel: any; constructor(validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[], valueAccessors: ControlValueAccessor[], _ngModelWarningConfig: string | null); ngOnChanges(changes: SimpleChanges): void; @@ -252,7 +248,6 @@ export declare class FormControlDirective extends NgControl implements OnChanges } export declare class FormControlName extends NgControl implements OnChanges, OnDestroy { - get asyncValidator(): AsyncValidatorFn; readonly control: FormControl; get formDirective(): any; set isDisabled(isDisabled: boolean); @@ -260,7 +255,6 @@ export declare class FormControlName extends NgControl implements OnChanges, OnD name: string | number | null; get path(): string[]; /** @deprecated */ update: EventEmitter; - get validator(): ValidatorFn | null; constructor(parent: ControlContainer, validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[], valueAccessors: ControlValueAccessor[], _ngModelWarningConfig: string | null); ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; @@ -306,7 +300,7 @@ export declare class FormGroupDirective extends ControlContainer implements Form ngSubmit: EventEmitter; get path(): string[]; readonly submitted: boolean; - constructor(_validators: (Validator | ValidatorFn)[], _asyncValidators: (AsyncValidator | AsyncValidatorFn)[]); + constructor(validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[]); addControl(dir: FormControlName): FormControl; addFormArray(dir: FormArrayName): void; addFormGroup(dir: FormGroupName): void; @@ -352,9 +346,7 @@ export declare const NG_VALIDATORS: InjectionToken<(Function | Validator)[]>; export declare const NG_VALUE_ACCESSOR: InjectionToken; export declare abstract class NgControl extends AbstractControlDirective { - get asyncValidator(): AsyncValidatorFn | null; name: string | number | null; - get validator(): ValidatorFn | null; valueAccessor: ControlValueAccessor | null; abstract viewToModelUpdate(newValue: any): void; } @@ -398,7 +390,6 @@ export declare class NgForm extends ControlContainer implements Form, AfterViewI } export declare class NgModel extends NgControl implements OnChanges, OnDestroy { - get asyncValidator(): AsyncValidatorFn | null; readonly control: FormControl; get formDirective(): any; isDisabled: boolean; @@ -411,7 +402,6 @@ export declare class NgModel extends NgControl implements OnChanges, OnDestroy { }; get path(): string[]; update: EventEmitter; - get validator(): ValidatorFn | null; viewModel: any; constructor(parent: ControlContainer, validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[], valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; diff --git a/packages/forms/src/directives/abstract_control_directive.ts b/packages/forms/src/directives/abstract_control_directive.ts index 54e5eeeace..e8f07b55ad 100644 --- a/packages/forms/src/directives/abstract_control_directive.ts +++ b/packages/forms/src/directives/abstract_control_directive.ts @@ -7,8 +7,12 @@ */ import {Observable} from 'rxjs'; + import {AbstractControl} from '../model'; -import {ValidationErrors} from './validators'; +import {composeAsyncValidators, composeValidators} from '../validators'; + +import {AsyncValidator, AsyncValidatorFn, ValidationErrors, Validator, ValidatorFn} from './validators'; + /** * @description @@ -165,6 +169,67 @@ export abstract class AbstractControlDirective { return null; } + /** + * Contains the result of merging synchronous validators into a single validator function + * (combined using `Validators.compose`). + */ + private _composedValidatorFn: ValidatorFn|null|undefined; + + /** + * Contains the result of merging asynchronous validators into a single validator function + * (combined using `Validators.composeAsync`). + */ + private _composedAsyncValidatorFn: AsyncValidatorFn|null|undefined; + + /** + * Set of synchronous validators as they were provided while calling `setValidators` function. + * @internal + */ + _rawValidators: Array = []; + + /** + * Set of asynchronous validators as they were provided while calling `setAsyncValidators` + * function. + * @internal + */ + _rawAsyncValidators: Array = []; + + /** + * Sets synchronous validators for this directive. + * @internal + */ + _setValidators(validators: Array|undefined): void { + this._rawValidators = validators || []; + this._composedValidatorFn = composeValidators(this._rawValidators); + } + + /** + * Sets asynchronous validators for this directive. + * @internal + */ + _setAsyncValidators(validators: Array|undefined): void { + this._rawAsyncValidators = validators || []; + this._composedAsyncValidatorFn = composeAsyncValidators(this._rawAsyncValidators); + } + + /** + * @description + * Synchronous validator function composed of all the synchronous validators registered with this + * directive. + */ + get validator(): ValidatorFn|null { + return this._composedValidatorFn || null; + } + + /** + * @description + * Asynchronous validator function composed of all the asynchronous validators registered with + * this directive. + */ + get asyncValidator(): AsyncValidatorFn|null { + return this._composedAsyncValidatorFn || null; + } + /** * @description * Resets the control with the provided value if the control is present. diff --git a/packages/forms/src/directives/abstract_form_group_directive.ts b/packages/forms/src/directives/abstract_form_group_directive.ts index 5519221902..d5497344ec 100644 --- a/packages/forms/src/directives/abstract_form_group_directive.ts +++ b/packages/forms/src/directives/abstract_form_group_directive.ts @@ -12,8 +12,7 @@ import {FormGroup} from '../model'; import {ControlContainer} from './control_container'; import {Form} from './form_interface'; -import {composeAsyncValidators, composeValidators, controlPath} from './shared'; -import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators'; +import {controlPath} from './shared'; @@ -34,24 +33,6 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn // TODO(issue/24571): remove '!'. _parent!: ControlContainer; - /** - * @description - * An array of synchronous validators for the group - * - * @internal - */ - // TODO(issue/24571): remove '!'. - _validators!: (Validator|ValidatorFn)[]; - - /** - * @description - * An array of async validators for the group - * - * @internal - */ - // TODO(issue/24571): remove '!'. - _asyncValidators!: (AsyncValidator|AsyncValidatorFn)[]; - /** @nodoc */ ngOnInit(): void { this._checkParentType(); @@ -91,22 +72,6 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn return this._parent ? this._parent.formDirective : null; } - /** - * @description - * The synchronous validators registered with this group. - */ - get validator(): ValidatorFn|null { - return composeValidators(this._validators); - } - - /** - * @description - * The async validators registered with this group. - */ - get asyncValidator(): AsyncValidatorFn|null { - return composeAsyncValidators(this._asyncValidators); - } - /** @internal */ _checkParentType(): void {} } diff --git a/packages/forms/src/directives/ng_control.ts b/packages/forms/src/directives/ng_control.ts index 23cb4edfd0..f9234624d2 100644 --- a/packages/forms/src/directives/ng_control.ts +++ b/packages/forms/src/directives/ng_control.ts @@ -6,17 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ - import {AbstractControlDirective} from './abstract_control_directive'; import {ControlContainer} from './control_container'; import {ControlValueAccessor} from './control_value_accessor'; -import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators'; -function unimplemented(): any { - if (typeof ngDevMode === 'undefined' || ngDevMode) { - throw new Error('unimplemented'); - } -} /** * @description @@ -46,42 +39,6 @@ export abstract class NgControl extends AbstractControlDirective { */ valueAccessor: ControlValueAccessor|null = null; - /** - * @description - * The uncomposed array of synchronous validators for the control - * - * @internal - */ - _rawValidators: Array = []; - - /** - * @description - * The uncomposed array of async validators for the control - * - * @internal - */ - _rawAsyncValidators: Array = []; - - /** - * @description - * The registered synchronous validator function for the control - * - * @throws An exception that this method is not implemented - */ - get validator(): ValidatorFn|null { - return unimplemented(); - } - - /** - * @description - * The registered async validator function for the control - * - * @throws An exception that this method is not implemented - */ - get asyncValidator(): AsyncValidatorFn|null { - return unimplemented(); - } - /** * @description * The callback method to update the model from the view when requested diff --git a/packages/forms/src/directives/ng_form.ts b/packages/forms/src/directives/ng_form.ts index c221aae8fa..ffb1593250 100644 --- a/packages/forms/src/directives/ng_form.ts +++ b/packages/forms/src/directives/ng_form.ts @@ -9,14 +9,14 @@ import {AfterViewInit, Directive, EventEmitter, forwardRef, Inject, Input, Optional, Self} from '@angular/core'; import {AbstractControl, FormControl, FormGroup, FormHooks} from '../model'; -import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators'; +import {composeAsyncValidators, composeValidators, NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators'; import {ControlContainer} from './control_container'; import {Form} from './form_interface'; import {NgControl} from './ng_control'; import {NgModel} from './ng_model'; import {NgModelGroup} from './ng_model_group'; -import {composeAsyncValidators, composeValidators, removeDir, setUpControl, setUpFormContainer, syncPendingControls} from './shared'; +import {removeDir, setUpControl, setUpFormContainer, syncPendingControls} from './shared'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators'; export const formDirectiveProvider: any = { diff --git a/packages/forms/src/directives/ng_model.ts b/packages/forms/src/directives/ng_model.ts index ae7a434332..6cd329877b 100644 --- a/packages/forms/src/directives/ng_model.ts +++ b/packages/forms/src/directives/ng_model.ts @@ -17,7 +17,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor' import {NgControl} from './ng_control'; import {NgForm} from './ng_form'; import {NgModelGroup} from './ng_model_group'; -import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared'; +import {controlPath, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared'; import {TemplateDrivenErrors} from './template_driven_errors'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators'; @@ -208,8 +208,8 @@ export class NgModel extends NgControl implements OnChanges, OnDestroy { @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) { super(); this._parent = parent; - this._rawValidators = validators || []; - this._rawAsyncValidators = asyncValidators || []; + this._setValidators(validators); + this._setAsyncValidators(asyncValidators); this.valueAccessor = selectValueAccessor(this, valueAccessors); } @@ -249,24 +249,6 @@ export class NgModel extends NgControl implements OnChanges, OnDestroy { return this._parent ? this._parent.formDirective : null; } - /** - * @description - * Synchronous validator function composed of all the synchronous validators - * registered with this directive. - */ - get validator(): ValidatorFn|null { - return composeValidators(this._rawValidators); - } - - /** - * @description - * Async validator function composed of all the async validators registered with this - * directive. - */ - get asyncValidator(): AsyncValidatorFn|null { - return composeAsyncValidators(this._rawAsyncValidators); - } - /** * @description * Sets the new value for the view model and emits an `ngModelChange` event. diff --git a/packages/forms/src/directives/ng_model_group.ts b/packages/forms/src/directives/ng_model_group.ts index 6618cdc179..434a4de918 100644 --- a/packages/forms/src/directives/ng_model_group.ts +++ b/packages/forms/src/directives/ng_model_group.ts @@ -64,8 +64,8 @@ export class NgModelGroup extends AbstractFormGroupDirective implements OnInit, (AsyncValidator|AsyncValidatorFn)[]) { super(); this._parent = parent; - this._validators = validators; - this._asyncValidators = asyncValidators; + this._setValidators(validators); + this._setAsyncValidators(asyncValidators); } /** @internal */ diff --git a/packages/forms/src/directives/reactive_directives/form_control_directive.ts b/packages/forms/src/directives/reactive_directives/form_control_directive.ts index 05d9565d6d..547ea216d1 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_directive.ts @@ -13,7 +13,7 @@ import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor'; import {NgControl} from '../ng_control'; import {ReactiveErrors} from '../reactive_errors'; -import {_ngModelWarning, composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from '../shared'; +import {_ngModelWarning, isPropertyUpdated, selectValueAccessor, setUpControl} from '../shared'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../validators'; @@ -51,7 +51,6 @@ export const formControlBinding: any = { * @publicApi */ @Directive({selector: '[formControl]', providers: [formControlBinding], exportAs: 'ngForm'}) - export class FormControlDirective extends NgControl implements OnChanges { /** * Internal reference to the view model value. @@ -111,8 +110,8 @@ export class FormControlDirective extends NgControl implements OnChanges { @Optional() @Inject(NG_MODEL_WITH_FORM_CONTROL_WARNING) private _ngModelWarningConfig: string| null) { super(); - this._rawValidators = validators || []; - this._rawAsyncValidators = asyncValidators || []; + this._setValidators(validators); + this._setAsyncValidators(asyncValidators); this.valueAccessor = selectValueAccessor(this, valueAccessors); } @@ -141,24 +140,6 @@ export class FormControlDirective extends NgControl implements OnChanges { return []; } - /** - * @description - * Synchronous validator function composed of all the synchronous validators - * registered with this directive. - */ - get validator(): ValidatorFn|null { - return composeValidators(this._rawValidators); - } - - /** - * @description - * Async validator function composed of all the async validators registered with this - * directive. - */ - get asyncValidator(): AsyncValidatorFn|null { - return composeAsyncValidators(this._rawAsyncValidators); - } - /** * @description * The `FormControl` bound to this directive. diff --git a/packages/forms/src/directives/reactive_directives/form_control_name.ts b/packages/forms/src/directives/reactive_directives/form_control_name.ts index e2d3fc65a2..9e76a3f91a 100644 --- a/packages/forms/src/directives/reactive_directives/form_control_name.ts +++ b/packages/forms/src/directives/reactive_directives/form_control_name.ts @@ -15,7 +15,7 @@ import {ControlContainer} from '../control_container'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor'; import {NgControl} from '../ng_control'; import {ReactiveErrors} from '../reactive_errors'; -import {_ngModelWarning, composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor} from '../shared'; +import {_ngModelWarning, controlPath, isPropertyUpdated, selectValueAccessor} from '../shared'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../validators'; import {NG_MODEL_WITH_FORM_CONTROL_WARNING} from './form_control_directive'; @@ -136,8 +136,8 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { null) { super(); this._parent = parent; - this._rawValidators = validators || []; - this._rawAsyncValidators = asyncValidators || []; + this._setValidators(validators); + this._setAsyncValidators(asyncValidators); this.valueAccessor = selectValueAccessor(this, valueAccessors); } @@ -186,24 +186,6 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { return this._parent ? this._parent.formDirective : null; } - /** - * @description - * Synchronous validator function composed of all the synchronous validators - * registered with this directive. - */ - get validator(): ValidatorFn|null { - return composeValidators(this._rawValidators); - } - - /** - * @description - * Async validator function composed of all the async validators registered with this - * directive. - */ - get asyncValidator(): AsyncValidatorFn { - return composeAsyncValidators(this._rawAsyncValidators)!; - } - private _checkParentType(): void { if (typeof ngDevMode === 'undefined' || ngDevMode) { if (!(this._parent instanceof FormGroupName) && diff --git a/packages/forms/src/directives/reactive_directives/form_group_directive.ts b/packages/forms/src/directives/reactive_directives/form_group_directive.ts index babb9b091b..f0651cfe83 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_directive.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_directive.ts @@ -13,7 +13,7 @@ import {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from '../../validators'; import {ControlContainer} from '../control_container'; import {Form} from '../form_interface'; import {ReactiveErrors} from '../reactive_errors'; -import {cleanUpControl, composeAsyncValidators, composeValidators, removeDir, setUpControl, setUpFormContainer, syncPendingControls} from '../shared'; +import {cleanUpControl, removeDir, setUpControl, setUpFormContainer, syncPendingControls} from '../shared'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../validators'; import {FormControlName} from './form_control_name'; @@ -82,10 +82,12 @@ export class FormGroupDirective extends ControlContainer implements Form, OnChan @Output() ngSubmit = new EventEmitter(); constructor( - @Optional() @Self() @Inject(NG_VALIDATORS) private _validators: (Validator|ValidatorFn)[], - @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: + @Optional() @Self() @Inject(NG_VALIDATORS) private validators: (Validator|ValidatorFn)[], + @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private asyncValidators: (AsyncValidator|AsyncValidatorFn)[]) { super(); + this._setValidators(validators); + this._setAsyncValidators(asyncValidators); } /** @nodoc */ @@ -280,11 +282,9 @@ export class FormGroupDirective extends ControlContainer implements Form, OnChan } private _updateValidators() { - const sync = composeValidators(this._validators); - this.form.validator = Validators.compose([this.form.validator!, sync!]); - - const async = composeAsyncValidators(this._asyncValidators); - this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator!, async!]); + this.form.validator = Validators.compose([this.form.validator, this.validator]); + this.form.asyncValidator = + Validators.composeAsync([this.form.asyncValidator, this.asyncValidator]); } private _checkFormPresent() { diff --git a/packages/forms/src/directives/reactive_directives/form_group_name.ts b/packages/forms/src/directives/reactive_directives/form_group_name.ts index 3cc056c3b9..b941e65e72 100644 --- a/packages/forms/src/directives/reactive_directives/form_group_name.ts +++ b/packages/forms/src/directives/reactive_directives/form_group_name.ts @@ -13,7 +13,7 @@ import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators'; import {AbstractFormGroupDirective} from '../abstract_form_group_directive'; import {ControlContainer} from '../control_container'; import {ReactiveErrors} from '../reactive_errors'; -import {composeAsyncValidators, composeValidators, controlPath} from '../shared'; +import {controlPath} from '../shared'; import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../validators'; import {FormGroupDirective} from './form_group_directive'; @@ -91,8 +91,8 @@ export class FormGroupName extends AbstractFormGroupDirective implements OnInit, (AsyncValidator|AsyncValidatorFn)[]) { super(); this._parent = parent; - this._validators = validators; - this._asyncValidators = asyncValidators; + this._setValidators(validators); + this._setAsyncValidators(asyncValidators); } /** @internal */ @@ -137,12 +137,6 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy /** @internal */ _parent: ControlContainer; - /** @internal */ - _validators: (Validator|ValidatorFn)[]; - - /** @internal */ - _asyncValidators: (AsyncValidator|AsyncValidatorFn)[]; - /** * @description * Tracks the name of the `FormArray` bound to the directive. The name corresponds @@ -162,8 +156,8 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy (AsyncValidator|AsyncValidatorFn)[]) { super(); this._parent = parent; - this._validators = validators; - this._asyncValidators = asyncValidators; + this._setValidators(validators); + this._setAsyncValidators(asyncValidators); } /** @@ -211,23 +205,6 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy return controlPath(this.name == null ? this.name : this.name.toString(), this._parent); } - /** - * @description - * Synchronous validator function composed of all the synchronous validators registered with this - * directive. - */ - get validator(): ValidatorFn|null { - return composeValidators(this._validators); - } - - /** - * @description - * Async validator function composed of all the async validators registered with this directive. - */ - get asyncValidator(): AsyncValidatorFn|null { - return composeAsyncValidators(this._asyncValidators); - } - private _checkParentType(): void { if (_hasInvalidParent(this._parent) && (typeof ngDevMode === 'undefined' || ngDevMode)) { ReactiveErrors.arrayParentException(); diff --git a/packages/forms/src/directives/shared.ts b/packages/forms/src/directives/shared.ts index a3dcce5616..3cb78e8c1b 100644 --- a/packages/forms/src/directives/shared.ts +++ b/packages/forms/src/directives/shared.ts @@ -9,7 +9,7 @@ import {isDevMode} from '@angular/core'; import {FormArray, FormControl, FormGroup} from '../model'; -import {normalizeValidators, Validators} from '../validators'; +import {Validators} from '../validators'; import {AbstractControlDirective} from './abstract_control_directive'; import {AbstractFormGroupDirective} from './abstract_form_group_directive'; @@ -150,18 +150,6 @@ function _throwError(dir: AbstractControlDirective, message: string): void { throw new Error(`${message} ${messageEnd}`); } -export function composeValidators(validators: Array): ValidatorFn|null { - return validators != null ? Validators.compose(normalizeValidators(validators)) : - null; -} - -export function composeAsyncValidators(validators: Array): - AsyncValidatorFn|null { - return validators != null ? - Validators.composeAsync(normalizeValidators(validators)) : - null; -} - export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean { if (!changes.hasOwnProperty('model')) return false; const change = changes['model']; diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts index e3356e5726..40d45d39a6 100644 --- a/packages/forms/src/model.ts +++ b/packages/forms/src/model.ts @@ -8,9 +8,9 @@ import {EventEmitter} from '@angular/core'; import {Observable} from 'rxjs'; -import {composeAsyncValidators, composeValidators} from './directives/shared'; + import {AsyncValidatorFn, ValidationErrors, ValidatorFn} from './directives/validators'; -import {toObservable} from './validators'; +import {composeAsyncValidators, composeValidators, toObservable} from './validators'; /** * Reports that a FormControl is valid, meaning that no errors exist in the input value. diff --git a/packages/forms/src/validators.ts b/packages/forms/src/validators.ts index 38f247dda2..5ebf86ce7a 100644 --- a/packages/forms/src/validators.ts +++ b/packages/forms/src/validators.ts @@ -531,4 +531,24 @@ export function normalizeValidators(validators: (V|Validator|AsyncValidator)[ validator : ((c: AbstractControl) => validator.validate(c)) as unknown as V; }); +} + +/** + * Merges synchronous validators into a single validator function (combined using + * `Validators.compose`). + */ +export function composeValidators(validators: Array): ValidatorFn|null { + return validators != null ? Validators.compose(normalizeValidators(validators)) : + null; +} + +/** + * Merges asynchronous validators into a single validator function (combined using + * `Validators.composeAsync`). + */ +export function composeAsyncValidators(validators: Array): + AsyncValidatorFn|null { + return validators != null ? + Validators.composeAsync(normalizeValidators(validators)) : + null; } \ No newline at end of file diff --git a/packages/forms/test/directives_spec.ts b/packages/forms/test/directives_spec.ts index 419ca1e413..2b99f910e1 100644 --- a/packages/forms/test/directives_spec.ts +++ b/packages/forms/test/directives_spec.ts @@ -10,7 +10,8 @@ import {SimpleChange} from '@angular/core'; import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal'; import {AbstractControl, CheckboxControlValueAccessor, ControlValueAccessor, DefaultValueAccessor, FormArray, FormArrayName, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormGroupName, NgControl, NgForm, NgModel, NgModelGroup, SelectControlValueAccessor, SelectMultipleControlValueAccessor, ValidationErrors, Validator, Validators} from '@angular/forms'; -import {composeValidators, selectValueAccessor} from '@angular/forms/src/directives/shared'; +import {selectValueAccessor} from '@angular/forms/src/directives/shared'; +import {composeValidators} from '@angular/forms/src/validators'; import {SpyNgControl, SpyValueAccessor} from './spies'; import {asyncValidator} from './util';