feat(forms): add email validator (#13709)
Closes #13706 PR Close #13709
This commit is contained in:
parent
00979838ef
commit
d69717cf79
|
@ -24,7 +24,7 @@ import {FormGroupDirective} from './directives/reactive_directives/form_group_di
|
||||||
import {FormArrayName, FormGroupName} from './directives/reactive_directives/form_group_name';
|
import {FormArrayName, FormGroupName} from './directives/reactive_directives/form_group_name';
|
||||||
import {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
import {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
||||||
import {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
import {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
||||||
import {CheckboxRequiredValidator, MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
|
import {CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
|
||||||
|
|
||||||
export {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
export {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
|
||||||
export {ControlValueAccessor} from './directives/control_value_accessor';
|
export {ControlValueAccessor} from './directives/control_value_accessor';
|
||||||
|
@ -62,6 +62,7 @@ export const SHARED_FORM_DIRECTIVES: Type<any>[] = [
|
||||||
MaxLengthValidator,
|
MaxLengthValidator,
|
||||||
PatternValidator,
|
PatternValidator,
|
||||||
CheckboxRequiredValidator,
|
CheckboxRequiredValidator,
|
||||||
|
EmailValidator,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const TEMPLATE_DRIVEN_DIRECTIVES: Type<any>[] = [NgModel, NgModelGroup, NgForm];
|
export const TEMPLATE_DRIVEN_DIRECTIVES: Type<any>[] = [NgModel, NgModelGroup, NgForm];
|
||||||
|
|
|
@ -106,6 +106,50 @@ export class CheckboxRequiredValidator extends RequiredValidator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider which adds {@link EmailValidator} to {@link NG_VALIDATORS}.
|
||||||
|
*/
|
||||||
|
export const EMAIL_VALIDATOR: any = {
|
||||||
|
provide: NG_VALIDATORS,
|
||||||
|
useExisting: forwardRef(() => EmailValidator),
|
||||||
|
multi: true
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Directive that adds the `email` validator to controls marked with the
|
||||||
|
* `email` attribute, via the {@link NG_VALIDATORS} binding.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* <input type="email" name="email" ngModel email>
|
||||||
|
* <input type="email" name="email" ngModel email="true">
|
||||||
|
* <input type="email" name="email" ngModel [email]="true">
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
@Directive({
|
||||||
|
selector: '[email][formControlName],[email][formControl],[email][ngModel]',
|
||||||
|
providers: [EMAIL_VALIDATOR]
|
||||||
|
})
|
||||||
|
export class EmailValidator implements Validator {
|
||||||
|
private _enabled: boolean;
|
||||||
|
private _onChange: () => void;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set email(value: boolean|string) {
|
||||||
|
this._enabled = value === '' || value === true || value === 'true';
|
||||||
|
if (this._onChange) this._onChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(c: AbstractControl): {[key: string]: any} {
|
||||||
|
return this._enabled ? Validators.email(c) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -38,7 +38,7 @@ export {FormArrayName} from './directives/reactive_directives/form_group_name';
|
||||||
export {FormGroupName} 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 {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
|
||||||
export {SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
export {SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
|
||||||
export {AsyncValidatorFn, CheckboxRequiredValidator, MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, Validator, ValidatorFn} from './directives/validators';
|
export {AsyncValidatorFn, CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, Validator, ValidatorFn} from './directives/validators';
|
||||||
export {FormBuilder} from './form_builder';
|
export {FormBuilder} from './form_builder';
|
||||||
export {AbstractControl, FormArray, FormControl, FormGroup} from './model';
|
export {AbstractControl, FormArray, FormControl, FormGroup} from './model';
|
||||||
export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './validators';
|
export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './validators';
|
||||||
|
|
|
@ -45,6 +45,9 @@ export const NG_VALIDATORS = new InjectionToken<Array<Validator|Function>>('NgVa
|
||||||
export const NG_ASYNC_VALIDATORS =
|
export const NG_ASYNC_VALIDATORS =
|
||||||
new InjectionToken<Array<Validator|Function>>('NgAsyncValidators');
|
new InjectionToken<Array<Validator|Function>>('NgAsyncValidators');
|
||||||
|
|
||||||
|
const EMAIL_REGEXP =
|
||||||
|
/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a set of validators used by form controls.
|
* Provides a set of validators used by form controls.
|
||||||
*
|
*
|
||||||
|
@ -74,6 +77,13 @@ export class Validators {
|
||||||
return control.value === true ? null : {'required': true};
|
return control.value === true ? null : {'required': true};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validator that performs email validation.
|
||||||
|
*/
|
||||||
|
static email(control: AbstractControl): {[key: string]: boolean} {
|
||||||
|
return EMAIL_REGEXP.test(control.value) ? null : {'email': true};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validator that requires controls to have a value of a minimum length.
|
* Validator that requires controls to have a value of a minimum length.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -859,6 +859,41 @@ export function main() {
|
||||||
expect(control.hasError('required')).toBe(true);
|
expect(control.hasError('required')).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should validate email', fakeAsync(() => {
|
||||||
|
const fixture = initTest(NgModelEmailValidator);
|
||||||
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
|
const control =
|
||||||
|
fixture.debugElement.children[0].injector.get(NgForm).control.get('email');
|
||||||
|
|
||||||
|
const input = fixture.debugElement.query(By.css('input'));
|
||||||
|
expect(control.hasError('email')).toBe(false);
|
||||||
|
|
||||||
|
fixture.componentInstance.validatorEnabled = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(input.nativeElement.value).toEqual('');
|
||||||
|
expect(control.hasError('email')).toBe(true);
|
||||||
|
|
||||||
|
input.nativeElement.value = 'test@gmail.com';
|
||||||
|
dispatchEvent(input.nativeElement, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(input.nativeElement.value).toEqual('test@gmail.com');
|
||||||
|
expect(control.hasError('email')).toBe(false);
|
||||||
|
|
||||||
|
input.nativeElement.value = 'text';
|
||||||
|
dispatchEvent(input.nativeElement, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(input.nativeElement.value).toEqual('text');
|
||||||
|
expect(control.hasError('email')).toBe(true);
|
||||||
|
}));
|
||||||
|
|
||||||
it('should support dir validators using bindings', fakeAsync(() => {
|
it('should support dir validators using bindings', fakeAsync(() => {
|
||||||
const fixture = initTest(NgModelValidationBindings);
|
const fixture = initTest(NgModelValidationBindings);
|
||||||
fixture.componentInstance.required = true;
|
fixture.componentInstance.required = true;
|
||||||
|
@ -1335,6 +1370,14 @@ class NgModelCheckboxRequiredValidator {
|
||||||
required: boolean = false;
|
required: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ng-model-email',
|
||||||
|
template: `<form><input type="email" ngModel [email]="validatorEnabled" name="email"></form>`
|
||||||
|
})
|
||||||
|
class NgModelEmailValidator {
|
||||||
|
validatorEnabled: boolean = false;
|
||||||
|
}
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[ng-async-validator]',
|
selector: '[ng-async-validator]',
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
@ -64,6 +64,14 @@ export function main() {
|
||||||
() => expect(Validators.requiredTrue(new FormControl(true))).toBeNull());
|
() => expect(Validators.requiredTrue(new FormControl(true))).toBeNull());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('email', () => {
|
||||||
|
it('should error on invalid email',
|
||||||
|
() => expect(Validators.email(new FormControl('some text'))).toEqual({'email': true}));
|
||||||
|
|
||||||
|
it('should not error on valid email',
|
||||||
|
() => expect(Validators.email(new FormControl('test@gmail.com'))).toBeNull());
|
||||||
|
});
|
||||||
|
|
||||||
describe('minLength', () => {
|
describe('minLength', () => {
|
||||||
it('should not error on an empty string',
|
it('should not error on an empty string',
|
||||||
() => { expect(Validators.minLength(2)(new FormControl(''))).toBeNull(); });
|
() => { expect(Validators.minLength(2)(new FormControl(''))).toBeNull(); });
|
||||||
|
|
|
@ -150,6 +150,15 @@ export declare class DefaultValueAccessor implements ControlValueAccessor {
|
||||||
writeValue(value: any): void;
|
writeValue(value: any): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare class EmailValidator implements Validator {
|
||||||
|
email: boolean | string;
|
||||||
|
registerOnValidatorChange(fn: () => void): void;
|
||||||
|
validate(c: AbstractControl): {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export interface Form {
|
export interface Form {
|
||||||
addControl(dir: NgControl): void;
|
addControl(dir: NgControl): void;
|
||||||
|
@ -529,6 +538,9 @@ export interface ValidatorFn {
|
||||||
export declare class Validators {
|
export declare class Validators {
|
||||||
static compose(validators: ValidatorFn[]): ValidatorFn;
|
static compose(validators: ValidatorFn[]): ValidatorFn;
|
||||||
static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn;
|
static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn;
|
||||||
|
static email(control: AbstractControl): {
|
||||||
|
[key: string]: boolean;
|
||||||
|
};
|
||||||
static maxLength(maxLength: number): ValidatorFn;
|
static maxLength(maxLength: number): ValidatorFn;
|
||||||
static minLength(minLength: number): ValidatorFn;
|
static minLength(minLength: number): ValidatorFn;
|
||||||
static nullValidator(c: AbstractControl): {
|
static nullValidator(c: AbstractControl): {
|
||||||
|
|
Loading…
Reference in New Issue