refactor(forms): get rid of duplicate functions (#38371)

This commit performs minor refactoring in Forms package to get rid of duplicate functions.
It looks like the functions were duplicated due to a slightly different type signatures, but
their logic is completely identical. The logic in retained functions remains the same and now
these function also accept a generic type to achieve the same level of type safety.

PR Close #38371
This commit is contained in:
Andrew Kushnir 2020-08-05 19:14:08 -07:00
parent 354e66efad
commit 856db56cca
6 changed files with 58 additions and 66 deletions

View File

@ -1855,11 +1855,6 @@
"packages/forms/src/directives/ng_model.ts", "packages/forms/src/directives/ng_model.ts",
"packages/forms/src/directives/ng_model_group.ts" "packages/forms/src/directives/ng_model_group.ts"
], ],
[
"packages/forms/src/directives/normalize_validator.ts",
"packages/forms/src/model.ts",
"packages/forms/src/directives/shared.ts"
],
[ [
"packages/forms/src/directives/reactive_directives/form_control_directive.ts", "packages/forms/src/directives/reactive_directives/form_control_directive.ts",
"packages/forms/src/directives/shared.ts", "packages/forms/src/directives/shared.ts",

View File

@ -701,9 +701,6 @@
{ {
"name": "_keyMap" "name": "_keyMap"
}, },
{
"name": "_mergeErrors"
},
{ {
"name": "_noControlError" "name": "_noControlError"
}, },
@ -914,6 +911,9 @@
{ {
"name": "executeTemplate" "name": "executeTemplate"
}, },
{
"name": "executeValidators"
},
{ {
"name": "executeViewQueryFn" "name": "executeViewQueryFn"
}, },
@ -1346,6 +1346,9 @@
{ {
"name": "mergeAll" "name": "mergeAll"
}, },
{
"name": "mergeErrors"
},
{ {
"name": "mergeHostAttribute" "name": "mergeHostAttribute"
}, },
@ -1401,10 +1404,7 @@
"name": "noop" "name": "noop"
}, },
{ {
"name": "normalizeAsyncValidator" "name": "normalizeValidators"
},
{
"name": "normalizeValidator"
}, },
{ {
"name": "observable" "name": "observable"

View File

@ -1,27 +0,0 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AbstractControl} from '../model';
import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators';
export function normalizeValidator(validator: ValidatorFn|Validator): ValidatorFn {
if (!!(<Validator>validator).validate) {
return (c: AbstractControl) => (<Validator>validator).validate(c);
} else {
return <ValidatorFn>validator;
}
}
export function normalizeAsyncValidator(validator: AsyncValidatorFn|
AsyncValidator): AsyncValidatorFn {
if (!!(<AsyncValidator>validator).validate) {
return (c: AbstractControl) => (<AsyncValidator>validator).validate(c);
} else {
return <AsyncValidatorFn>validator;
}
}

View File

@ -9,7 +9,8 @@
import {isDevMode} from '@angular/core'; import {isDevMode} from '@angular/core';
import {FormArray, FormControl, FormGroup} from '../model'; import {FormArray, FormControl, FormGroup} from '../model';
import {Validators} from '../validators'; import {normalizeValidators, Validators} from '../validators';
import {AbstractControlDirective} from './abstract_control_directive'; import {AbstractControlDirective} from './abstract_control_directive';
import {AbstractFormGroupDirective} from './abstract_form_group_directive'; import {AbstractFormGroupDirective} from './abstract_form_group_directive';
import {CheckboxControlValueAccessor} from './checkbox_value_accessor'; import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
@ -17,7 +18,6 @@ import {ControlContainer} from './control_container';
import {ControlValueAccessor} from './control_value_accessor'; import {ControlValueAccessor} from './control_value_accessor';
import {DefaultValueAccessor} from './default_value_accessor'; import {DefaultValueAccessor} from './default_value_accessor';
import {NgControl} from './ng_control'; import {NgControl} from './ng_control';
import {normalizeAsyncValidator, normalizeValidator} from './normalize_validator';
import {NumberValueAccessor} from './number_value_accessor'; import {NumberValueAccessor} from './number_value_accessor';
import {RadioControlValueAccessor} from './radio_control_value_accessor'; import {RadioControlValueAccessor} from './radio_control_value_accessor';
import {RangeValueAccessor} from './range_value_accessor'; import {RangeValueAccessor} from './range_value_accessor';
@ -142,12 +142,14 @@ function _throwError(dir: AbstractControlDirective, message: string): void {
} }
export function composeValidators(validators: Array<Validator|ValidatorFn>): ValidatorFn|null { export function composeValidators(validators: Array<Validator|ValidatorFn>): ValidatorFn|null {
return validators != null ? Validators.compose(validators.map(normalizeValidator)) : null; return validators != null ? Validators.compose(normalizeValidators<ValidatorFn>(validators)) :
null;
} }
export function composeAsyncValidators(validators: Array<AsyncValidator|AsyncValidatorFn>): export function composeAsyncValidators(validators: Array<AsyncValidator|AsyncValidatorFn>):
AsyncValidatorFn|null { AsyncValidatorFn|null {
return validators != null ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) : return validators != null ?
Validators.composeAsync(normalizeValidators<AsyncValidatorFn>(validators)) :
null; null;
} }

View File

@ -10,7 +10,7 @@ import {InjectionToken, ɵisObservable as isObservable, ɵisPromise as isPromise
import {forkJoin, from, Observable} from 'rxjs'; import {forkJoin, from, Observable} from 'rxjs';
import {map} from 'rxjs/operators'; import {map} from 'rxjs/operators';
import {AsyncValidatorFn, ValidationErrors, Validator, ValidatorFn} from './directives/validators'; import {AsyncValidator, AsyncValidatorFn, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
import {AbstractControl} from './model'; import {AbstractControl} from './model';
function isEmptyInputValue(value: any): boolean { function isEmptyInputValue(value: any): boolean {
@ -435,7 +435,7 @@ export class Validators {
if (presentValidators.length == 0) return null; if (presentValidators.length == 0) return null;
return function(control: AbstractControl) { return function(control: AbstractControl) {
return _mergeErrors(_executeValidators(control, presentValidators)); return mergeErrors(executeValidators<ValidatorFn>(control, presentValidators));
}; };
} }
@ -456,8 +456,9 @@ export class Validators {
if (presentValidators.length == 0) return null; if (presentValidators.length == 0) return null;
return function(control: AbstractControl) { return function(control: AbstractControl) {
const observables = _executeAsyncValidators(control, presentValidators).map(toObservable); const observables =
return forkJoin(observables).pipe(map(_mergeErrors)); executeValidators<AsyncValidatorFn>(control, presentValidators).map(toObservable);
return forkJoin(observables).pipe(map(mergeErrors));
}; };
} }
} }
@ -474,15 +475,7 @@ export function toObservable(r: any): Observable<any> {
return obs; return obs;
} }
function _executeValidators(control: AbstractControl, validators: ValidatorFn[]): any[] { function mergeErrors(arrayOfErrors: (ValidationErrors|null)[]): ValidationErrors|null {
return validators.map(v => v(control));
}
function _executeAsyncValidators(control: AbstractControl, validators: AsyncValidatorFn[]): any[] {
return validators.map(v => v(control));
}
function _mergeErrors(arrayOfErrors: ValidationErrors[]): ValidationErrors|null {
let res: {[key: string]: any} = {}; let res: {[key: string]: any} = {};
// Not using Array.reduce here due to a Chrome 80 bug // Not using Array.reduce here due to a Chrome 80 bug
@ -493,3 +486,30 @@ function _mergeErrors(arrayOfErrors: ValidationErrors[]): ValidationErrors|null
return Object.keys(res).length === 0 ? null : res; return Object.keys(res).length === 0 ? null : res;
} }
type GenericValidatorFn = (control: AbstractControl) => any;
function executeValidators<V extends GenericValidatorFn>(
control: AbstractControl, validators: V[]): ReturnType<V>[] {
return validators.map(validator => validator(control));
}
function isValidatorFn<V>(validator: V|Validator|AsyncValidator): validator is V {
return !(validator as Validator).validate;
}
/**
* Given the list of validators that may contain both functions as well as classes, return the list
* of validator functions (convert validator classes into validator functions). This is needed to
* have consistent structure in validators list before composing them.
*
* @param validators The set of validators that may contain validators both in plain function form
* as well as represented as a validator class.
*/
export function normalizeValidators<V>(validators: (V|Validator|AsyncValidator)[]): V[] {
return validators.map(validator => {
return isValidatorFn<V>(validator) ?
validator :
((c: AbstractControl) => validator.validate(c)) as unknown as V;
});
}

View File

@ -8,12 +8,12 @@
import {fakeAsync, tick} from '@angular/core/testing'; import {fakeAsync, tick} from '@angular/core/testing';
import {describe, expect, it} from '@angular/core/testing/src/testing_internal'; import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
import {AbstractControl, AsyncValidatorFn, FormArray, FormControl, Validators} from '@angular/forms'; import {AbstractControl, AsyncValidator, AsyncValidatorFn, FormArray, FormControl, ValidationErrors, ValidatorFn, Validators} from '@angular/forms';
import {normalizeAsyncValidator} from '@angular/forms/src/directives/normalize_validator';
import {AsyncValidator, ValidationErrors, ValidatorFn} from '@angular/forms/src/directives/validators';
import {Observable, of, timer} from 'rxjs'; import {Observable, of, timer} from 'rxjs';
import {first, map} from 'rxjs/operators'; import {first, map} from 'rxjs/operators';
import {normalizeValidators} from '../src/validators';
(function() { (function() {
function validator(key: string, error: any): ValidatorFn { function validator(key: string, error: any): ValidatorFn {
return (c: AbstractControl) => { return (c: AbstractControl) => {
@ -413,11 +413,12 @@ describe('Validators', () => {
})); }));
it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => { it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => {
const v = Validators.composeAsync( const normalizedValidators = normalizeValidators<AsyncValidatorFn>(
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))])!; [new AsyncValidatorDirective('expected', {'one': true})]);
const validatorFn = Validators.composeAsync(normalizedValidators)!;
let errorMap: {[key: string]: any}|null = undefined!; let errorMap: {[key: string]: any}|null = undefined!;
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>) (validatorFn(new FormControl('invalid')) as Observable<ValidationErrors|null>)
.pipe(first()) .pipe(first())
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors); .subscribe((errors: {[key: string]: any}|null) => errorMap = errors);
tick(); tick();
@ -475,11 +476,12 @@ describe('Validators', () => {
}); });
it('should normalize and evaluate async validator-directives correctly', () => { it('should normalize and evaluate async validator-directives correctly', () => {
const v = Validators.composeAsync( const normalizedValidators = normalizeValidators<AsyncValidatorFn>(
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))])!; [new AsyncValidatorDirective('expected', {'one': true})]);
const validatorFn = Validators.composeAsync(normalizedValidators)!;
let errorMap: {[key: string]: any}|null = undefined!; let errorMap: {[key: string]: any}|null = undefined!;
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>) (validatorFn(new FormControl('invalid')) as Observable<ValidationErrors|null>)
.pipe(first()) .pipe(first())
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors)!; .subscribe((errors: {[key: string]: any}|null) => errorMap = errors)!;