feat(forms): allow to compile forms in strictNullChecks mode (#14679)

Closes #14667

PR Close #14679
This commit is contained in:
Dzmitry Shylovich 2017-02-23 20:53:29 +03:00 committed by Miško Hevery
parent 2d78c8cc05
commit 5486e5417b
8 changed files with 81 additions and 113 deletions

View File

@ -8,6 +8,7 @@
import {Observable} from 'rxjs/Observable';
import {AbstractControl} from '../model';
import {ValidationErrors} from './validators';
/**
* Base class for control directives.
@ -27,7 +28,7 @@ export abstract class AbstractControlDirective {
get pending(): boolean { return this.control ? this.control.pending : null; }
get errors(): {[key: string]: any} { return this.control ? this.control.errors : null; }
get errors(): ValidationErrors|null { return this.control ? this.control.errors : null; }
get pristine(): boolean { return this.control ? this.control.pristine : null; }

View File

@ -11,6 +11,11 @@ import {Observable} from 'rxjs/Observable';
import {AbstractControl} from '../model';
import {NG_VALIDATORS, Validators} from '../validators';
/** @experimental */
export type ValidationErrors = {
[key: string]: any
};
/**
* An interface that can be implemented by classes that can act as validators.
*
@ -31,13 +36,13 @@ import {NG_VALIDATORS, Validators} from '../validators';
* @stable
*/
export interface Validator {
validate(c: AbstractControl): {[key: string]: any};
validate(c: AbstractControl): ValidationErrors|null;
registerOnValidatorChange?(fn: () => void): void;
}
/** @experimental */
export interface AsyncValidator extends Validator {
validate(c: AbstractControl): Promise<{[key: string]: any}>|Observable<{[key: string]: any}>;
validate(c: AbstractControl): Promise<ValidationErrors|null>|Observable<ValidationErrors|null>;
}
export const REQUIRED_VALIDATOR: Provider = {
@ -75,14 +80,14 @@ export class RequiredValidator implements Validator {
private _onChange: () => void;
@Input()
get required(): boolean /*| string*/ { return this._required; }
get required(): boolean|string { return this._required; }
set required(value: boolean) {
set required(value: boolean|string) {
this._required = value != null && value !== false && `${value}` !== 'false';
if (this._onChange) this._onChange();
}
validate(c: AbstractControl): {[key: string]: any} {
validate(c: AbstractControl): ValidationErrors|null {
return this.required ? Validators.required(c) : null;
}
@ -108,7 +113,7 @@ export class RequiredValidator implements Validator {
host: {'[attr.required]': 'required ? "" : null'}
})
export class CheckboxRequiredValidator extends RequiredValidator {
validate(c: AbstractControl): {[key: string]: any} {
validate(c: AbstractControl): ValidationErrors|null {
return this.required ? Validators.requiredTrue(c) : null;
}
}
@ -150,7 +155,7 @@ export class EmailValidator implements Validator {
if (this._onChange) this._onChange();
}
validate(c: AbstractControl): {[key: string]: any} {
validate(c: AbstractControl): ValidationErrors|null {
return this._enabled ? Validators.email(c) : null;
}
@ -160,13 +165,13 @@ export class EmailValidator implements Validator {
/**
* @stable
*/
export interface ValidatorFn { (c: AbstractControl): {[key: string]: any}; }
export interface ValidatorFn { (c: AbstractControl): ValidationErrors|null; }
/**
* @stable
*/
export interface AsyncValidatorFn {
(c: AbstractControl): Promise<{[key: string]: any}>|Observable<{[key: string]: any}>;
(c: AbstractControl): Promise<ValidationErrors|null>|Observable<ValidationErrors|null>;
}
/**
@ -207,7 +212,7 @@ export class MinLengthValidator implements Validator,
}
}
validate(c: AbstractControl): {[key: string]: any} {
validate(c: AbstractControl): ValidationErrors|null {
return this.minlength == null ? null : this._validator(c);
}
@ -257,7 +262,7 @@ export class MaxLengthValidator implements Validator,
}
}
validate(c: AbstractControl): {[key: string]: any} {
validate(c: AbstractControl): ValidationErrors|null {
return this.maxlength != null ? this._validator(c) : null;
}
@ -299,7 +304,7 @@ export class PatternValidator implements Validator,
private _validator: ValidatorFn;
private _onChange: () => void;
@Input() pattern: string /*|RegExp*/;
@Input() pattern: string|RegExp;
ngOnChanges(changes: SimpleChanges): void {
if ('pattern' in changes) {
@ -308,7 +313,7 @@ export class PatternValidator implements Validator,
}
}
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
validate(c: AbstractControl): ValidationErrors|null { return this._validator(c); }
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }

View File

@ -38,9 +38,10 @@ export {FormArrayName} from './directives/reactive_directives/form_group_name';
export {FormGroupName} from './directives/reactive_directives/form_group_name';
export {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
export {SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
export {AsyncValidator, AsyncValidatorFn, CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, Validator, ValidatorFn} from './directives/validators';
export {AsyncValidator, AsyncValidatorFn, CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
export {FormBuilder} from './form_builder';
export {AbstractControl, FormArray, FormControl, FormGroup} from './model';
export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './validators';
export {VERSION} from './version';
export * from './form_providers';

View File

@ -8,13 +8,10 @@
import {EventEmitter} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {composeAsyncValidators, composeValidators} from './directives/shared';
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
import {AsyncValidatorFn, ValidationErrors, ValidatorFn} from './directives/validators';
import {toObservable} from './validators';
/**
* Indicates that a FormControl is valid, i.e. that no errors exist in the input value.
*/
@ -57,6 +54,7 @@ function _find(control: AbstractControl, path: Array<string|number>| string, del
return null;
}, control);
}
function coerceToValidator(validator: ValidatorFn | ValidatorFn[]): ValidatorFn {
return Array.isArray(validator) ? composeValidators(validator) : validator;
}
@ -86,7 +84,7 @@ export abstract class AbstractControl {
private _valueChanges: EventEmitter<any>;
private _statusChanges: EventEmitter<any>;
private _status: string;
private _errors: {[key: string]: any};
private _errors: ValidationErrors|null;
private _pristine: boolean = true;
private _touched: boolean = false;
private _parent: FormGroup|FormArray;
@ -163,7 +161,7 @@ export abstract class AbstractControl {
* Returns any errors generated by failing validation. If there
* are no errors, it will return null.
*/
get errors(): {[key: string]: any} { return this._errors; }
get errors(): ValidationErrors|null { return this._errors; }
/**
* A control is `pristine` if the user has not yet changed
@ -407,7 +405,7 @@ export abstract class AbstractControl {
private _setInitialStatus() { this._status = this._allControlsDisabled() ? DISABLED : VALID; }
private _runValidator(): {[key: string]: any} {
private _runValidator(): ValidationErrors|null {
return this.validator ? this.validator(this) : null;
}
@ -416,7 +414,7 @@ export abstract class AbstractControl {
this._status = PENDING;
const obs = toObservable(this.asyncValidator(this));
this._asyncValidationSubscription =
obs.subscribe((res: {[key: string]: any}) => this.setErrors(res, {emitEvent}));
obs.subscribe((errors: ValidationErrors | null) => this.setErrors(errors, {emitEvent}));
}
}
@ -449,7 +447,7 @@ export abstract class AbstractControl {
* expect(login.valid).toEqual(true);
* ```
*/
setErrors(errors: {[key: string]: any}, {emitEvent}: {emitEvent?: boolean} = {}): void {
setErrors(errors: ValidationErrors|null, {emitEvent}: {emitEvent?: boolean} = {}): void {
this._errors = errors;
this._updateControlsErrors(emitEvent !== false);
}

View File

@ -11,9 +11,8 @@ import {Observable} from 'rxjs/Observable';
import {forkJoin} from 'rxjs/observable/forkJoin';
import {fromPromise} from 'rxjs/observable/fromPromise';
import {map} from 'rxjs/operator/map';
import {AsyncValidatorFn, Validator, ValidatorFn} from './directives/validators';
import {AbstractControl, FormControl, FormGroup} from './model';
import {AsyncValidatorFn, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
import {AbstractControl, FormControl} from './model';
function isEmptyInputValue(value: any): boolean {
// we don't check for string here so it also works with arrays
@ -66,21 +65,21 @@ export class Validators {
/**
* Validator that requires controls to have a non-empty value.
*/
static required(control: AbstractControl): {[key: string]: boolean} {
static required(control: AbstractControl): ValidationErrors|null {
return isEmptyInputValue(control.value) ? {'required': true} : null;
}
/**
* Validator that requires control value to be true.
*/
static requiredTrue(control: AbstractControl): {[key: string]: boolean} {
static requiredTrue(control: AbstractControl): ValidationErrors|null {
return control.value === true ? null : {'required': true};
}
/**
* Validator that performs email validation.
*/
static email(control: AbstractControl): {[key: string]: boolean} {
static email(control: AbstractControl): ValidationErrors|null {
return EMAIL_REGEXP.test(control.value) ? null : {'email': true};
}
@ -88,7 +87,7 @@ export class Validators {
* Validator that requires controls to have a value of a minimum length.
*/
static minLength(minLength: number): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
return (control: AbstractControl): ValidationErrors | null => {
if (isEmptyInputValue(control.value)) {
return null; // don't validate empty values to allow optional controls
}
@ -103,7 +102,7 @@ export class Validators {
* Validator that requires controls to have a value of a maximum length.
*/
static maxLength(maxLength: number): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
return (control: AbstractControl): ValidationErrors | null => {
const length: number = control.value ? control.value.length : 0;
return length > maxLength ?
{'maxlength': {'requiredLength': maxLength, 'actualLength': length}} :
@ -125,7 +124,7 @@ export class Validators {
regexStr = pattern.toString();
regex = pattern;
}
return (control: AbstractControl): {[key: string]: any} => {
return (control: AbstractControl): ValidationErrors | null => {
if (isEmptyInputValue(control.value)) {
return null; // don't validate empty values to allow optional controls
}
@ -138,7 +137,7 @@ export class Validators {
/**
* No-op validator.
*/
static nullValidator(c: AbstractControl): {[key: string]: boolean} { return null; }
static nullValidator(c: AbstractControl): ValidationErrors|null { return null; }
/**
* Compose multiple validators into a single function that returns the union
@ -186,9 +185,9 @@ function _executeAsyncValidators(control: AbstractControl, validators: AsyncVali
return validators.map(v => v(control));
}
function _mergeErrors(arrayOfErrors: any[]): {[key: string]: any} {
function _mergeErrors(arrayOfErrors: ValidationErrors[]): ValidationErrors|null {
const res: {[key: string]: any} =
arrayOfErrors.reduce((res: {[key: string]: any}, errors: {[key: string]: any}) => {
arrayOfErrors.reduce((res: ValidationErrors | null, errors: ValidationErrors | null) => {
return errors != null ? merge(res, errors) : res;
}, {});
return Object.keys(res).length === 0 ? null : res;

View File

@ -6,29 +6,28 @@
* found in the LICENSE file at https://angular.io/license
*/
import {SimpleChange} from '@angular/core/src/change_detection';
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 {CheckboxControlValueAccessor, ControlValueAccessor, DefaultValueAccessor, FormArray, FormArrayName, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormGroupName, NgControl, NgForm, NgModel, NgModelGroup, SelectControlValueAccessor, SelectMultipleControlValueAccessor, Validator, Validators} from '@angular/forms';
import {composeValidators, selectValueAccessor} from '@angular/forms/src/directives/shared';
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 '../src/directives/shared';
import {SpyNgControl, SpyValueAccessor} from './spies';
class DummyControlValueAccessor implements ControlValueAccessor {
writtenValue: any;
registerOnChange(fn: any /** TODO #9100 */) {}
registerOnTouched(fn: any /** TODO #9100 */) {}
registerOnChange(fn: any) {}
registerOnTouched(fn: any) {}
writeValue(obj: any): void { this.writtenValue = obj; }
}
class CustomValidatorDirective implements Validator {
validate(c: FormControl): {[key: string]: any} { return {'custom': true}; }
validate(c: FormControl): ValidationErrors { return {'custom': true}; }
}
function asyncValidator(expected: any /** TODO #9100 */, timeout = 0) {
return (c: any /** TODO #9100 */) => {
function asyncValidator(expected: any, timeout = 0) {
return (c: AbstractControl): any => {
let resolve: (result: any) => void;
const promise = new Promise(res => { resolve = res; });
const res = c.value != expected ? {'async': true} : null;
@ -228,7 +227,7 @@ export function main() {
});
describe('addFormGroup', () => {
const matchingPasswordsValidator = (g: any /** TODO #9100 */) => {
const matchingPasswordsValidator = (g: FormGroup) => {
if (g.controls['password'].value != g.controls['passwordConfirm'].value) {
return {'differentPasswords': true};
} else {
@ -283,7 +282,7 @@ export function main() {
});
it('should set up a sync validator', () => {
const formValidator = (c: any /** TODO #9100 */) => ({'custom': true});
const formValidator = (c: AbstractControl) => ({'custom': true});
const f = new FormGroupDirective([formValidator], []);
f.form = formModel;
f.ngOnChanges({'form': new SimpleChange(null, null, false)});
@ -475,7 +474,7 @@ export function main() {
describe('FormControlDirective', () => {
let controlDir: any /** TODO #9100 */;
let control: any /** TODO #9100 */;
const checkProperties = function(control: any /** TODO #9100 */) {
const checkProperties = function(control: AbstractControl) {
expect(controlDir.control).toBe(control);
expect(controlDir.value).toBe(control.value);
expect(controlDir.valid).toBe(control.valid);

View File

@ -8,20 +8,19 @@
import {fakeAsync, tick} from '@angular/core/testing';
import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
import {AbstractControl, AsyncValidatorFn, FormArray, FormControl, FormGroup, Validators} from '@angular/forms';
import {AbstractControl, AsyncValidatorFn, FormArray, FormControl, Validators} from '@angular/forms';
import {Observable} from 'rxjs/Observable';
import {of } from 'rxjs/observable/of';
import {timer} from 'rxjs/observable/timer';
import {first} from 'rxjs/operator/first';
import {map} from 'rxjs/operator/map';
import {normalizeAsyncValidator} from '../src/directives/normalize_validator';
import {AsyncValidator} from '../src/directives/validators';
import {AsyncValidator, ValidationErrors, ValidatorFn} from '../src/directives/validators';
export function main() {
function validator(key: string, error: any) {
return function(c: AbstractControl) {
const r: {[k: string]: string} = {};
function validator(key: string, error: any): ValidatorFn {
return (c: AbstractControl) => {
const r: ValidationErrors = {};
r[key] = error;
return r;
};
@ -30,8 +29,7 @@ export function main() {
class AsyncValidatorDirective implements AsyncValidator {
constructor(private expected: string, private error: any) {}
validate(c: any): Observable < { [key: string]: any; }
> {
validate(c: any): Observable<ValidationErrors> {
return Observable.create((obs: any) => {
const error = this.expected !== c.value ? this.error : null;
obs.next(error);

View File

@ -4,9 +4,7 @@ export declare abstract class AbstractControl {
readonly dirty: boolean;
readonly disabled: boolean;
readonly enabled: boolean;
readonly errors: {
[key: string]: any;
};
readonly errors: ValidationErrors | null;
readonly invalid: boolean;
readonly parent: FormGroup | FormArray;
readonly pending: boolean;
@ -52,9 +50,7 @@ export declare abstract class AbstractControl {
abstract patchValue(value: any, options?: Object): void;
abstract reset(value?: any, options?: Object): void;
setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[]): void;
setErrors(errors: {
[key: string]: any;
}, {emitEvent}?: {
setErrors(errors: ValidationErrors | null, {emitEvent}?: {
emitEvent?: boolean;
}): void;
setParent(parent: FormGroup | FormArray): void;
@ -72,9 +68,7 @@ export declare abstract class AbstractControlDirective {
readonly dirty: boolean;
readonly disabled: boolean;
readonly enabled: boolean;
readonly errors: {
[key: string]: any;
};
readonly errors: ValidationErrors | null;
readonly invalid: boolean;
readonly path: string[];
readonly pending: boolean;
@ -103,20 +97,12 @@ export declare class AbstractFormGroupDirective extends ControlContainer impleme
/** @experimental */
export interface AsyncValidator extends Validator {
validate(c: AbstractControl): Promise<{
[key: string]: any;
}> | Observable<{
[key: string]: any;
}>;
validate(c: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>;
}
/** @stable */
export interface AsyncValidatorFn {
(c: AbstractControl): Promise<{
[key: string]: any;
}> | Observable<{
[key: string]: any;
}>;
(c: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>;
}
/** @stable */
@ -132,9 +118,7 @@ export declare class CheckboxControlValueAccessor implements ControlValueAccesso
/** @experimental */
export declare class CheckboxRequiredValidator extends RequiredValidator {
validate(c: AbstractControl): {
[key: string]: any;
};
validate(c: AbstractControl): ValidationErrors | null;
}
/** @stable */
@ -167,9 +151,7 @@ export declare class DefaultValueAccessor implements ControlValueAccessor {
export declare class EmailValidator implements Validator {
email: boolean | string;
registerOnValidatorChange(fn: () => void): void;
validate(c: AbstractControl): {
[key: string]: any;
};
validate(c: AbstractControl): ValidationErrors | null;
}
/** @stable */
@ -361,9 +343,7 @@ export declare class MaxLengthValidator implements Validator, OnChanges {
maxlength: string;
ngOnChanges(changes: SimpleChanges): void;
registerOnValidatorChange(fn: () => void): void;
validate(c: AbstractControl): {
[key: string]: any;
};
validate(c: AbstractControl): ValidationErrors | null;
}
/** @stable */
@ -371,9 +351,7 @@ export declare class MinLengthValidator implements Validator, OnChanges {
minlength: string;
ngOnChanges(changes: SimpleChanges): void;
registerOnValidatorChange(fn: () => void): void;
validate(c: AbstractControl): {
[key: string]: any;
};
validate(c: AbstractControl): ValidationErrors | null;
}
/** @stable */
@ -472,12 +450,10 @@ export declare class NgSelectOption implements OnDestroy {
/** @stable */
export declare class PatternValidator implements Validator, OnChanges {
pattern: string;
pattern: string | RegExp;
ngOnChanges(changes: SimpleChanges): void;
registerOnValidatorChange(fn: () => void): void;
validate(c: AbstractControl): {
[key: string]: any;
};
validate(c: AbstractControl): ValidationErrors | null;
}
/** @stable */
@ -503,11 +479,9 @@ export declare class ReactiveFormsModule {
/** @stable */
export declare class RequiredValidator implements Validator {
required: boolean;
required: boolean | string;
registerOnValidatorChange(fn: () => void): void;
validate(c: AbstractControl): {
[key: string]: any;
};
validate(c: AbstractControl): ValidationErrors | null;
}
/** @stable */
@ -536,40 +510,33 @@ export declare class SelectMultipleControlValueAccessor implements ControlValueA
writeValue(value: any): void;
}
/** @experimental */
export declare type ValidationErrors = {
[key: string]: any;
};
/** @stable */
export interface Validator {
registerOnValidatorChange?(fn: () => void): void;
validate(c: AbstractControl): {
[key: string]: any;
};
validate(c: AbstractControl): ValidationErrors | null;
}
/** @stable */
export interface ValidatorFn {
(c: AbstractControl): {
[key: string]: any;
};
(c: AbstractControl): ValidationErrors | null;
}
/** @stable */
export declare class Validators {
static compose(validators: ValidatorFn[]): ValidatorFn;
static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn;
static email(control: AbstractControl): {
[key: string]: boolean;
};
static email(control: AbstractControl): ValidationErrors | null;
static maxLength(maxLength: number): ValidatorFn;
static minLength(minLength: number): ValidatorFn;
static nullValidator(c: AbstractControl): {
[key: string]: boolean;
};
static nullValidator(c: AbstractControl): ValidationErrors | null;
static pattern(pattern: string | RegExp): ValidatorFn;
static required(control: AbstractControl): {
[key: string]: boolean;
};
static requiredTrue(control: AbstractControl): {
[key: string]: boolean;
};
static required(control: AbstractControl): ValidationErrors | null;
static requiredTrue(control: AbstractControl): ValidationErrors | null;
}
/** @stable */