parent
0b665c0ece
commit
d2ad871279
|
@ -9,7 +9,7 @@
|
|||
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
import {AsyncValidatorFn, Validator, ValidatorFn} from './validators';
|
||||
|
||||
function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
|
@ -26,6 +26,10 @@ function unimplemented(): any {
|
|||
export abstract class NgControl extends AbstractControlDirective {
|
||||
name: string = null;
|
||||
valueAccessor: ControlValueAccessor = null;
|
||||
/** @internal */
|
||||
_rawValidators: Array<Validator|ValidatorFn> = [];
|
||||
/** @internal */
|
||||
_rawAsyncValidators: Array<Validator|ValidatorFn> = [];
|
||||
|
||||
get validator(): ValidatorFn { return <ValidatorFn>unimplemented(); }
|
||||
get asyncValidator(): AsyncValidatorFn { return <AsyncValidatorFn>unimplemented(); }
|
||||
|
|
|
@ -20,7 +20,7 @@ import {NgForm} from './ng_form';
|
|||
import {NgModelGroup} from './ng_model_group';
|
||||
import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared';
|
||||
import {TemplateDrivenErrors} from './template_driven_errors';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
import {AsyncValidatorFn, Validator, ValidatorFn} from './validators';
|
||||
|
||||
export const formControlBinding: any = {
|
||||
provide: NgControl,
|
||||
|
@ -72,11 +72,13 @@ export class NgModel extends NgControl implements OnChanges,
|
|||
@Output('ngModelChange') update = new EventEmitter();
|
||||
|
||||
constructor(@Optional() @Host() private _parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[],
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<Validator|AsyncValidatorFn>,
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this._rawValidators = validators || [];
|
||||
this._rawAsyncValidators = asyncValidators || [];
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
|
@ -103,10 +105,10 @@ export class NgModel extends NgControl implements OnChanges,
|
|||
|
||||
get formDirective(): any { return this._parent ? this._parent.formDirective : null; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
get validator(): ValidatorFn { return composeValidators(this._rawValidators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
return composeAsyncValidators(this._rawAsyncValidators);
|
||||
}
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
|
|
|
@ -16,7 +16,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor
|
|||
import {NgControl} from '../ng_control';
|
||||
import {ReactiveErrors} from '../reactive_errors';
|
||||
import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from '../shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from '../validators';
|
||||
import {AsyncValidatorFn, Validator, ValidatorFn} from '../validators';
|
||||
|
||||
export const formControlBinding: any = {
|
||||
provide: NgControl,
|
||||
|
@ -84,13 +84,13 @@ export class FormControlDirective extends NgControl implements OnChanges {
|
|||
@Input('disabled')
|
||||
set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); }
|
||||
|
||||
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<Validator|AsyncValidatorFn>,
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this._rawValidators = validators || [];
|
||||
this._rawAsyncValidators = asyncValidators || [];
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
|
@ -108,10 +108,10 @@ export class FormControlDirective extends NgControl implements OnChanges {
|
|||
|
||||
get path(): string[] { return []; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
get validator(): ValidatorFn { return composeValidators(this._rawValidators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
return composeAsyncValidators(this._rawAsyncValidators);
|
||||
}
|
||||
|
||||
get control(): FormControl { return this.form; }
|
||||
|
|
|
@ -17,7 +17,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor
|
|||
import {NgControl} from '../ng_control';
|
||||
import {ReactiveErrors} from '../reactive_errors';
|
||||
import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor} from '../shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from '../validators';
|
||||
import {AsyncValidatorFn, Validator, ValidatorFn} from '../validators';
|
||||
|
||||
import {FormGroupDirective} from './form_group_directive';
|
||||
import {FormArrayName, FormGroupName} from './form_group_name';
|
||||
|
@ -110,12 +110,13 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
|||
|
||||
constructor(
|
||||
@Optional() @Host() @SkipSelf() private _parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators:
|
||||
Array<Validator|AsyncValidatorFn>,
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this._rawValidators = validators || [];
|
||||
this._rawAsyncValidators = asyncValidators || [];
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
|
@ -147,9 +148,11 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
|||
|
||||
get formDirective(): any { return this._parent ? this._parent.formDirective : null; }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
get validator(): ValidatorFn { return composeValidators(this._rawValidators); }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._rawAsyncValidators);
|
||||
}
|
||||
|
||||
get control(): FormControl { return this.formDirective.getControl(this); }
|
||||
|
||||
|
|
|
@ -124,7 +124,6 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
|||
|
||||
var async = composeAsyncValidators(this._asyncValidators);
|
||||
this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator, async]);
|
||||
this.form.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
this._updateDomValue(changes);
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +188,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
|||
/** @internal */
|
||||
_updateDomValue(changes: SimpleChanges) {
|
||||
const oldForm = changes['form'].previousValue;
|
||||
|
||||
this.directives.forEach(dir => {
|
||||
const newCtrl: any = this.form.get(dir.path);
|
||||
const oldCtrl = oldForm.get(dir.path);
|
||||
|
@ -197,6 +197,8 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
|||
if (newCtrl) setUpControl(newCtrl, dir);
|
||||
}
|
||||
});
|
||||
|
||||
this.form._updateTreeValidity({emitEvent: false});
|
||||
}
|
||||
|
||||
private _checkFormPresent() {
|
||||
|
|
|
@ -25,7 +25,7 @@ import {RadioControlValueAccessor} from './radio_control_value_accessor';
|
|||
import {FormArrayName} from './reactive_directives/form_group_name';
|
||||
import {SelectControlValueAccessor} from './select_control_value_accessor';
|
||||
import {SelectMultipleControlValueAccessor} from './select_multiple_control_value_accessor';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './validators';
|
||||
import {AsyncValidatorFn, Validator, ValidatorFn} from './validators';
|
||||
|
||||
|
||||
export function controlPath(name: string, parent: ControlContainer): string[] {
|
||||
|
@ -49,6 +49,9 @@ export function setUpControl(control: FormControl, dir: NgControl): void {
|
|||
control.setValue(newValue, {emitModelToViewChange: false});
|
||||
});
|
||||
|
||||
// touched
|
||||
dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
|
||||
|
||||
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
|
||||
// control -> view
|
||||
dir.valueAccessor.writeValue(newValue);
|
||||
|
@ -62,13 +65,23 @@ export function setUpControl(control: FormControl, dir: NgControl): void {
|
|||
(isDisabled: boolean) => { dir.valueAccessor.setDisabledState(isDisabled); });
|
||||
}
|
||||
|
||||
// touched
|
||||
dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
|
||||
// re-run validation when validator binding changes, e.g. minlength=3 -> minlength=4
|
||||
dir._rawValidators.forEach((validator: Validator | ValidatorFn) => {
|
||||
if ((<Validator>validator).registerOnChange)
|
||||
(<Validator>validator).registerOnChange(() => control.updateValueAndValidity());
|
||||
});
|
||||
|
||||
dir._rawAsyncValidators.forEach((validator: Validator | ValidatorFn) => {
|
||||
if ((<Validator>validator).registerOnChange)
|
||||
(<Validator>validator).registerOnChange(() => control.updateValueAndValidity());
|
||||
});
|
||||
}
|
||||
|
||||
export function cleanUpControl(control: FormControl, dir: NgControl) {
|
||||
dir.valueAccessor.registerOnChange(() => _noControlError(dir));
|
||||
dir.valueAccessor.registerOnTouched(() => _noControlError(dir));
|
||||
dir._rawValidators.forEach((validator: Validator) => validator.registerOnChange(null));
|
||||
dir._rawAsyncValidators.forEach((validator: Validator) => validator.registerOnChange(null));
|
||||
if (control) control._clearChangeFns();
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Attribute, Directive, HostBinding, Input, OnChanges, SimpleChanges, forwardRef} from '@angular/core';
|
||||
import {Directive, Input, OnChanges, SimpleChanges, forwardRef} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {AbstractControl} from '../model';
|
||||
|
@ -33,7 +33,10 @@ import {NG_VALIDATORS, Validators} from '../validators';
|
|||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface Validator { validate(c: AbstractControl): {[key: string]: any}; }
|
||||
export interface Validator {
|
||||
validate(c: AbstractControl): {[key: string]: any};
|
||||
registerOnChange?(fn: () => void): void;
|
||||
}
|
||||
|
||||
export const REQUIRED_VALIDATOR: any = {
|
||||
provide: NG_VALIDATORS,
|
||||
|
@ -60,15 +63,21 @@ export const REQUIRED_VALIDATOR: any = {
|
|||
})
|
||||
export class RequiredValidator implements Validator {
|
||||
private _required: boolean;
|
||||
private _onChange: () => void;
|
||||
|
||||
@Input()
|
||||
get required(): boolean { return this._required; }
|
||||
|
||||
set required(value: boolean) { this._required = isPresent(value) && `${value}` !== 'false'; }
|
||||
set required(value: boolean) {
|
||||
this._required = isPresent(value) && `${value}` !== 'false';
|
||||
if (this._onChange) this._onChange();
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} {
|
||||
return this.required ? Validators.required(c) : null;
|
||||
}
|
||||
|
||||
registerOnChange(fn: () => void) { this._onChange = fn; }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,6 +119,7 @@ export const MIN_LENGTH_VALIDATOR: any = {
|
|||
export class MinLengthValidator implements Validator,
|
||||
OnChanges {
|
||||
private _validator: ValidatorFn;
|
||||
private _onChange: () => void;
|
||||
|
||||
@Input() minlength: string;
|
||||
|
||||
|
@ -118,15 +128,17 @@ export class MinLengthValidator implements Validator,
|
|||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const minlengthChange = changes['minlength'];
|
||||
if (minlengthChange) {
|
||||
if (changes['minlength']) {
|
||||
this._createValidator();
|
||||
if (this._onChange) this._onChange();
|
||||
}
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} {
|
||||
return isPresent(this.minlength) ? this._validator(c) : null;
|
||||
}
|
||||
|
||||
registerOnChange(fn: () => void) { this._onChange = fn; }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,6 +169,7 @@ export const MAX_LENGTH_VALIDATOR: any = {
|
|||
export class MaxLengthValidator implements Validator,
|
||||
OnChanges {
|
||||
private _validator: ValidatorFn;
|
||||
private _onChange: () => void;
|
||||
|
||||
@Input() maxlength: string;
|
||||
|
||||
|
@ -165,15 +178,17 @@ export class MaxLengthValidator implements Validator,
|
|||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const maxlengthChange = changes['maxlength'];
|
||||
if (maxlengthChange) {
|
||||
if (changes['maxlength']) {
|
||||
this._createValidator();
|
||||
if (this._onChange) this._onChange();
|
||||
}
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} {
|
||||
return isPresent(this.maxlength) ? this._validator(c) : null;
|
||||
}
|
||||
|
||||
registerOnChange(fn: () => void) { this._onChange = fn; }
|
||||
}
|
||||
|
||||
|
||||
|
@ -205,19 +220,22 @@ export const PATTERN_VALIDATOR: any = {
|
|||
export class PatternValidator implements Validator,
|
||||
OnChanges {
|
||||
private _validator: ValidatorFn;
|
||||
private _onChange: () => void;
|
||||
|
||||
@Input() pattern: string;
|
||||
|
||||
private _createValidator() { this._validator = Validators.pattern(this.pattern); }
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const patternChange = changes['pattern'];
|
||||
if (patternChange) {
|
||||
if (changes['pattern']) {
|
||||
this._createValidator();
|
||||
if (this._onChange) this._onChange();
|
||||
}
|
||||
}
|
||||
|
||||
validate(c: AbstractControl): {[key: string]: any} {
|
||||
return isPresent(this.pattern) ? this._validator(c) : null;
|
||||
}
|
||||
|
||||
registerOnChange(fn: () => void) { this._onChange = fn; }
|
||||
}
|
||||
|
|
|
@ -253,6 +253,12 @@ export abstract class AbstractControl {
|
|||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_updateTreeValidity({emitEvent}: {emitEvent?: boolean} = {emitEvent: true}) {
|
||||
this._forEachChild((ctrl: AbstractControl) => ctrl._updateTreeValidity({emitEvent}));
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent});
|
||||
}
|
||||
|
||||
private _runValidator(): {[key: string]: any} {
|
||||
return isPresent(this.validator) ? this.validator(this) : null;
|
||||
}
|
||||
|
|
|
@ -809,5 +809,39 @@ export function main() {
|
|||
});
|
||||
|
||||
});
|
||||
|
||||
describe('updateTreeValidity()', () => {
|
||||
let c: FormControl, c2: FormControl, c3: FormControl;
|
||||
let nested: FormGroup, form: FormGroup;
|
||||
let logger: string[];
|
||||
|
||||
beforeEach(() => {
|
||||
c = new FormControl('one');
|
||||
c2 = new FormControl('two');
|
||||
c3 = new FormControl('three');
|
||||
nested = new FormGroup({one: c, two: c2});
|
||||
form = new FormGroup({nested: nested, three: c3});
|
||||
logger = [];
|
||||
|
||||
c.statusChanges.subscribe(() => logger.push('one'));
|
||||
c2.statusChanges.subscribe(() => logger.push('two'));
|
||||
c3.statusChanges.subscribe(() => logger.push('three'));
|
||||
nested.statusChanges.subscribe(() => logger.push('nested'));
|
||||
form.statusChanges.subscribe(() => logger.push('form'));
|
||||
});
|
||||
|
||||
it('should update tree validity', () => {
|
||||
form._updateTreeValidity();
|
||||
expect(logger).toEqual(['one', 'two', 'nested', 'three', 'form']);
|
||||
});
|
||||
|
||||
it('should not emit events when turned off', () => {
|
||||
form._updateTreeValidity({emitEvent: false});
|
||||
expect(logger).toEqual([]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
|
|
@ -160,6 +160,30 @@ export function main() {
|
|||
expect(inputs[0].nativeElement.value).toEqual('Bess');
|
||||
});
|
||||
|
||||
it('should pick up dir validators from form controls', () => {
|
||||
const fixture = TestBed.createComponent(LoginIsEmptyWrapper);
|
||||
const form = new FormGroup({
|
||||
'login': new FormControl(''),
|
||||
'min': new FormControl(''),
|
||||
'max': new FormControl(''),
|
||||
'pattern': new FormControl('')
|
||||
});
|
||||
fixture.debugElement.componentInstance.form = form;
|
||||
fixture.detectChanges();
|
||||
expect(form.get('login').errors).toEqual({required: true});
|
||||
|
||||
const newForm = new FormGroup({
|
||||
'login': new FormControl(''),
|
||||
'min': new FormControl(''),
|
||||
'max': new FormControl(''),
|
||||
'pattern': new FormControl('')
|
||||
});
|
||||
fixture.debugElement.componentInstance.form = newForm;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(newForm.get('login').errors).toEqual({required: true});
|
||||
});
|
||||
|
||||
it('should pick up dir validators from nested form groups', () => {
|
||||
const fixture = TestBed.createComponent(NestedFormGroupComp);
|
||||
const form = new FormGroup({
|
||||
|
@ -1024,7 +1048,7 @@ export function main() {
|
|||
expect(form.valid).toEqual(true);
|
||||
});
|
||||
|
||||
it('changes on binded properties should change the validation state of the form', () => {
|
||||
it('changes on bound properties should change the validation state of the form', () => {
|
||||
const fixture = TestBed.createComponent(ValidationBindingsForm);
|
||||
const form = new FormGroup({
|
||||
'login': new FormControl(''),
|
||||
|
@ -1087,11 +1111,6 @@ export function main() {
|
|||
fixture.debugElement.componentInstance.pattern = null;
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchEvent(required.nativeElement, 'input');
|
||||
dispatchEvent(minLength.nativeElement, 'input');
|
||||
dispatchEvent(maxLength.nativeElement, 'input');
|
||||
dispatchEvent(pattern.nativeElement, 'input');
|
||||
|
||||
expect(form.hasError('required', ['login'])).toEqual(false);
|
||||
expect(form.hasError('minlength', ['min'])).toEqual(false);
|
||||
expect(form.hasError('maxlength', ['max'])).toEqual(false);
|
||||
|
@ -1104,6 +1123,43 @@ export function main() {
|
|||
expect(required.nativeElement.getAttribute('pattern')).toEqual(null);
|
||||
});
|
||||
|
||||
it('should support rebound controls with rebound validators', () => {
|
||||
const fixture = TestBed.createComponent(ValidationBindingsForm);
|
||||
const form = new FormGroup({
|
||||
'login': new FormControl(''),
|
||||
'min': new FormControl(''),
|
||||
'max': new FormControl(''),
|
||||
'pattern': new FormControl('')
|
||||
});
|
||||
fixture.debugElement.componentInstance.form = form;
|
||||
fixture.debugElement.componentInstance.required = true;
|
||||
fixture.debugElement.componentInstance.minLen = 3;
|
||||
fixture.debugElement.componentInstance.maxLen = 3;
|
||||
fixture.debugElement.componentInstance.pattern = '.{3,}';
|
||||
fixture.detectChanges();
|
||||
|
||||
const newForm = new FormGroup({
|
||||
'login': new FormControl(''),
|
||||
'min': new FormControl(''),
|
||||
'max': new FormControl(''),
|
||||
'pattern': new FormControl('')
|
||||
});
|
||||
fixture.debugElement.componentInstance.form = newForm;
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.debugElement.componentInstance.required = false;
|
||||
fixture.debugElement.componentInstance.minLen = null;
|
||||
fixture.debugElement.componentInstance.maxLen = null;
|
||||
fixture.debugElement.componentInstance.pattern = null;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(newForm.hasError('required', ['login'])).toEqual(false);
|
||||
expect(newForm.hasError('minlength', ['min'])).toEqual(false);
|
||||
expect(newForm.hasError('maxlength', ['max'])).toEqual(false);
|
||||
expect(newForm.hasError('pattern', ['pattern'])).toEqual(false);
|
||||
expect(newForm.valid).toEqual(true);
|
||||
});
|
||||
|
||||
it('should use async validators defined in the html', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(UniqLoginWrapper);
|
||||
const form = new FormGroup({'login': new FormControl('')});
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import {NgFor, NgIf} from '@angular/common';
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {TestBed, async, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {beforeEach, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
|
||||
import {ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR, NgForm} from '@angular/forms';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
@ -25,7 +24,8 @@ export function main() {
|
|||
declarations: [
|
||||
StandaloneNgModel, NgModelForm, NgModelGroupForm, NgModelValidBinding, NgModelNgIfForm,
|
||||
NgModelRadioForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName,
|
||||
NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper
|
||||
NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper,
|
||||
NgModelValidationBindings
|
||||
],
|
||||
imports: [FormsModule]
|
||||
});
|
||||
|
@ -574,6 +574,125 @@ export function main() {
|
|||
|
||||
});
|
||||
|
||||
describe('validation directives', () => {
|
||||
|
||||
it('should support dir validators using bindings', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelValidationBindings);
|
||||
fixture.debugElement.componentInstance.required = true;
|
||||
fixture.debugElement.componentInstance.minLen = 3;
|
||||
fixture.debugElement.componentInstance.maxLen = 3;
|
||||
fixture.debugElement.componentInstance.pattern = '.{3,}';
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
const required = fixture.debugElement.query(By.css('[name=required]'));
|
||||
const minLength = fixture.debugElement.query(By.css('[name=minlength]'));
|
||||
const maxLength = fixture.debugElement.query(By.css('[name=maxlength]'));
|
||||
const pattern = fixture.debugElement.query(By.css('[name=pattern]'));
|
||||
|
||||
required.nativeElement.value = '';
|
||||
minLength.nativeElement.value = '1';
|
||||
maxLength.nativeElement.value = '1234';
|
||||
pattern.nativeElement.value = '12';
|
||||
|
||||
dispatchEvent(required.nativeElement, 'input');
|
||||
dispatchEvent(minLength.nativeElement, 'input');
|
||||
dispatchEvent(maxLength.nativeElement, 'input');
|
||||
dispatchEvent(pattern.nativeElement, 'input');
|
||||
fixture.detectChanges();
|
||||
|
||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||
expect(form.control.hasError('required', ['required'])).toEqual(true);
|
||||
expect(form.control.hasError('minlength', ['minlength'])).toEqual(true);
|
||||
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(true);
|
||||
expect(form.control.hasError('pattern', ['pattern'])).toEqual(true);
|
||||
|
||||
required.nativeElement.value = '1';
|
||||
minLength.nativeElement.value = '123';
|
||||
maxLength.nativeElement.value = '123';
|
||||
pattern.nativeElement.value = '123';
|
||||
|
||||
dispatchEvent(required.nativeElement, 'input');
|
||||
dispatchEvent(minLength.nativeElement, 'input');
|
||||
dispatchEvent(maxLength.nativeElement, 'input');
|
||||
dispatchEvent(pattern.nativeElement, 'input');
|
||||
|
||||
expect(form.valid).toEqual(true);
|
||||
}));
|
||||
|
||||
it('changes on bound properties should change the validation state of the form',
|
||||
fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(NgModelValidationBindings);
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
const required = fixture.debugElement.query(By.css('[name=required]'));
|
||||
const minLength = fixture.debugElement.query(By.css('[name=minlength]'));
|
||||
const maxLength = fixture.debugElement.query(By.css('[name=maxlength]'));
|
||||
const pattern = fixture.debugElement.query(By.css('[name=pattern]'));
|
||||
|
||||
required.nativeElement.value = '';
|
||||
minLength.nativeElement.value = '1';
|
||||
maxLength.nativeElement.value = '1234';
|
||||
pattern.nativeElement.value = '12';
|
||||
|
||||
dispatchEvent(required.nativeElement, 'input');
|
||||
dispatchEvent(minLength.nativeElement, 'input');
|
||||
dispatchEvent(maxLength.nativeElement, 'input');
|
||||
dispatchEvent(pattern.nativeElement, 'input');
|
||||
|
||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||
expect(form.control.hasError('required', ['required'])).toEqual(false);
|
||||
expect(form.control.hasError('minlength', ['minlength'])).toEqual(false);
|
||||
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false);
|
||||
expect(form.control.hasError('pattern', ['pattern'])).toEqual(false);
|
||||
expect(form.valid).toEqual(true);
|
||||
|
||||
fixture.debugElement.componentInstance.required = true;
|
||||
fixture.debugElement.componentInstance.minLen = 3;
|
||||
fixture.debugElement.componentInstance.maxLen = 3;
|
||||
fixture.debugElement.componentInstance.pattern = '.{3,}';
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchEvent(required.nativeElement, 'input');
|
||||
dispatchEvent(minLength.nativeElement, 'input');
|
||||
dispatchEvent(maxLength.nativeElement, 'input');
|
||||
dispatchEvent(pattern.nativeElement, 'input');
|
||||
|
||||
expect(form.control.hasError('required', ['required'])).toEqual(true);
|
||||
expect(form.control.hasError('minlength', ['minlength'])).toEqual(true);
|
||||
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(true);
|
||||
expect(form.control.hasError('pattern', ['pattern'])).toEqual(true);
|
||||
expect(form.valid).toEqual(false);
|
||||
|
||||
expect(required.nativeElement.getAttribute('required')).toEqual('');
|
||||
expect(fixture.debugElement.componentInstance.minLen.toString())
|
||||
.toEqual(minLength.nativeElement.getAttribute('minlength'));
|
||||
expect(fixture.debugElement.componentInstance.maxLen.toString())
|
||||
.toEqual(maxLength.nativeElement.getAttribute('maxlength'));
|
||||
expect(fixture.debugElement.componentInstance.pattern.toString())
|
||||
.toEqual(pattern.nativeElement.getAttribute('pattern'));
|
||||
|
||||
fixture.debugElement.componentInstance.required = false;
|
||||
fixture.debugElement.componentInstance.minLen = null;
|
||||
fixture.debugElement.componentInstance.maxLen = null;
|
||||
fixture.debugElement.componentInstance.pattern = null;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(form.control.hasError('required', ['required'])).toEqual(false);
|
||||
expect(form.control.hasError('minlength', ['minlength'])).toEqual(false);
|
||||
expect(form.control.hasError('maxlength', ['maxlength'])).toEqual(false);
|
||||
expect(form.control.hasError('pattern', ['pattern'])).toEqual(false);
|
||||
expect(form.valid).toEqual(true);
|
||||
|
||||
expect(required.nativeElement.getAttribute('required')).toEqual(null);
|
||||
expect(required.nativeElement.getAttribute('minlength')).toEqual(null);
|
||||
expect(required.nativeElement.getAttribute('maxlength')).toEqual(null);
|
||||
expect(required.nativeElement.getAttribute('pattern')).toEqual(null);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('ngModel corner cases', () => {
|
||||
it('should update the view when the model is set back to what used to be in the view',
|
||||
fakeAsync(() => {
|
||||
|
@ -791,6 +910,24 @@ class NgModelCustomWrapper {
|
|||
isDisabled = false;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ng-model-validation-bindings',
|
||||
template: `
|
||||
<form>
|
||||
<input name="required" ngModel [required]="required">
|
||||
<input name="minlength" ngModel [minlength]="minLen">
|
||||
<input name="maxlength" ngModel [maxlength]="maxLen">
|
||||
<input name="pattern" ngModel [pattern]="pattern">
|
||||
</form>
|
||||
`
|
||||
})
|
||||
class NgModelValidationBindings {
|
||||
required: boolean;
|
||||
minLen: number;
|
||||
maxLen: number;
|
||||
pattern: string;
|
||||
}
|
||||
|
||||
function sortedClassList(el: HTMLElement) {
|
||||
var l = getDOM().classList(el);
|
||||
ListWrapper.sort(l);
|
||||
|
|
|
@ -229,7 +229,7 @@ export declare class FormControlDirective extends NgControl implements OnChanges
|
|||
update: EventEmitter<{}>;
|
||||
validator: ValidatorFn;
|
||||
viewModel: any;
|
||||
constructor(_validators: any[], _asyncValidators: any[], valueAccessors: ControlValueAccessor[]);
|
||||
constructor(validators: Array<Validator | ValidatorFn>, asyncValidators: Array<Validator | AsyncValidatorFn>, valueAccessors: ControlValueAccessor[]);
|
||||
ngOnChanges(changes: SimpleChanges): void;
|
||||
viewToModelUpdate(newValue: any): void;
|
||||
}
|
||||
|
@ -245,7 +245,7 @@ export declare class FormControlName extends NgControl implements OnChanges, OnD
|
|||
path: string[];
|
||||
update: EventEmitter<{}>;
|
||||
validator: ValidatorFn;
|
||||
constructor(_parent: ControlContainer, _validators: any[], _asyncValidators: any[], valueAccessors: ControlValueAccessor[]);
|
||||
constructor(_parent: ControlContainer, validators: Array<Validator | ValidatorFn>, asyncValidators: Array<Validator | AsyncValidatorFn>, valueAccessors: ControlValueAccessor[]);
|
||||
ngOnChanges(changes: SimpleChanges): void;
|
||||
ngOnDestroy(): void;
|
||||
viewToModelUpdate(newValue: any): void;
|
||||
|
@ -319,6 +319,7 @@ export declare class FormsModule {
|
|||
export declare class MaxLengthValidator implements Validator, OnChanges {
|
||||
maxlength: string;
|
||||
ngOnChanges(changes: SimpleChanges): void;
|
||||
registerOnChange(fn: () => void): void;
|
||||
validate(c: AbstractControl): {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
@ -328,6 +329,7 @@ export declare class MaxLengthValidator implements Validator, OnChanges {
|
|||
export declare class MinLengthValidator implements Validator, OnChanges {
|
||||
minlength: string;
|
||||
ngOnChanges(changes: SimpleChanges): void;
|
||||
registerOnChange(fn: () => void): void;
|
||||
validate(c: AbstractControl): {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
@ -404,7 +406,7 @@ export declare class NgModel extends NgControl implements OnChanges, OnDestroy {
|
|||
update: EventEmitter<{}>;
|
||||
validator: ValidatorFn;
|
||||
viewModel: any;
|
||||
constructor(_parent: ControlContainer, _validators: any[], _asyncValidators: any[], valueAccessors: ControlValueAccessor[]);
|
||||
constructor(_parent: ControlContainer, validators: Array<Validator | ValidatorFn>, asyncValidators: Array<Validator | AsyncValidatorFn>, valueAccessors: ControlValueAccessor[]);
|
||||
ngOnChanges(changes: SimpleChanges): void;
|
||||
ngOnDestroy(): void;
|
||||
viewToModelUpdate(newValue: any): void;
|
||||
|
@ -429,6 +431,7 @@ export declare class NgSelectOption implements OnDestroy {
|
|||
export declare class PatternValidator implements Validator, OnChanges {
|
||||
pattern: string;
|
||||
ngOnChanges(changes: SimpleChanges): void;
|
||||
registerOnChange(fn: () => void): void;
|
||||
validate(c: AbstractControl): {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
@ -441,6 +444,7 @@ export declare class ReactiveFormsModule {
|
|||
/** @stable */
|
||||
export declare class RequiredValidator implements Validator {
|
||||
required: boolean;
|
||||
registerOnChange(fn: () => void): void;
|
||||
validate(c: AbstractControl): {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
@ -472,6 +476,7 @@ export declare class SelectMultipleControlValueAccessor implements ControlValueA
|
|||
|
||||
/** @stable */
|
||||
export interface Validator {
|
||||
registerOnChange?(fn: () => void): void;
|
||||
validate(c: AbstractControl): {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue