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
This commit is contained in:
Andrew Kushnir 2020-07-27 19:31:40 -07:00 committed by Alex Rickabaugh
parent e649f1dda6
commit 0723331b2a
16 changed files with 177 additions and 279 deletions

View File

@ -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",

View File

@ -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<any> | null;
getError(errorCode: string, path?: Array<string | number> | 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<any>;
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<any>;
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<any>;
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<readonly ControlValueAccessor[]>;
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<any>;
get validator(): ValidatorFn | null;
viewModel: any;
constructor(parent: ControlContainer, validators: (Validator | ValidatorFn)[], asyncValidators: (AsyncValidator | AsyncValidatorFn)[], valueAccessors: ControlValueAccessor[]);
ngOnChanges(changes: SimpleChanges): void;

View File

@ -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<Validator|ValidatorFn> = [];
/**
* Set of asynchronous validators as they were provided while calling `setAsyncValidators`
* function.
* @internal
*/
_rawAsyncValidators: Array<AsyncValidator|AsyncValidatorFn> = [];
/**
* Sets synchronous validators for this directive.
* @internal
*/
_setValidators(validators: Array<Validator|ValidatorFn>|undefined): void {
this._rawValidators = validators || [];
this._composedValidatorFn = composeValidators(this._rawValidators);
}
/**
* Sets asynchronous validators for this directive.
* @internal
*/
_setAsyncValidators(validators: Array<AsyncValidator|AsyncValidatorFn>|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.

View File

@ -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 {}
}

View File

@ -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<Validator|ValidatorFn> = [];
/**
* @description
* The uncomposed array of async validators for the control
*
* @internal
*/
_rawAsyncValidators: Array<AsyncValidator|AsyncValidatorFn> = [];
/**
* @description
* The registered synchronous validator function for the control
*
* @throws An exception that this method is not implemented
*/
get validator(): ValidatorFn|null {
return <ValidatorFn>unimplemented();
}
/**
* @description
* The registered async validator function for the control
*
* @throws An exception that this method is not implemented
*/
get asyncValidator(): AsyncValidatorFn|null {
return <AsyncValidatorFn>unimplemented();
}
/**
* @description
* The callback method to update the model from the view when requested

View File

@ -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 = {

View File

@ -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.

View File

@ -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 */

View File

@ -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.

View File

@ -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) &&

View File

@ -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() {

View File

@ -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();

View File

@ -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<Validator|ValidatorFn>): ValidatorFn|null {
return validators != null ? Validators.compose(normalizeValidators<ValidatorFn>(validators)) :
null;
}
export function composeAsyncValidators(validators: Array<AsyncValidator|AsyncValidatorFn>):
AsyncValidatorFn|null {
return validators != null ?
Validators.composeAsync(normalizeValidators<AsyncValidatorFn>(validators)) :
null;
}
export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {
if (!changes.hasOwnProperty('model')) return false;
const change = changes['model'];

View File

@ -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.

View File

@ -531,4 +531,24 @@ export function normalizeValidators<V>(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<Validator|ValidatorFn>): ValidatorFn|null {
return validators != null ? Validators.compose(normalizeValidators<ValidatorFn>(validators)) :
null;
}
/**
* Merges asynchronous validators into a single validator function (combined using
* `Validators.composeAsync`).
*/
export function composeAsyncValidators(validators: Array<AsyncValidator|AsyncValidatorFn>):
AsyncValidatorFn|null {
return validators != null ?
Validators.composeAsync(normalizeValidators<AsyncValidatorFn>(validators)) :
null;
}

View File

@ -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';