style(forms): reformat of the forms package after clang update (#36466)

PR Close #36466
This commit is contained in:
Andrew Kushnir 2020-04-06 15:44:00 -07:00 committed by Kara Erickson
parent aeb6d0d0f7
commit f48a065db0
37 changed files with 5410 additions and 5093 deletions

View File

@ -31,7 +31,9 @@ export abstract class AbstractControlDirective {
* @description * @description
* Reports the value of the control if it is present, otherwise null. * Reports the value of the control if it is present, otherwise null.
*/ */
get value(): any { return this.control ? this.control.value : null; } get value(): any {
return this.control ? this.control.value : null;
}
/** /**
* @description * @description
@ -39,14 +41,18 @@ export abstract class AbstractControlDirective {
* validation errors exist with the current value. * validation errors exist with the current value.
* If the control is not present, null is returned. * If the control is not present, null is returned.
*/ */
get valid(): boolean|null { return this.control ? this.control.valid : null; } get valid(): boolean|null {
return this.control ? this.control.valid : null;
}
/** /**
* @description * @description
* Reports whether the control is invalid, meaning that an error exists in the input value. * Reports whether the control is invalid, meaning that an error exists in the input value.
* If the control is not present, null is returned. * If the control is not present, null is returned.
*/ */
get invalid(): boolean|null { return this.control ? this.control.invalid : null; } get invalid(): boolean|null {
return this.control ? this.control.invalid : null;
}
/** /**
* @description * @description
@ -54,7 +60,9 @@ export abstract class AbstractControlDirective {
* errors are not yet available for the input value. If the control is not present, null is * errors are not yet available for the input value. If the control is not present, null is
* returned. * returned.
*/ */
get pending(): boolean|null { return this.control ? this.control.pending : null; } get pending(): boolean|null {
return this.control ? this.control.pending : null;
}
/** /**
* @description * @description
@ -62,41 +70,53 @@ export abstract class AbstractControlDirective {
* in the UI and is exempt from validation checks and excluded from aggregate * in the UI and is exempt from validation checks and excluded from aggregate
* values of ancestor controls. If the control is not present, null is returned. * values of ancestor controls. If the control is not present, null is returned.
*/ */
get disabled(): boolean|null { return this.control ? this.control.disabled : null; } get disabled(): boolean|null {
return this.control ? this.control.disabled : null;
}
/** /**
* @description * @description
* Reports whether the control is enabled, meaning that the control is included in ancestor * Reports whether the control is enabled, meaning that the control is included in ancestor
* calculations of validity or value. If the control is not present, null is returned. * calculations of validity or value. If the control is not present, null is returned.
*/ */
get enabled(): boolean|null { return this.control ? this.control.enabled : null; } get enabled(): boolean|null {
return this.control ? this.control.enabled : null;
}
/** /**
* @description * @description
* Reports the control's validation errors. If the control is not present, null is returned. * Reports the control's validation errors. If the control is not present, null is returned.
*/ */
get errors(): ValidationErrors|null { return this.control ? this.control.errors : null; } get errors(): ValidationErrors|null {
return this.control ? this.control.errors : null;
}
/** /**
* @description * @description
* Reports whether the control is pristine, meaning that the user has not yet changed * Reports whether the control is pristine, meaning that the user has not yet changed
* the value in the UI. If the control is not present, null is returned. * the value in the UI. If the control is not present, null is returned.
*/ */
get pristine(): boolean|null { return this.control ? this.control.pristine : null; } get pristine(): boolean|null {
return this.control ? this.control.pristine : null;
}
/** /**
* @description * @description
* Reports whether the control is dirty, meaning that the user has changed * Reports whether the control is dirty, meaning that the user has changed
* the value in the UI. If the control is not present, null is returned. * the value in the UI. If the control is not present, null is returned.
*/ */
get dirty(): boolean|null { return this.control ? this.control.dirty : null; } get dirty(): boolean|null {
return this.control ? this.control.dirty : null;
}
/** /**
* @description * @description
* Reports whether the control is touched, meaning that the user has triggered * Reports whether the control is touched, meaning that the user has triggered
* a `blur` event on it. If the control is not present, null is returned. * a `blur` event on it. If the control is not present, null is returned.
*/ */
get touched(): boolean|null { return this.control ? this.control.touched : null; } get touched(): boolean|null {
return this.control ? this.control.touched : null;
}
/** /**
* @description * @description
@ -104,14 +124,18 @@ export abstract class AbstractControlDirective {
* 'VALID', 'INVALID', 'DISABLED', and 'PENDING'. * 'VALID', 'INVALID', 'DISABLED', and 'PENDING'.
* If the control is not present, null is returned. * If the control is not present, null is returned.
*/ */
get status(): string|null { return this.control ? this.control.status : null; } get status(): string|null {
return this.control ? this.control.status : null;
}
/** /**
* @description * @description
* Reports whether the control is untouched, meaning that the user has not yet triggered * Reports whether the control is untouched, meaning that the user has not yet triggered
* a `blur` event on it. If the control is not present, null is returned. * a `blur` event on it. If the control is not present, null is returned.
*/ */
get untouched(): boolean|null { return this.control ? this.control.untouched : null; } get untouched(): boolean|null {
return this.control ? this.control.untouched : null;
}
/** /**
* @description * @description
@ -137,7 +161,9 @@ export abstract class AbstractControlDirective {
* Returns an array that represents the path from the top-level form to this control. * Returns an array that represents the path from the top-level form to this control.
* Each index is the string name of the control on that level. * Each index is the string name of the control on that level.
*/ */
get path(): string[]|null { return null; } get path(): string[]|null {
return null;
}
/** /**
* @description * @description

View File

@ -76,7 +76,9 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn
* @description * @description
* The `FormGroup` bound to this directive. * The `FormGroup` bound to this directive.
*/ */
get control(): FormGroup { return this.formDirective !.getFormGroup(this); } get control(): FormGroup {
return this.formDirective!.getFormGroup(this);
}
/** /**
* @description * @description
@ -90,13 +92,17 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn
* @description * @description
* The top-level directive for this group if present, otherwise null. * The top-level directive for this group if present, otherwise null.
*/ */
get formDirective(): Form|null { return this._parent ? this._parent.formDirective : null; } get formDirective(): Form|null {
return this._parent ? this._parent.formDirective : null;
}
/** /**
* @description * @description
* The synchronous validators registered with this group. * The synchronous validators registered with this group.
*/ */
get validator(): ValidatorFn|null { return composeValidators(this._validators); } get validator(): ValidatorFn|null {
return composeValidators(this._validators);
}
/** /**
* @description * @description

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Renderer2, forwardRef} from '@angular/core'; import {Directive, ElementRef, forwardRef, Renderer2} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
@ -75,7 +75,9 @@ export class CheckboxControlValueAccessor implements ControlValueAccessor {
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; } registerOnChange(fn: (_: any) => {}): void {
this.onChange = fn;
}
/** /**
* @description * @description
@ -83,7 +85,9 @@ export class CheckboxControlValueAccessor implements ControlValueAccessor {
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnTouched(fn: () => {}): void { this.onTouched = fn; } registerOnTouched(fn: () => {}): void {
this.onTouched = fn;
}
/** /**
* Sets the "disabled" property on the input element. * Sets the "disabled" property on the input element.

View File

@ -29,11 +29,15 @@ export abstract class ControlContainer extends AbstractControlDirective {
* @description * @description
* The top-level form directive for the control. * The top-level form directive for the control.
*/ */
get formDirective(): Form|null { return null; } get formDirective(): Form|null {
return null;
}
/** /**
* @description * @description
* The path to this group. * The path to this group.
*/ */
get path(): string[]|null { return null; } get path(): string[]|null {
return null;
}
} }

View File

@ -7,7 +7,8 @@
*/ */
import {ɵgetDOM as getDOM} from '@angular/common'; import {ɵgetDOM as getDOM} from '@angular/common';
import {Directive, ElementRef, Inject, InjectionToken, Optional, Renderer2, forwardRef} from '@angular/core'; import {Directive, ElementRef, forwardRef, Inject, InjectionToken, Optional, Renderer2} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const DEFAULT_VALUE_ACCESSOR: any = { export const DEFAULT_VALUE_ACCESSOR: any = {
@ -112,7 +113,9 @@ export class DefaultValueAccessor implements ControlValueAccessor {
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; } registerOnChange(fn: (_: any) => void): void {
this.onChange = fn;
}
/** /**
* @description * @description
@ -120,7 +123,9 @@ export class DefaultValueAccessor implements ControlValueAccessor {
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnTouched(fn: () => void): void { this.onTouched = fn; } registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
/** /**
* Sets the "disabled" property on the input element. * Sets the "disabled" property on the input element.
@ -139,7 +144,9 @@ export class DefaultValueAccessor implements ControlValueAccessor {
} }
/** @internal */ /** @internal */
_compositionStart(): void { this._composing = true; } _compositionStart(): void {
this._composing = true;
}
/** @internal */ /** @internal */
_compositionEnd(value: any): void { _compositionEnd(value: any): void {

View File

@ -66,7 +66,9 @@ export abstract class NgControl extends AbstractControlDirective {
* *
* @throws An exception that this method is not implemented * @throws An exception that this method is not implemented
*/ */
get validator(): ValidatorFn|null { return <ValidatorFn>unimplemented(); } get validator(): ValidatorFn|null {
return <ValidatorFn>unimplemented();
}
/** /**
* @description * @description
@ -74,7 +76,9 @@ export abstract class NgControl extends AbstractControlDirective {
* *
* @throws An exception that this method is not implemented * @throws An exception that this method is not implemented
*/ */
get asyncValidator(): AsyncValidatorFn|null { return <AsyncValidatorFn>unimplemented(); } get asyncValidator(): AsyncValidatorFn|null {
return <AsyncValidatorFn>unimplemented();
}
/** /**
* @description * @description

View File

@ -15,15 +15,31 @@ import {NgControl} from './ng_control';
export class AbstractControlStatus { export class AbstractControlStatus {
private _cd: AbstractControlDirective; private _cd: AbstractControlDirective;
constructor(cd: AbstractControlDirective) { this._cd = cd; } constructor(cd: AbstractControlDirective) {
this._cd = cd;
}
get ngClassUntouched(): boolean { return this._cd.control ? this._cd.control.untouched : false; } get ngClassUntouched(): boolean {
get ngClassTouched(): boolean { return this._cd.control ? this._cd.control.touched : false; } return this._cd.control ? this._cd.control.untouched : false;
get ngClassPristine(): boolean { return this._cd.control ? this._cd.control.pristine : false; } }
get ngClassDirty(): boolean { return this._cd.control ? this._cd.control.dirty : false; } get ngClassTouched(): boolean {
get ngClassValid(): boolean { return this._cd.control ? this._cd.control.valid : false; } return this._cd.control ? this._cd.control.touched : false;
get ngClassInvalid(): boolean { return this._cd.control ? this._cd.control.invalid : false; } }
get ngClassPending(): boolean { return this._cd.control ? this._cd.control.pending : false; } get ngClassPristine(): boolean {
return this._cd.control ? this._cd.control.pristine : false;
}
get ngClassDirty(): boolean {
return this._cd.control ? this._cd.control.dirty : false;
}
get ngClassValid(): boolean {
return this._cd.control ? this._cd.control.valid : false;
}
get ngClassInvalid(): boolean {
return this._cd.control ? this._cd.control.invalid : false;
}
get ngClassPending(): boolean {
return this._cd.control ? this._cd.control.pending : false;
}
} }
export const ngControlStatusHost = { export const ngControlStatusHost = {
@ -61,7 +77,9 @@ export const ngControlStatusHost = {
*/ */
@Directive({selector: '[formControlName],[ngModel],[formControl]', host: ngControlStatusHost}) @Directive({selector: '[formControlName],[ngModel],[formControl]', host: ngControlStatusHost})
export class NgControlStatus extends AbstractControlStatus { export class NgControlStatus extends AbstractControlStatus {
constructor(@Self() cd: NgControl) { super(cd); } constructor(@Self() cd: NgControl) {
super(cd);
}
} }
/** /**
@ -81,5 +99,7 @@ export class NgControlStatus extends AbstractControlStatus {
host: ngControlStatusHost host: ngControlStatusHost
}) })
export class NgControlStatusGroup extends AbstractControlStatus { export class NgControlStatusGroup extends AbstractControlStatus {
constructor(@Self() cd: ControlContainer) { super(cd); } constructor(@Self() cd: ControlContainer) {
super(cd);
}
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AfterViewInit, Directive, EventEmitter, Inject, Input, Optional, Self, forwardRef} from '@angular/core'; import {AfterViewInit, Directive, EventEmitter, forwardRef, Inject, Input, Optional, Self} from '@angular/core';
import {AbstractControl, FormControl, FormGroup, FormHooks} from '../model'; import {AbstractControl, FormControl, FormGroup, FormHooks} from '../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators'; import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
@ -96,8 +96,7 @@ const resolvedPromise = (() => Promise.resolve(null))();
outputs: ['ngSubmit'], outputs: ['ngSubmit'],
exportAs: 'ngForm' exportAs: 'ngForm'
}) })
export class NgForm extends ControlContainer implements Form, export class NgForm extends ControlContainer implements Form, AfterViewInit {
AfterViewInit {
/** /**
* @description * @description
* Returns whether the form submission has been triggered. * Returns whether the form submission has been triggered.
@ -142,32 +141,42 @@ export class NgForm extends ControlContainer implements Form,
* @description * @description
* Lifecycle method called after the view is initialized. For internal use only. * Lifecycle method called after the view is initialized. For internal use only.
*/ */
ngAfterViewInit() { this._setUpdateStrategy(); } ngAfterViewInit() {
this._setUpdateStrategy();
}
/** /**
* @description * @description
* The directive instance. * The directive instance.
*/ */
get formDirective(): Form { return this; } get formDirective(): Form {
return this;
}
/** /**
* @description * @description
* The internal `FormGroup` instance. * The internal `FormGroup` instance.
*/ */
get control(): FormGroup { return this.form; } get control(): FormGroup {
return this.form;
}
/** /**
* @description * @description
* Returns an array representing the path to this group. Because this directive * Returns an array representing the path to this group. Because this directive
* always lives at the top level of a form, it is always an empty array. * always lives at the top level of a form, it is always an empty array.
*/ */
get path(): string[] { return []; } get path(): string[] {
return [];
}
/** /**
* @description * @description
* Returns a map of the controls in this group. * Returns a map of the controls in this group.
*/ */
get controls(): {[key: string]: AbstractControl} { return this.form.controls; } get controls(): {[key: string]: AbstractControl} {
return this.form.controls;
}
/** /**
* @description * @description
@ -193,7 +202,9 @@ export class NgForm extends ControlContainer implements Form,
* *
* @param dir The `NgModel` directive instance. * @param dir The `NgModel` directive instance.
*/ */
getControl(dir: NgModel): FormControl { return <FormControl>this.form.get(dir.path); } getControl(dir: NgModel): FormControl {
return <FormControl>this.form.get(dir.path);
}
/** /**
* @description * @description
@ -248,7 +259,9 @@ export class NgForm extends ControlContainer implements Form,
* *
* @param dir The `NgModelGroup` directive instance. * @param dir The `NgModelGroup` directive instance.
*/ */
getFormGroup(dir: NgModelGroup): FormGroup { return <FormGroup>this.form.get(dir.path); } getFormGroup(dir: NgModelGroup): FormGroup {
return <FormGroup>this.form.get(dir.path);
}
/** /**
* Sets the new value for the provided `NgControl` directive. * Sets the new value for the provided `NgControl` directive.
@ -269,7 +282,9 @@ export class NgForm extends ControlContainer implements Form,
* *
* @param value The new value * @param value The new value
*/ */
setValue(value: {[key: string]: any}): void { this.control.setValue(value); } setValue(value: {[key: string]: any}): void {
this.control.setValue(value);
}
/** /**
* @description * @description
@ -289,7 +304,9 @@ export class NgForm extends ControlContainer implements Form,
* @description * @description
* Method called when the "reset" event is triggered on the form. * Method called when the "reset" event is triggered on the form.
*/ */
onReset(): void { this.resetForm(); } onReset(): void {
this.resetForm();
}
/** /**
* @description * @description

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, EventEmitter, Host, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges, forwardRef} from '@angular/core'; import {Directive, EventEmitter, forwardRef, Host, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges} from '@angular/core';
import {FormControl, FormHooks} from '../model'; import {FormControl, FormHooks} from '../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators'; import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
@ -65,10 +65,10 @@ const resolvedPromise = (() => Promise.resolve(null))();
* the domain model in your class. * the domain model in your class.
* *
* To inspect the properties of the associated `FormControl` (like validity state), * To inspect the properties of the associated `FormControl` (like validity state),
* export the directive into a local template variable using `ngModel` as the key (ex: `#myVar="ngModel"`). * export the directive into a local template variable using `ngModel` as the key (ex:
* You then access the control using the directive's `control` property, * `#myVar="ngModel"`). You then access the control using the directive's `control` property, but
* but most properties used (like `valid` and `dirty`) fall through to the control anyway for direct access. * most properties used (like `valid` and `dirty`) fall through to the control anyway for direct
* See a full list of properties directly available in `AbstractControlDirective`. * access. See a full list of properties directly available in `AbstractControlDirective`.
* *
* @see `RadioControlValueAccessor` * @see `RadioControlValueAccessor`
* @see `SelectControlValueAccessor` * @see `SelectControlValueAccessor`
@ -114,8 +114,8 @@ const resolvedPromise = (() => Promise.resolve(null))();
* *
* ### Setting the ngModel name attribute through options * ### Setting the ngModel name attribute through options
* *
* The following example shows you an alternate way to set the name attribute. The name attribute is used * The following example shows you an alternate way to set the name attribute. The name attribute is
* within a custom form component, and the name `@Input` property serves a different purpose. * used within a custom form component, and the name `@Input` property serves a different purpose.
* *
* ```html * ```html
* <form> * <form>
@ -133,8 +133,7 @@ const resolvedPromise = (() => Promise.resolve(null))();
providers: [formControlBinding], providers: [formControlBinding],
exportAs: 'ngModel' exportAs: 'ngModel'
}) })
export class NgModel extends NgControl implements OnChanges, export class NgModel extends NgControl implements OnChanges, OnDestroy {
OnDestroy {
public readonly control: FormControl = new FormControl(); public readonly control: FormControl = new FormControl();
// At runtime we coerce arbitrary values assigned to the "disabled" input to a "boolean". // At runtime we coerce arbitrary values assigned to the "disabled" input to a "boolean".
@ -192,8 +191,7 @@ export class NgModel extends NgControl implements OnChanges,
* *
*/ */
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input('ngModelOptions') @Input('ngModelOptions') options!: {name?: string, standalone?: boolean, updateOn?: FormHooks};
options !: {name?: string, standalone?: boolean, updateOn?: FormHooks};
/** /**
* @description * @description
@ -202,11 +200,12 @@ export class NgModel extends NgControl implements OnChanges,
*/ */
@Output('ngModelChange') update = new EventEmitter(); @Output('ngModelChange') update = new EventEmitter();
constructor(@Optional() @Host() parent: ControlContainer, constructor(
@Optional() @Host() parent: ControlContainer,
@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>, @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<AsyncValidator|AsyncValidatorFn>, @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators:
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) Array<AsyncValidator|AsyncValidatorFn>,
valueAccessors: ControlValueAccessor[]) { @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
super(); super();
this._parent = parent; this._parent = parent;
this._rawValidators = validators || []; this._rawValidators = validators || [];
@ -239,7 +238,9 @@ export class NgModel extends NgControl implements OnChanges,
* Lifecycle method called before the directive's instance is destroyed. For internal * Lifecycle method called before the directive's instance is destroyed. For internal
* use only. * use only.
*/ */
ngOnDestroy(): void { this.formDirective && this.formDirective.removeControl(this); } ngOnDestroy(): void {
this.formDirective && this.formDirective.removeControl(this);
}
/** /**
* @description * @description
@ -254,14 +255,18 @@ export class NgModel extends NgControl implements OnChanges,
* @description * @description
* The top-level directive for this control if present, otherwise null. * The top-level directive for this control if present, otherwise null.
*/ */
get formDirective(): any { return this._parent ? this._parent.formDirective : null; } get formDirective(): any {
return this._parent ? this._parent.formDirective : null;
}
/** /**
* @description * @description
* Synchronous validator function composed of all the synchronous validators * Synchronous validator function composed of all the synchronous validators
* registered with this directive. * registered with this directive.
*/ */
get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } get validator(): ValidatorFn|null {
return composeValidators(this._rawValidators);
}
/** /**
* @description * @description
@ -285,8 +290,7 @@ export class NgModel extends NgControl implements OnChanges,
private _setUpControl(): void { private _setUpControl(): void {
this._setUpdateStrategy(); this._setUpdateStrategy();
this._isStandalone() ? this._setUpStandalone() : this._isStandalone() ? this._setUpStandalone() : this.formDirective.addControl(this);
this.formDirective.addControl(this);
this._registered = true; this._registered = true;
} }
@ -316,8 +320,7 @@ export class NgModel extends NgControl implements OnChanges,
if (!(this._parent instanceof NgModelGroup) && if (!(this._parent instanceof NgModelGroup) &&
this._parent instanceof AbstractFormGroupDirective) { this._parent instanceof AbstractFormGroupDirective) {
TemplateDrivenErrors.formGroupNameException(); TemplateDrivenErrors.formGroupNameException();
} else if ( } else if (!(this._parent instanceof NgModelGroup) && !(this._parent instanceof NgForm)) {
!(this._parent instanceof NgModelGroup) && !(this._parent instanceof NgForm)) {
TemplateDrivenErrors.modelParentException(); TemplateDrivenErrors.modelParentException();
} }
} }
@ -331,15 +334,15 @@ export class NgModel extends NgControl implements OnChanges,
} }
private _updateValue(value: any): void { private _updateValue(value: any): void {
resolvedPromise.then( resolvedPromise.then(() => {
() => { this.control.setValue(value, {emitViewToModelChange: false}); }); this.control.setValue(value, {emitViewToModelChange: false});
});
} }
private _updateDisabled(changes: SimpleChanges) { private _updateDisabled(changes: SimpleChanges) {
const disabledValue = changes['isDisabled'].currentValue; const disabledValue = changes['isDisabled'].currentValue;
const isDisabled = const isDisabled = disabledValue === '' || (disabledValue && disabledValue !== 'false');
disabledValue === '' || (disabledValue && disabledValue !== 'false');
resolvedPromise.then(() => { resolvedPromise.then(() => {
if (isDisabled && !this.control.disabled) { if (isDisabled && !this.control.disabled) {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, Host, Inject, Input, OnDestroy, OnInit, Optional, Self, SkipSelf, forwardRef} from '@angular/core'; import {Directive, forwardRef, Host, Inject, Input, OnDestroy, OnInit, Optional, Self, SkipSelf} from '@angular/core';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators'; import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';

View File

@ -17,8 +17,8 @@ export function normalizeValidator(validator: ValidatorFn | Validator): Validato
} }
} }
export function normalizeAsyncValidator(validator: AsyncValidatorFn | AsyncValidator): export function normalizeAsyncValidator(validator: AsyncValidatorFn|
AsyncValidatorFn { AsyncValidator): AsyncValidatorFn {
if ((<AsyncValidator>validator).validate) { if ((<AsyncValidator>validator).validate) {
return (c: AbstractControl) => (<AsyncValidator>validator).validate(c); return (c: AbstractControl) => (<AsyncValidator>validator).validate(c);
} else { } else {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Renderer2, forwardRef} from '@angular/core'; import {Directive, ElementRef, forwardRef, Renderer2} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
@ -84,7 +84,9 @@ export class NumberValueAccessor implements ControlValueAccessor {
* @param fn The callback function * @param fn The callback function
*/ */
registerOnChange(fn: (_: number|null) => void): void { registerOnChange(fn: (_: number|null) => void): void {
this.onChange = (value) => { fn(value == '' ? null : parseFloat(value)); }; this.onChange = (value) => {
fn(value == '' ? null : parseFloat(value));
};
} }
/** /**
@ -93,7 +95,9 @@ export class NumberValueAccessor implements ControlValueAccessor {
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnTouched(fn: () => void): void { this.onTouched = fn; } registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
/** /**
* Sets the "disabled" property on the input element. * Sets the "disabled" property on the input element.

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Injectable, Injector, Input, OnDestroy, OnInit, Renderer2, forwardRef} from '@angular/core'; import {Directive, ElementRef, forwardRef, Injectable, Injector, Input, OnDestroy, OnInit, Renderer2} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {NgControl} from './ng_control'; import {NgControl} from './ng_control';
@ -93,8 +93,7 @@ export class RadioControlRegistry {
host: {'(change)': 'onChange()', '(blur)': 'onTouched()'}, host: {'(change)': 'onChange()', '(blur)': 'onTouched()'},
providers: [RADIO_VALUE_ACCESSOR] providers: [RADIO_VALUE_ACCESSOR]
}) })
export class RadioControlValueAccessor implements ControlValueAccessor, export class RadioControlValueAccessor implements ControlValueAccessor, OnDestroy, OnInit {
OnDestroy, OnInit {
/** @internal */ /** @internal */
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
_state!: boolean; _state!: boolean;
@ -156,7 +155,9 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
* @description * @description
* Lifecycle method called before the directive's instance is destroyed. For internal use only. * Lifecycle method called before the directive's instance is destroyed. For internal use only.
*/ */
ngOnDestroy(): void { this._registry.remove(this); } ngOnDestroy(): void {
this._registry.remove(this);
}
/** /**
* @description * @description
@ -188,7 +189,9 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
* *
* @param value * @param value
*/ */
fireUncheck(value: any): void { this.writeValue(value); } fireUncheck(value: any): void {
this.writeValue(value);
}
/** /**
* @description * @description
@ -196,7 +199,9 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnTouched(fn: () => {}): void { this.onTouched = fn; } registerOnTouched(fn: () => {}): void {
this.onTouched = fn;
}
/** /**
* Sets the "disabled" property on the input element. * Sets the "disabled" property on the input element.

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Renderer2, StaticProvider, forwardRef} from '@angular/core'; import {Directive, ElementRef, forwardRef, Renderer2, StaticProvider} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
@ -82,7 +82,9 @@ export class RangeValueAccessor implements ControlValueAccessor {
* @param fn The callback function * @param fn The callback function
*/ */
registerOnChange(fn: (_: number|null) => void): void { registerOnChange(fn: (_: number|null) => void): void {
this.onChange = (value) => { fn(value == '' ? null : parseFloat(value)); }; this.onChange = (value) => {
fn(value == '' ? null : parseFloat(value));
};
} }
/** /**
@ -91,7 +93,9 @@ export class RangeValueAccessor implements ControlValueAccessor {
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnTouched(fn: () => void): void { this.onTouched = fn; } registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
/** /**
* Sets the "disabled" property on the range input element. * Sets the "disabled" property on the range input element.

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, EventEmitter, Inject, InjectionToken, Input, OnChanges, Optional, Output, Self, SimpleChanges, forwardRef} from '@angular/core'; import {Directive, EventEmitter, forwardRef, Inject, InjectionToken, Input, OnChanges, Optional, Output, Self, SimpleChanges} from '@angular/core';
import {FormControl} from '../../model'; import {FormControl} from '../../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators'; import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
@ -136,7 +136,9 @@ export class FormControlDirective extends NgControl implements OnChanges {
* Triggers a warning that this input should not be used with reactive forms. * Triggers a warning that this input should not be used with reactive forms.
*/ */
@Input('disabled') @Input('disabled')
set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); } set isDisabled(isDisabled: boolean) {
ReactiveErrors.disabledAttrWarning();
}
// TODO(kara): remove next 4 properties once deprecation period is over // TODO(kara): remove next 4 properties once deprecation period is over
@ -164,11 +166,13 @@ export class FormControlDirective extends NgControl implements OnChanges {
*/ */
_ngModelWarningSent = false; _ngModelWarningSent = false;
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>, constructor(
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<AsyncValidator|AsyncValidatorFn>, @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators:
valueAccessors: ControlValueAccessor[], Array<AsyncValidator|AsyncValidatorFn>,
@Optional() @Inject(NG_MODEL_WITH_FORM_CONTROL_WARNING) private _ngModelWarningConfig: string|null) { @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[],
@Optional() @Inject(NG_MODEL_WITH_FORM_CONTROL_WARNING) private _ngModelWarningConfig: string|
null) {
super(); super();
this._rawValidators = validators || []; this._rawValidators = validators || [];
this._rawAsyncValidators = asyncValidators || []; this._rawAsyncValidators = asyncValidators || [];
@ -191,8 +195,7 @@ export class FormControlDirective extends NgControl implements OnChanges {
this.form.updateValueAndValidity({emitEvent: false}); this.form.updateValueAndValidity({emitEvent: false});
} }
if (isPropertyUpdated(changes, this.viewModel)) { if (isPropertyUpdated(changes, this.viewModel)) {
_ngModelWarning( _ngModelWarning('formControl', FormControlDirective, this, this._ngModelWarningConfig);
'formControl', FormControlDirective, this, this._ngModelWarningConfig);
this.form.setValue(this.model); this.form.setValue(this.model);
this.viewModel = this.model; this.viewModel = this.model;
} }
@ -203,14 +206,18 @@ export class FormControlDirective extends NgControl implements OnChanges {
* Returns an array that represents the path from the top-level form to this control. * Returns an array that represents the path from the top-level form to this control.
* Each index is the string name of the control on that level. * Each index is the string name of the control on that level.
*/ */
get path(): string[] { return []; } get path(): string[] {
return [];
}
/** /**
* @description * @description
* Synchronous validator function composed of all the synchronous validators * Synchronous validator function composed of all the synchronous validators
* registered with this directive. * registered with this directive.
*/ */
get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } get validator(): ValidatorFn|null {
return composeValidators(this._rawValidators);
}
/** /**
* @description * @description
@ -225,7 +232,9 @@ export class FormControlDirective extends NgControl implements OnChanges {
* @description * @description
* The `FormControl` bound to this directive. * The `FormControl` bound to this directive.
*/ */
get control(): FormControl { return this.form; } get control(): FormControl {
return this.form;
}
/** /**
* @description * @description

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, EventEmitter, Host, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges, SkipSelf, forwardRef} from '@angular/core'; import {Directive, EventEmitter, forwardRef, Host, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges, SkipSelf} from '@angular/core';
import {FormControl} from '../../model'; import {FormControl} from '../../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators'; import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
@ -159,7 +159,9 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
* Triggers a warning that this input should not be used with reactive forms. * Triggers a warning that this input should not be used with reactive forms.
*/ */
@Input('disabled') @Input('disabled')
set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); } set isDisabled(isDisabled: boolean) {
ReactiveErrors.disabledAttrWarning();
}
// TODO(kara): remove next 4 properties once deprecation period is over // TODO(kara): remove next 4 properties once deprecation period is over
@ -251,14 +253,18 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
* @description * @description
* The top-level directive for this group if present, otherwise null. * The top-level directive for this group if present, otherwise null.
*/ */
get formDirective(): any { return this._parent ? this._parent.formDirective : null; } get formDirective(): any {
return this._parent ? this._parent.formDirective : null;
}
/** /**
* @description * @description
* Synchronous validator function composed of all the synchronous validators * Synchronous validator function composed of all the synchronous validators
* registered with this directive. * registered with this directive.
*/ */
get validator(): ValidatorFn|null { return composeValidators(this._rawValidators); } get validator(): ValidatorFn|null {
return composeValidators(this._rawValidators);
}
/** /**
* @description * @description

View File

@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, EventEmitter, Inject, Input, OnChanges, Optional, Output, Self, SimpleChanges, forwardRef} from '@angular/core'; import {Directive, EventEmitter, forwardRef, Inject, Input, OnChanges, Optional, Output, Self, SimpleChanges} from '@angular/core';
import {FormArray, FormControl, FormGroup} from '../../model'; import {FormArray, FormControl, FormGroup} from '../../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from '../../validators'; import {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from '../../validators';
import {ControlContainer} from '../control_container'; import {ControlContainer} from '../control_container';
@ -51,8 +52,7 @@ export const formDirectiveProvider: any = {
host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'}, host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
exportAs: 'ngForm' exportAs: 'ngForm'
}) })
export class FormGroupDirective extends ControlContainer implements Form, export class FormGroupDirective extends ControlContainer implements Form, OnChanges {
OnChanges {
/** /**
* @description * @description
* Reports whether the form submission has been triggered. * Reports whether the form submission has been triggered.
@ -105,20 +105,26 @@ export class FormGroupDirective extends ControlContainer implements Form,
* @description * @description
* Returns this directive's instance. * Returns this directive's instance.
*/ */
get formDirective(): Form { return this; } get formDirective(): Form {
return this;
}
/** /**
* @description * @description
* Returns the `FormGroup` bound to this directive. * Returns the `FormGroup` bound to this directive.
*/ */
get control(): FormGroup { return this.form; } get control(): FormGroup {
return this.form;
}
/** /**
* @description * @description
* Returns an array representing the path to this group. Because this directive * Returns an array representing the path to this group. Because this directive
* always lives at the top level of a form, it always an empty array. * always lives at the top level of a form, it always an empty array.
*/ */
get path(): string[] { return []; } get path(): string[] {
return [];
}
/** /**
* @description * @description
@ -141,7 +147,9 @@ export class FormGroupDirective extends ControlContainer implements Form,
* *
* @param dir The `FormControlName` directive instance. * @param dir The `FormControlName` directive instance.
*/ */
getControl(dir: FormControlName): FormControl { return <FormControl>this.form.get(dir.path); } getControl(dir: FormControlName): FormControl {
return <FormControl>this.form.get(dir.path);
}
/** /**
* @description * @description
@ -149,7 +157,9 @@ export class FormGroupDirective extends ControlContainer implements Form,
* *
* @param dir The `FormControlName` directive instance. * @param dir The `FormControlName` directive instance.
*/ */
removeControl(dir: FormControlName): void { removeDir<FormControlName>(this.directives, dir); } removeControl(dir: FormControlName): void {
removeDir<FormControlName>(this.directives, dir);
}
/** /**
* Adds a new `FormGroupName` directive instance to the form. * Adds a new `FormGroupName` directive instance to the form.
@ -175,7 +185,9 @@ export class FormGroupDirective extends ControlContainer implements Form,
* *
* @param dir The `FormGroupName` directive instance. * @param dir The `FormGroupName` directive instance.
*/ */
getFormGroup(dir: FormGroupName): FormGroup { return <FormGroup>this.form.get(dir.path); } getFormGroup(dir: FormGroupName): FormGroup {
return <FormGroup>this.form.get(dir.path);
}
/** /**
* Adds a new `FormArrayName` directive instance to the form. * Adds a new `FormArrayName` directive instance to the form.
@ -201,7 +213,9 @@ export class FormGroupDirective extends ControlContainer implements Form,
* *
* @param dir The `FormArrayName` directive instance. * @param dir The `FormArrayName` directive instance.
*/ */
getFormArray(dir: FormArrayName): FormArray { return <FormArray>this.form.get(dir.path); } getFormArray(dir: FormArrayName): FormArray {
return <FormArray>this.form.get(dir.path);
}
/** /**
* Sets the new value for the provided `FormControlName` directive. * Sets the new value for the provided `FormControlName` directive.
@ -232,7 +246,9 @@ export class FormGroupDirective extends ControlContainer implements Form,
* @description * @description
* Method called when the "reset" event is triggered on the form. * Method called when the "reset" event is triggered on the form.
*/ */
onReset(): void { this.resetForm(); } onReset(): void {
this.resetForm();
}
/** /**
* @description * @description

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, Host, Inject, Input, OnDestroy, OnInit, Optional, Self, SkipSelf, forwardRef} from '@angular/core'; import {Directive, forwardRef, Host, Inject, Input, OnDestroy, OnInit, Optional, Self, SkipSelf} from '@angular/core';
import {FormArray} from '../../model'; import {FormArray} from '../../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators'; import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
@ -189,7 +189,9 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy
* @description * @description
* The `FormArray` bound to this directive. * The `FormArray` bound to this directive.
*/ */
get control(): FormArray { return this.formDirective !.getFormArray(this); } get control(): FormArray {
return this.formDirective!.getFormArray(this);
}
/** /**
* @description * @description
@ -213,7 +215,9 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy
* Synchronous validator function composed of all the synchronous validators registered with this * Synchronous validator function composed of all the synchronous validators registered with this
* directive. * directive.
*/ */
get validator(): ValidatorFn|null { return composeValidators(this._validators); } get validator(): ValidatorFn|null {
return composeValidators(this._validators);
}
/** /**
* @description * @description

View File

@ -83,8 +83,9 @@ export class ReactiveErrors {
in Angular v7. in Angular v7.
For more information on this, see our API docs here: For more information on this, see our API docs here:
https://angular.io/api/forms/${directiveName === 'formControl' ? 'FormControlDirective' https://angular.io/api/forms/${
: 'FormControlName'}#use-with-ngmodel directiveName === 'formControl' ? 'FormControlDirective' :
'FormControlName'}#use-with-ngmodel
`); `);
} }
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer2, StaticProvider, forwardRef, ɵlooseIdentical as looseIdentical} from '@angular/core'; import {Directive, ElementRef, forwardRef, Host, Input, OnDestroy, Optional, Renderer2, StaticProvider, ɵlooseIdentical as looseIdentical} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
@ -160,7 +160,9 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnTouched(fn: () => any): void { this.onTouched = fn; } registerOnTouched(fn: () => any): void {
this.onTouched = fn;
}
/** /**
* Sets the "disabled" property on the select input element. * Sets the "disabled" property on the select input element.
@ -172,7 +174,9 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
} }
/** @internal */ /** @internal */
_registerOption(): string { return (this._idCounter++).toString(); } _registerOption(): string {
return (this._idCounter++).toString();
}
/** @internal */ /** @internal */
_getOptionId(value: any): string|null { _getOptionId(value: any): string|null {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer2, StaticProvider, forwardRef, ɵlooseIdentical as looseIdentical} from '@angular/core'; import {Directive, ElementRef, forwardRef, Host, Input, OnDestroy, Optional, Renderer2, StaticProvider, ɵlooseIdentical as looseIdentical} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
@ -42,9 +42,9 @@ abstract class HTMLCollection {
/** /**
* @description * @description
* The `ControlValueAccessor` for writing multi-select control values and listening to multi-select control * The `ControlValueAccessor` for writing multi-select control values and listening to multi-select
* changes. The value accessor is used by the `FormControlDirective`, `FormControlName`, and `NgModel` * control changes. The value accessor is used by the `FormControlDirective`, `FormControlName`, and
* directives. * `NgModel` directives.
* *
* @see `SelectControlValueAccessor` * @see `SelectControlValueAccessor`
* *
@ -135,9 +135,13 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
if (Array.isArray(value)) { if (Array.isArray(value)) {
// convert values to ids // convert values to ids
const ids = value.map((v) => this._getOptionId(v)); const ids = value.map((v) => this._getOptionId(v));
optionSelectedStateSetter = (opt, o) => { opt._setSelected(ids.indexOf(o.toString()) > -1); }; optionSelectedStateSetter = (opt, o) => {
opt._setSelected(ids.indexOf(o.toString()) > -1);
};
} else { } else {
optionSelectedStateSetter = (opt, o) => { opt._setSelected(false); }; optionSelectedStateSetter = (opt, o) => {
opt._setSelected(false);
};
} }
this._optionMap.forEach(optionSelectedStateSetter); this._optionMap.forEach(optionSelectedStateSetter);
} }
@ -182,7 +186,9 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnTouched(fn: () => any): void { this.onTouched = fn; } registerOnTouched(fn: () => any): void {
this.onTouched = fn;
}
/** /**
* Sets the "disabled" property on the select input element. * Sets the "disabled" property on the select input element.

View File

@ -46,8 +46,9 @@ export function setUpControl(control: FormControl, dir: NgControl): void {
setUpBlurPipeline(control, dir); setUpBlurPipeline(control, dir);
if (dir.valueAccessor!.setDisabledState) { if (dir.valueAccessor!.setDisabledState) {
control.registerOnDisabledChange( control.registerOnDisabledChange((isDisabled: boolean) => {
(isDisabled: boolean) => { dir.valueAccessor !.setDisabledState !(isDisabled); }); dir.valueAccessor!.setDisabledState!(isDisabled);
});
} }
// re-run validation when validator binding changes, e.g. minlength=3 -> minlength=4 // re-run validation when validator binding changes, e.g. minlength=3 -> minlength=4

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, Input, OnChanges, SimpleChanges, StaticProvider, forwardRef} from '@angular/core'; import {Directive, forwardRef, Input, OnChanges, SimpleChanges, StaticProvider} from '@angular/core';
import {Observable} from 'rxjs'; import {Observable} from 'rxjs';
import {AbstractControl} from '../model'; import {AbstractControl} from '../model';
@ -168,7 +168,9 @@ export class RequiredValidator implements Validator {
* Tracks changes to the required attribute bound to this directive. * Tracks changes to the required attribute bound to this directive.
*/ */
@Input() @Input()
get required(): boolean|string { return this._required; } get required(): boolean|string {
return this._required;
}
set required(value: boolean|string) { set required(value: boolean|string) {
this._required = value != null && value !== false && `${value}` !== 'false'; this._required = value != null && value !== false && `${value}` !== 'false';
@ -190,7 +192,9 @@ export class RequiredValidator implements Validator {
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; } registerOnValidatorChange(fn: () => void): void {
this._onChange = fn;
}
} }
@ -204,7 +208,8 @@ export class RequiredValidator implements Validator {
* *
* ### Adding a required checkbox validator using template-driven forms * ### Adding a required checkbox validator using template-driven forms
* *
* The following example shows how to add a checkbox required validator to an input attached to an ngModel binding. * The following example shows how to add a checkbox required validator to an input attached to an
* ngModel binding.
* *
* ``` * ```
* <input type="checkbox" name="active" ngModel required> * <input type="checkbox" name="active" ngModel required>
@ -251,7 +256,8 @@ export const EMAIL_VALIDATOR: any = {
* *
* ### Adding an email validator * ### Adding an email validator
* *
* The following example shows how to add an email validator to an input attached to an ngModel binding. * The following example shows how to add an email validator to an input attached to an ngModel
* binding.
* *
* ``` * ```
* <input type="email" name="email" ngModel email> * <input type="email" name="email" ngModel email>
@ -298,7 +304,9 @@ export class EmailValidator implements Validator {
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; } registerOnValidatorChange(fn: () => void): void {
this._onChange = fn;
}
} }
/** /**
@ -308,7 +316,9 @@ export class EmailValidator implements Validator {
* *
* @publicApi * @publicApi
*/ */
export interface ValidatorFn { (control: AbstractControl): ValidationErrors|null; } export interface ValidatorFn {
(control: AbstractControl): ValidationErrors|null;
}
/** /**
* @description * @description
@ -357,8 +367,7 @@ export const MIN_LENGTH_VALIDATOR: any = {
providers: [MIN_LENGTH_VALIDATOR], providers: [MIN_LENGTH_VALIDATOR],
host: {'[attr.minlength]': 'minlength ? minlength : null'} host: {'[attr.minlength]': 'minlength ? minlength : null'}
}) })
export class MinLengthValidator implements Validator, export class MinLengthValidator implements Validator, OnChanges {
OnChanges {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
private _validator!: ValidatorFn; private _validator!: ValidatorFn;
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@ -400,7 +409,9 @@ export class MinLengthValidator implements Validator,
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; } registerOnValidatorChange(fn: () => void): void {
this._onChange = fn;
}
private _createValidator(): void { private _createValidator(): void {
this._validator = Validators.minLength( this._validator = Validators.minLength(
@ -444,8 +455,7 @@ export const MAX_LENGTH_VALIDATOR: any = {
providers: [MAX_LENGTH_VALIDATOR], providers: [MAX_LENGTH_VALIDATOR],
host: {'[attr.maxlength]': 'maxlength ? maxlength : null'} host: {'[attr.maxlength]': 'maxlength ? maxlength : null'}
}) })
export class MaxLengthValidator implements Validator, export class MaxLengthValidator implements Validator, OnChanges {
OnChanges {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
private _validator!: ValidatorFn; private _validator!: ValidatorFn;
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@ -487,7 +497,9 @@ export class MaxLengthValidator implements Validator,
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; } registerOnValidatorChange(fn: () => void): void {
this._onChange = fn;
}
private _createValidator(): void { private _createValidator(): void {
this._validator = Validators.maxLength( this._validator = Validators.maxLength(
@ -534,8 +546,7 @@ export const PATTERN_VALIDATOR: any = {
providers: [PATTERN_VALIDATOR], providers: [PATTERN_VALIDATOR],
host: {'[attr.pattern]': 'pattern ? pattern : null'} host: {'[attr.pattern]': 'pattern ? pattern : null'}
}) })
export class PatternValidator implements Validator, export class PatternValidator implements Validator, OnChanges {
OnChanges {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
private _validator!: ValidatorFn; private _validator!: ValidatorFn;
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@ -567,7 +578,9 @@ export class PatternValidator implements Validator,
* Method that validates whether the value matches the * Method that validates whether the value matches the
* the pattern requirement. * the pattern requirement.
*/ */
validate(control: AbstractControl): ValidationErrors|null { return this._validator(control); } validate(control: AbstractControl): ValidationErrors|null {
return this._validator(control);
}
/** /**
* @description * @description
@ -575,7 +588,11 @@ export class PatternValidator implements Validator,
* *
* @param fn The callback function * @param fn The callback function
*/ */
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; } registerOnValidatorChange(fn: () => void): void {
this._onChange = fn;
private _createValidator(): void { this._validator = Validators.pattern(this.pattern); } }
private _createValidator(): void {
this._validator = Validators.pattern(this.pattern);
}
} }

View File

@ -11,8 +11,8 @@ import {Injectable} from '@angular/core';
import {AsyncValidatorFn, ValidatorFn} from './directives/validators'; import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
import {AbstractControl, AbstractControlOptions, FormArray, FormControl, FormGroup, FormHooks} from './model'; import {AbstractControl, AbstractControlOptions, FormArray, FormControl, FormGroup, FormHooks} from './model';
function isAbstractControlOptions(options: AbstractControlOptions | {[key: string]: any}): function isAbstractControlOptions(options: AbstractControlOptions|
options is AbstractControlOptions { {[key: string]: any}): options is AbstractControlOptions {
return (<AbstractControlOptions>options).asyncValidators !== undefined || return (<AbstractControlOptions>options).asyncValidators !== undefined ||
(<AbstractControlOptions>options).validators !== undefined || (<AbstractControlOptions>options).validators !== undefined ||
(<AbstractControlOptions>options).updateOn !== undefined; (<AbstractControlOptions>options).updateOn !== undefined;

View File

@ -56,10 +56,9 @@ export class ReactiveFormsModule {
}): ModuleWithProviders<ReactiveFormsModule> { }): ModuleWithProviders<ReactiveFormsModule> {
return { return {
ngModule: ReactiveFormsModule, ngModule: ReactiveFormsModule,
providers: [{ providers: [
provide: NG_MODEL_WITH_FORM_CONTROL_WARNING, {provide: NG_MODEL_WITH_FORM_CONTROL_WARNING, useValue: opts.warnOnNgModelWithFormControl}
useValue: opts.warnOnNgModelWithFormControl ]
}]
}; };
} }
} }

View File

@ -69,9 +69,8 @@ function _find(control: AbstractControl, path: Array<string|number>| string, del
return controlToFind; return controlToFind;
} }
function coerceToValidator( function coerceToValidator(validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|
validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null): ValidatorFn| null): ValidatorFn|null {
null {
const validator = const validator =
(isOptionsObj(validatorOrOpts) ? (validatorOrOpts as AbstractControlOptions).validators : (isOptionsObj(validatorOrOpts) ? (validatorOrOpts as AbstractControlOptions).validators :
validatorOrOpts) as ValidatorFn | validatorOrOpts) as ValidatorFn |
@ -81,8 +80,9 @@ function coerceToValidator(
} }
function coerceToAsyncValidator( function coerceToAsyncValidator(
asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null, validatorOrOpts?: ValidatorFn | asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null,
ValidatorFn[] | AbstractControlOptions | null): AsyncValidatorFn|null { validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null): AsyncValidatorFn|
null {
const origAsyncValidator = const origAsyncValidator =
(isOptionsObj(validatorOrOpts) ? (validatorOrOpts as AbstractControlOptions).asyncValidators : (isOptionsObj(validatorOrOpts) ? (validatorOrOpts as AbstractControlOptions).asyncValidators :
asyncValidator) as AsyncValidatorFn | asyncValidator) as AsyncValidatorFn |
@ -118,8 +118,8 @@ export interface AbstractControlOptions {
} }
function isOptionsObj( function isOptionsObj(validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|
validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null): boolean { null): boolean {
return validatorOrOpts != null && !Array.isArray(validatorOrOpts) && return validatorOrOpts != null && !Array.isArray(validatorOrOpts) &&
typeof validatorOrOpts === 'object'; typeof validatorOrOpts === 'object';
} }
@ -184,7 +184,9 @@ export abstract class AbstractControl {
/** /**
* The parent control. * The parent control.
*/ */
get parent(): FormGroup|FormArray { return this._parent; } get parent(): FormGroup|FormArray {
return this._parent;
}
/** /**
* The validation status of the control. There are four possible * The validation status of the control. There are four possible
@ -209,7 +211,9 @@ export abstract class AbstractControl {
* @returns True if the control has passed all of its validation tests, * @returns True if the control has passed all of its validation tests,
* false otherwise. * false otherwise.
*/ */
get valid(): boolean { return this.status === VALID; } get valid(): boolean {
return this.status === VALID;
}
/** /**
* A control is `invalid` when its `status` is `INVALID`. * A control is `invalid` when its `status` is `INVALID`.
@ -219,7 +223,9 @@ export abstract class AbstractControl {
* @returns True if this control has failed one or more of its validation checks, * @returns True if this control has failed one or more of its validation checks,
* false otherwise. * false otherwise.
*/ */
get invalid(): boolean { return this.status === INVALID; } get invalid(): boolean {
return this.status === INVALID;
}
/** /**
* A control is `pending` when its `status` is `PENDING`. * A control is `pending` when its `status` is `PENDING`.
@ -229,7 +235,9 @@ export abstract class AbstractControl {
* @returns True if this control is in the process of conducting a validation check, * @returns True if this control is in the process of conducting a validation check,
* false otherwise. * false otherwise.
*/ */
get pending(): boolean { return this.status == PENDING; } get pending(): boolean {
return this.status == PENDING;
}
/** /**
* A control is `disabled` when its `status` is `DISABLED`. * A control is `disabled` when its `status` is `DISABLED`.
@ -242,7 +250,9 @@ export abstract class AbstractControl {
* *
* @returns True if the control is disabled, false otherwise. * @returns True if the control is disabled, false otherwise.
*/ */
get disabled(): boolean { return this.status === DISABLED; } get disabled(): boolean {
return this.status === DISABLED;
}
/** /**
* A control is `enabled` as long as its `status` is not `DISABLED`. * A control is `enabled` as long as its `status` is not `DISABLED`.
@ -253,7 +263,9 @@ export abstract class AbstractControl {
* @see {@link AbstractControl.status} * @see {@link AbstractControl.status}
* *
*/ */
get enabled(): boolean { return this.status !== DISABLED; } get enabled(): boolean {
return this.status !== DISABLED;
}
/** /**
* An object containing any errors generated by failing validation, * An object containing any errors generated by failing validation,
@ -278,7 +290,9 @@ export abstract class AbstractControl {
* @returns True if the user has changed the value of this control in the UI; compare `pristine`. * @returns True if the user has changed the value of this control in the UI; compare `pristine`.
* Programmatic changes to a control's value do not mark it dirty. * Programmatic changes to a control's value do not mark it dirty.
*/ */
get dirty(): boolean { return !this.pristine; } get dirty(): boolean {
return !this.pristine;
}
/** /**
* True if the control is marked as `touched`. * True if the control is marked as `touched`.
@ -294,7 +308,9 @@ export abstract class AbstractControl {
* A control is `untouched` if the user has not yet triggered * A control is `untouched` if the user has not yet triggered
* a `blur` event on it. * a `blur` event on it.
*/ */
get untouched(): boolean { return !this.touched; } get untouched(): boolean {
return !this.touched;
}
/** /**
* A multicasting observable that emits an event every time the value of the control changes, in * A multicasting observable that emits an event every time the value of the control changes, in
@ -355,7 +371,9 @@ export abstract class AbstractControl {
* `updateValueAndValidity()` for the new validation to take effect. * `updateValueAndValidity()` for the new validation to take effect.
* *
*/ */
clearValidators(): void { this.validator = null; } clearValidators(): void {
this.validator = null;
}
/** /**
* Empties out the async validator list. * Empties out the async validator list.
@ -364,7 +382,9 @@ export abstract class AbstractControl {
* `updateValueAndValidity()` for the new validation to take effect. * `updateValueAndValidity()` for the new validation to take effect.
* *
*/ */
clearAsyncValidators(): void { this.asyncValidator = null; } clearAsyncValidators(): void {
this.asyncValidator = null;
}
/** /**
* Marks the control as `touched`. A control is touched by focus and * Marks the control as `touched`. A control is touched by focus and
@ -416,8 +436,9 @@ export abstract class AbstractControl {
(this as {touched: boolean}).touched = false; (this as {touched: boolean}).touched = false;
this._pendingTouched = false; this._pendingTouched = false;
this._forEachChild( this._forEachChild((control: AbstractControl) => {
(control: AbstractControl) => { control.markAsUntouched({onlySelf: true}); }); control.markAsUntouched({onlySelf: true});
});
if (this._parent && !opts.onlySelf) { if (this._parent && !opts.onlySelf) {
this._parent._updateTouched(opts); this._parent._updateTouched(opts);
@ -465,7 +486,9 @@ export abstract class AbstractControl {
(this as {pristine: boolean}).pristine = true; (this as {pristine: boolean}).pristine = true;
this._pendingDirty = false; this._pendingDirty = false;
this._forEachChild((control: AbstractControl) => { control.markAsPristine({onlySelf: true}); }); this._forEachChild((control: AbstractControl) => {
control.markAsPristine({onlySelf: true});
});
if (this._parent && !opts.onlySelf) { if (this._parent && !opts.onlySelf) {
this._parent._updatePristine(opts); this._parent._updatePristine(opts);
@ -524,8 +547,9 @@ export abstract class AbstractControl {
(this as {status: string}).status = DISABLED; (this as {status: string}).status = DISABLED;
(this as {errors: ValidationErrors | null}).errors = null; (this as {errors: ValidationErrors | null}).errors = null;
this._forEachChild( this._forEachChild((control: AbstractControl) => {
(control: AbstractControl) => { control.disable({...opts, onlySelf: true}); }); control.disable({...opts, onlySelf: true});
});
this._updateValue(); this._updateValue();
if (opts.emitEvent !== false) { if (opts.emitEvent !== false) {
@ -561,8 +585,9 @@ export abstract class AbstractControl {
const skipPristineCheck = this._parentMarkedDirty(opts.onlySelf); const skipPristineCheck = this._parentMarkedDirty(opts.onlySelf);
(this as {status: string}).status = VALID; (this as {status: string}).status = VALID;
this._forEachChild( this._forEachChild((control: AbstractControl) => {
(control: AbstractControl) => { control.enable({...opts, onlySelf: true}); }); control.enable({...opts, onlySelf: true});
});
this.updateValueAndValidity({onlySelf: true, emitEvent: opts.emitEvent}); this.updateValueAndValidity({onlySelf: true, emitEvent: opts.emitEvent});
this._updateAncestors({...opts, skipPristineCheck}); this._updateAncestors({...opts, skipPristineCheck});
@ -583,7 +608,9 @@ export abstract class AbstractControl {
/** /**
* @param parent Sets the parent of the control * @param parent Sets the parent of the control
*/ */
setParent(parent: FormGroup|FormArray): void { this._parent = parent; } setParent(parent: FormGroup|FormArray): void {
this._parent = parent;
}
/** /**
* Sets the value of the control. Abstract method (implemented in sub-classes). * Sets the value of the control. Abstract method (implemented in sub-classes).
@ -711,7 +738,9 @@ export abstract class AbstractControl {
* *
* * `this.form.get(['person', 'name']);` * * `this.form.get(['person', 'name']);`
*/ */
get(path: Array<string|number>|string): AbstractControl|null { return _find(this, path, '.'); } get(path: Array<string|number>|string): AbstractControl|null {
return _find(this, path, '.');
}
/** /**
* @description * @description
@ -878,7 +907,9 @@ export abstract class AbstractControl {
} }
/** @internal */ /** @internal */
_registerOnCollectionChange(fn: () => void): void { this._onCollectionChange = fn; } _registerOnCollectionChange(fn: () => void): void {
this._onCollectionChange = fn;
}
/** @internal */ /** @internal */
_setUpdateStrategy(opts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null): void { _setUpdateStrategy(opts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null): void {
@ -1120,19 +1151,25 @@ export class FormControl extends AbstractControl {
/** /**
* @internal * @internal
*/ */
_anyControls(condition: Function): boolean { return false; } _anyControls(condition: Function): boolean {
return false;
}
/** /**
* @internal * @internal
*/ */
_allControlsDisabled(): boolean { return this.disabled; } _allControlsDisabled(): boolean {
return this.disabled;
}
/** /**
* Register a listener for change events. * Register a listener for change events.
* *
* @param fn The method that is called when the value changes * @param fn The method that is called when the value changes
*/ */
registerOnChange(fn: Function): void { this._onChange.push(fn); } registerOnChange(fn: Function): void {
this._onChange.push(fn);
}
/** /**
* @internal * @internal
@ -1556,7 +1593,9 @@ export class FormGroup extends AbstractControl {
} }
/** @internal */ /** @internal */
_updateValue(): void { (this as{value: any}).value = this._reduceValue(); } _updateValue(): void {
(this as {value: any}).value = this._reduceValue();
}
/** @internal */ /** @internal */
_anyControls(condition: Function): boolean { _anyControls(condition: Function): boolean {
@ -1581,8 +1620,9 @@ export class FormGroup extends AbstractControl {
/** @internal */ /** @internal */
_reduceChildren(initValue: any, fn: Function) { _reduceChildren(initValue: any, fn: Function) {
let res = initValue; let res = initValue;
this._forEachChild( this._forEachChild((control: AbstractControl, name: string) => {
(control: AbstractControl, name: string) => { res = fn(res, control, name); }); res = fn(res, control, name);
});
return res; return res;
} }
@ -1702,7 +1742,9 @@ export class FormArray extends AbstractControl {
* *
* @param index Index in the array to retrieve the control * @param index Index in the array to retrieve the control
*/ */
at(index: number): AbstractControl { return this.controls[index]; } at(index: number): AbstractControl {
return this.controls[index];
}
/** /**
* Insert a new `AbstractControl` at the end of the array. * Insert a new `AbstractControl` at the end of the array.
@ -1762,7 +1804,9 @@ export class FormArray extends AbstractControl {
/** /**
* Length of the control array. * Length of the control array.
*/ */
get length(): number { return this.controls.length; } get length(): number {
return this.controls.length;
}
/** /**
* Sets the value of the `FormArray`. It accepts an array that matches * Sets the value of the `FormArray`. It accepts an array that matches
@ -1979,7 +2023,9 @@ export class FormArray extends AbstractControl {
/** @internal */ /** @internal */
_forEachChild(cb: Function): void { _forEachChild(cb: Function): void {
this.controls.forEach((control: AbstractControl, index: number) => { cb(control, index); }); this.controls.forEach((control: AbstractControl, index: number) => {
cb(control, index);
});
} }
/** @internal */ /** @internal */

View File

@ -7,8 +7,9 @@
*/ */
import {InjectionToken, ɵisObservable as isObservable, ɵisPromise as isPromise} from '@angular/core'; import {InjectionToken, ɵisObservable as isObservable, ɵisPromise as isPromise} from '@angular/core';
import {Observable, forkJoin, from} 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 {AsyncValidatorFn, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
import {AbstractControl, FormControl} from './model'; import {AbstractControl, FormControl} from './model';
@ -19,7 +20,8 @@ function isEmptyInputValue(value: any): boolean {
/** /**
* @description * @description
* An `InjectionToken` for registering additional synchronous validators used with `AbstractControl`s. * An `InjectionToken` for registering additional synchronous validators used with
* `AbstractControl`s.
* *
* @see `NG_ASYNC_VALIDATORS` * @see `NG_ASYNC_VALIDATORS`
* *
@ -48,7 +50,8 @@ export const NG_VALIDATORS = new InjectionToken<Array<Validator|Function>>('NgVa
/** /**
* @description * @description
* An `InjectionToken` for registering additional asynchronous validators used with `AbstractControl`s. * An `InjectionToken` for registering additional asynchronous validators used with
* `AbstractControl`s.
* *
* @see `NG_VALIDATORS` * @see `NG_VALIDATORS`
* *
@ -221,11 +224,13 @@ export class Validators {
* @description * @description
* Validator that requires the control's value pass an email validation test. * Validator that requires the control's value pass an email validation test.
* *
* Tests the value using a [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) * Tests the value using a [regular
* expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
* pattern suitable for common usecases. The pattern is based on the definition of a valid email * pattern suitable for common usecases. The pattern is based on the definition of a valid email
* address in the [WHATWG HTML specification](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address) * address in the [WHATWG HTML
* with some enhancements to incorporate more RFC rules (such as rules related to domain names and * specification](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address) with
* the lengths of different parts of the address). * some enhancements to incorporate more RFC rules (such as rules related to domain names and the
* lengths of different parts of the address).
* *
* The differences from the WHATWG version include: * The differences from the WHATWG version include:
* - Disallow `local-part` (the part before the `@` symbol) to begin or end with a period (`.`). * - Disallow `local-part` (the part before the `@` symbol) to begin or end with a period (`.`).
@ -396,7 +401,9 @@ export class Validators {
* @see `updateValueAndValidity()` * @see `updateValueAndValidity()`
* *
*/ */
static nullValidator(control: AbstractControl): ValidationErrors|null { return null; } static nullValidator(control: AbstractControl): ValidationErrors|null {
return null;
}
/** /**
* @description * @description

View File

@ -19,22 +19,30 @@ class DummyControlValueAccessor implements ControlValueAccessor {
registerOnChange(fn: any) {} registerOnChange(fn: any) {}
registerOnTouched(fn: any) {} registerOnTouched(fn: any) {}
writeValue(obj: any): void { this.writtenValue = obj; } writeValue(obj: any): void {
this.writtenValue = obj;
}
} }
class CustomValidatorDirective implements Validator { class CustomValidatorDirective implements Validator {
validate(c: FormControl): ValidationErrors { return {'custom': true}; } validate(c: FormControl): ValidationErrors {
return {'custom': true};
}
} }
function asyncValidator(expected: any, timeout = 0) { function asyncValidator(expected: any, timeout = 0) {
return (c: AbstractControl): any => { return (c: AbstractControl): any => {
let resolve: (result: any) => void = undefined!; let resolve: (result: any) => void = undefined!;
const promise = new Promise(res => { resolve = res; }); const promise = new Promise(res => {
resolve = res;
});
const res = c.value != expected ? {'async': true} : null; const res = c.value != expected ? {'async': true} : null;
if (timeout == 0) { if (timeout == 0) {
resolve(res); resolve(res);
} else { } else {
setTimeout(() => { resolve(res); }, timeout); setTimeout(() => {
resolve(res);
}, timeout);
} }
return promise; return promise;
}; };
@ -44,16 +52,21 @@ function asyncValidator(expected: any, timeout = 0) {
describe('Form Directives', () => { describe('Form Directives', () => {
let defaultAccessor: DefaultValueAccessor; let defaultAccessor: DefaultValueAccessor;
beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null !, null !, null !); }); beforeEach(() => {
defaultAccessor = new DefaultValueAccessor(null!, null!, null!);
});
describe('shared', () => { describe('shared', () => {
describe('selectValueAccessor', () => { describe('selectValueAccessor', () => {
let dir: NgControl; let dir: NgControl;
beforeEach(() => { dir = <any>new SpyNgControl(); }); beforeEach(() => {
dir = <any>new SpyNgControl();
});
it('should throw when given an empty array', it('should throw when given an empty array', () => {
() => { expect(() => selectValueAccessor(dir, [])).toThrowError(); }); expect(() => selectValueAccessor(dir, [])).toThrowError();
});
it('should throw when accessor is not provided as array', () => { it('should throw when accessor is not provided as array', () => {
expect(() => selectValueAccessor(dir, {} as any[])) expect(() => selectValueAccessor(dir, {} as any[]))
@ -61,8 +74,9 @@ function asyncValidator(expected: any, timeout = 0) {
`Value accessor was not provided as an array for form control with unspecified name attribute`); `Value accessor was not provided as an array for form control with unspecified name attribute`);
}); });
it('should return the default value accessor when no other provided', it('should return the default value accessor when no other provided', () => {
() => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); }); expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor);
});
it('should return checkbox accessor when provided', () => { it('should return checkbox accessor when provided', () => {
const checkboxAccessor = new CheckboxControlValueAccessor(null!, null!); const checkboxAccessor = new CheckboxControlValueAccessor(null!, null!);
@ -94,16 +108,17 @@ function asyncValidator(expected: any, timeout = 0) {
it('should return custom accessor when provided', () => { it('should return custom accessor when provided', () => {
const customAccessor: ControlValueAccessor = new SpyValueAccessor() as any; const customAccessor: ControlValueAccessor = new SpyValueAccessor() as any;
const checkboxAccessor = new CheckboxControlValueAccessor(null!, null!); const checkboxAccessor = new CheckboxControlValueAccessor(null!, null!);
expect(selectValueAccessor(dir, <any>[defaultAccessor, customAccessor, checkboxAccessor])) expect(selectValueAccessor(dir, <any>[
.toEqual(customAccessor); defaultAccessor, customAccessor, checkboxAccessor
])).toEqual(customAccessor);
}); });
it('should return custom accessor when provided with select multiple', () => { it('should return custom accessor when provided with select multiple', () => {
const customAccessor: ControlValueAccessor = new SpyValueAccessor() as any; const customAccessor: ControlValueAccessor = new SpyValueAccessor() as any;
const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null!, null!); const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null!, null!);
expect(selectValueAccessor( expect(selectValueAccessor(dir, <any>[
dir, <any>[defaultAccessor, customAccessor, selectMultipleAccessor])) defaultAccessor, customAccessor, selectMultipleAccessor
.toEqual(customAccessor); ])).toEqual(customAccessor);
}); });
it('should throw when more than one custom accessor is provided', () => { it('should throw when more than one custom accessor is provided', () => {
@ -137,8 +152,8 @@ function asyncValidator(expected: any, timeout = 0) {
form = new FormGroupDirective([], []); form = new FormGroupDirective([], []);
formModel = new FormGroup({ formModel = new FormGroup({
'login': new FormControl(), 'login': new FormControl(),
'passwords': new FormGroup( 'passwords':
{'password': new FormControl(), 'passwordConfirm': new FormControl()}) new FormGroup({'password': new FormControl(), 'passwordConfirm': new FormControl()})
}); });
form.form = formModel; form.form = formModel;
@ -509,7 +524,9 @@ function asyncValidator(expected: any, timeout = 0) {
controlDir.form = control; controlDir.form = control;
}); });
it('should reexport control properties', () => { checkProperties(control); }); it('should reexport control properties', () => {
checkProperties(control);
});
it('should reexport control methods', () => { it('should reexport control methods', () => {
expect(controlDir.hasError('required')).toBe(control.hasError('required')); expect(controlDir.hasError('required')).toBe(control.hasError('required'));
@ -641,7 +658,6 @@ function asyncValidator(expected: any, timeout = 0) {
ngModel.ngOnChanges({isDisabled: new SimpleChange(null, 'anything else', false)}); ngModel.ngOnChanges({isDisabled: new SimpleChange(null, 'anything else', false)});
tick(); tick();
expect(ngModel.control.disabled).toEqual(true); expect(ngModel.control.disabled).toEqual(true);
})); }));
}); });

View File

@ -16,14 +16,18 @@ import {of } from 'rxjs';
function asyncValidator(expected: string, timeouts = {}) { function asyncValidator(expected: string, timeouts = {}) {
return (c: AbstractControl) => { return (c: AbstractControl) => {
let resolve: (result: any) => void = undefined!; let resolve: (result: any) => void = undefined!;
const promise = new Promise<ValidationErrors|null>(res => { resolve = res; }); const promise = new Promise<ValidationErrors|null>(res => {
resolve = res;
});
const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0;
const res = c.value != expected ? {'async': true} : null; const res = c.value != expected ? {'async': true} : null;
if (t == 0) { if (t == 0) {
resolve(res); resolve(res);
} else { } else {
setTimeout(() => { resolve(res); }, t); setTimeout(() => {
resolve(res);
}, t);
} }
return promise; return promise;
@ -31,7 +35,6 @@ import {of } from 'rxjs';
} }
describe('FormArray', () => { describe('FormArray', () => {
describe('adding/removing', () => { describe('adding/removing', () => {
let a: FormArray; let a: FormArray;
let c1: FormControl, c2: FormControl, c3: FormControl; let c1: FormControl, c2: FormControl, c3: FormControl;
@ -113,8 +116,7 @@ import {of } from 'rxjs';
describe('markAllAsTouched', () => { describe('markAllAsTouched', () => {
it('should mark all descendants as touched', () => { it('should mark all descendants as touched', () => {
const formArray: FormArray = new FormArray([ const formArray: FormArray = new FormArray([
new FormControl('v1'), new FormControl('v2'), new FormControl('v1'), new FormControl('v2'), new FormGroup({'c1': new FormControl('v1')}),
new FormGroup({'c1': new FormControl('v1')}),
new FormArray([new FormGroup({'c2': new FormControl('v2')})]) new FormArray([new FormGroup({'c2': new FormControl('v2')})])
]); ]);
@ -254,10 +256,18 @@ import {of } from 'rxjs';
}); });
it('should not fire an event when explicitly specified', fakeAsync(() => { it('should not fire an event when explicitly specified', fakeAsync(() => {
form.valueChanges.subscribe((value) => { throw 'Should not happen'; }); form.valueChanges.subscribe((value) => {
a.valueChanges.subscribe((value) => { throw 'Should not happen'; }); throw 'Should not happen';
c.valueChanges.subscribe((value) => { throw 'Should not happen'; }); });
c2.valueChanges.subscribe((value) => { throw 'Should not happen'; }); a.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c2.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
a.setValue(['one', 'two'], {emitEvent: false}); a.setValue(['one', 'two'], {emitEvent: false});
tick(); tick();
@ -369,10 +379,18 @@ import {of } from 'rxjs';
}); });
it('should not fire an event when explicitly specified', fakeAsync(() => { it('should not fire an event when explicitly specified', fakeAsync(() => {
form.valueChanges.subscribe((value) => { throw 'Should not happen'; }); form.valueChanges.subscribe((value) => {
a.valueChanges.subscribe((value) => { throw 'Should not happen'; }); throw 'Should not happen';
c.valueChanges.subscribe((value) => { throw 'Should not happen'; }); });
c2.valueChanges.subscribe((value) => { throw 'Should not happen'; }); a.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c2.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
a.patchValue(['one', 'two'], {emitEvent: false}); a.patchValue(['one', 'two'], {emitEvent: false});
tick(); tick();
@ -580,11 +598,21 @@ import {of } from 'rxjs';
}); });
it('should not fire an event when explicitly specified', fakeAsync(() => { it('should not fire an event when explicitly specified', fakeAsync(() => {
form.valueChanges.subscribe((value) => { throw 'Should not happen'; }); form.valueChanges.subscribe((value) => {
a.valueChanges.subscribe((value) => { throw 'Should not happen'; }); throw 'Should not happen';
c.valueChanges.subscribe((value) => { throw 'Should not happen'; }); });
c2.valueChanges.subscribe((value) => { throw 'Should not happen'; }); a.valueChanges.subscribe((value) => {
c3.valueChanges.subscribe((value) => { throw 'Should not happen'; }); throw 'Should not happen';
});
c.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c2.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c3.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
a.reset([], {emitEvent: false}); a.reset([], {emitEvent: false});
tick(); tick();
@ -650,7 +678,9 @@ import {of } from 'rxjs';
a = new FormArray([c]); a = new FormArray([c]);
}); });
it('should be false after creating a control', () => { expect(a.dirty).toEqual(false); }); it('should be false after creating a control', () => {
expect(a.dirty).toEqual(false);
});
it('should be true after changing the value of the control', () => { it('should be true after changing the value of the control', () => {
c.markAsDirty(); c.markAsDirty();
@ -668,7 +698,9 @@ import {of } from 'rxjs';
a = new FormArray([c]); a = new FormArray([c]);
}); });
it('should be false after creating a control', () => { expect(a.touched).toEqual(false); }); it('should be false after creating a control', () => {
expect(a.touched).toEqual(false);
});
it('should be true after child control is marked as touched', () => { it('should be true after child control is marked as touched', () => {
c.markAsTouched(); c.markAsTouched();
@ -734,7 +766,6 @@ import {of } from 'rxjs';
expect(logger).toEqual(['PENDING']); expect(logger).toEqual(['PENDING']);
}); });
}); });
}); });
describe('valueChanges', () => { describe('valueChanges', () => {
@ -764,7 +795,11 @@ import {of } from 'rxjs';
let controlCallbackIsCalled = false; let controlCallbackIsCalled = false;
c1.valueChanges.subscribe({next: (value: any) => { controlCallbackIsCalled = true; }}); c1.valueChanges.subscribe({
next: (value: any) => {
controlCallbackIsCalled = true;
}
});
a.valueChanges.subscribe({ a.valueChanges.subscribe({
next: (value: any) => { next: (value: any) => {
@ -894,7 +929,9 @@ import {of } from 'rxjs';
}); });
describe('asyncValidator', () => { describe('asyncValidator', () => {
function otherObservableValidator() { return of ({'other': true}); } function otherObservableValidator() {
return of({'other': true});
}
it('should run the async validator', fakeAsync(() => { it('should run the async validator', fakeAsync(() => {
const c = new FormControl('value'); const c = new FormControl('value');
@ -1187,7 +1224,6 @@ import {of } from 'rxjs';
a.enable({emitEvent: false}); a.enable({emitEvent: false});
expect(logger).toEqual([]); expect(logger).toEqual([]);
}); });
}); });
describe('setControl()', () => { describe('setControl()', () => {
@ -1230,9 +1266,7 @@ import {of } from 'rxjs';
a.setControl(0, c2); a.setControl(0, c2);
expect(logger).toEqual(['change!']); expect(logger).toEqual(['change!']);
}); });
}); });
}); });
}); });
})(); })();

View File

@ -11,13 +11,19 @@ import {FormBuilder, Validators} from '@angular/forms';
import {of} from 'rxjs'; import {of} from 'rxjs';
(function() { (function() {
function syncValidator(_: any /** TODO #9100 */): any /** TODO #9100 */ { return null; } function syncValidator(_: any /** TODO #9100 */): any /** TODO #9100 */ {
function asyncValidator(_: any /** TODO #9100 */) { return Promise.resolve(null); } return null;
}
function asyncValidator(_: any /** TODO #9100 */) {
return Promise.resolve(null);
}
describe('Form Builder', () => { describe('Form Builder', () => {
let b: FormBuilder; let b: FormBuilder;
beforeEach(() => { b = new FormBuilder(); }); beforeEach(() => {
b = new FormBuilder();
});
it('should create controls from a value', () => { it('should create controls from a value', () => {
const g = b.group({'login': 'some value'}); const g = b.group({'login': 'some value'});
@ -91,8 +97,7 @@ import {of } from 'rxjs';
const e = b.control(null); const e = b.control(null);
const f = b.control(undefined); const f = b.control(undefined);
const a = b.array( const a = b.array(
['one', ['two', syncValidator], c, b.array(['four']), e, f], syncValidator, ['one', ['two', syncValidator], c, b.array(['four']), e, f], syncValidator, asyncValidator);
asyncValidator);
expect(a.value).toEqual(['one', 'two', 'three', ['four'], null, null]); expect(a.value).toEqual(['one', 'two', 'three', ['four'], null, null]);
expect(a.validator).toBe(syncValidator); expect(a.validator).toBe(syncValidator);
@ -100,8 +105,12 @@ import {of } from 'rxjs';
}); });
it('should create control arrays with multiple async validators', fakeAsync(() => { it('should create control arrays with multiple async validators', fakeAsync(() => {
function asyncValidator1() { return of ({'async1': true}); } function asyncValidator1() {
function asyncValidator2() { return of ({'async2': true}); } return of({'async1': true});
}
function asyncValidator2() {
return of({'async2': true});
}
const a = b.array(['one', 'two'], null, [asyncValidator1, asyncValidator2]); const a = b.array(['one', 'two'], null, [asyncValidator1, asyncValidator2]);
expect(a.value).toEqual(['one', 'two']); expect(a.value).toEqual(['one', 'two']);
@ -112,8 +121,12 @@ import {of } from 'rxjs';
})); }));
it('should create control arrays with multiple sync validators', () => { it('should create control arrays with multiple sync validators', () => {
function syncValidator1() { return {'sync1': true}; } function syncValidator1() {
function syncValidator2() { return {'sync2': true}; } return {'sync1': true};
}
function syncValidator2() {
return {'sync2': true};
}
const a = b.array(['one', 'two'], [syncValidator1, syncValidator2]); const a = b.array(['one', 'two'], [syncValidator1, syncValidator2]);
expect(a.value).toEqual(['one', 'two']); expect(a.value).toEqual(['one', 'two']);

View File

@ -17,14 +17,18 @@ import {FormArray} from '@angular/forms/src/model';
function asyncValidator(expected: string, timeouts = {}): AsyncValidatorFn { function asyncValidator(expected: string, timeouts = {}): AsyncValidatorFn {
return (c: AbstractControl) => { return (c: AbstractControl) => {
let resolve: (result: any) => void = undefined!; let resolve: (result: any) => void = undefined!;
const promise = new Promise<ValidationErrors|null>(res => { resolve = res; }); const promise = new Promise<ValidationErrors|null>(res => {
resolve = res;
});
const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0;
const res = c.value != expected ? {'async': true} : null; const res = c.value != expected ? {'async': true} : null;
if (t == 0) { if (t == 0) {
resolve(res); resolve(res);
} else { } else {
setTimeout(() => { resolve(res); }, t); setTimeout(() => {
resolve(res);
}, t);
} }
return promise; return promise;
@ -33,13 +37,19 @@ import {FormArray} from '@angular/forms/src/model';
function asyncValidatorReturningObservable(c: AbstractControl) { function asyncValidatorReturningObservable(c: AbstractControl) {
const e = new EventEmitter(); const e = new EventEmitter();
Promise.resolve(null).then(() => { e.emit({'async': true}); }); Promise.resolve(null).then(() => {
e.emit({'async': true});
});
return e; return e;
} }
function otherAsyncValidator() { return Promise.resolve({'other': true}); } function otherAsyncValidator() {
return Promise.resolve({'other': true});
}
function syncValidator(_: any /** TODO #9100 */): any /** TODO #9100 */ { return null; } function syncValidator(_: any /** TODO #9100 */): any /** TODO #9100 */ {
return null;
}
describe('FormControl', () => { describe('FormControl', () => {
it('should default the value to null', () => { it('should default the value to null', () => {
@ -82,11 +92,9 @@ import {FormArray} from '@angular/forms/src/model';
expect(c.value).toEqual({value: '', test: 'test'}); expect(c.value).toEqual({value: '', test: 'test'});
expect(c.disabled).toBe(false); expect(c.disabled).toBe(false);
}); });
}); });
describe('updateOn', () => { describe('updateOn', () => {
it('should default to on change', () => { it('should default to on change', () => {
const c = new FormControl(''); const c = new FormControl('');
expect(c.updateOn).toEqual('change'); expect(c.updateOn).toEqual('change');
@ -165,14 +173,10 @@ import {FormArray} from '@angular/forms/src/model';
expect(g.get('groupTwo.one')!.updateOn).toEqual('submit'); expect(g.get('groupTwo.one')!.updateOn).toEqual('submit');
expect(g.get('three')!.updateOn).toEqual('change'); expect(g.get('three')!.updateOn).toEqual('change');
}); });
}); });
}); });
describe('validator', () => { describe('validator', () => {
it('should run validator with the initial value', () => { it('should run validator with the initial value', () => {
const c = new FormControl('value', Validators.required); const c = new FormControl('value', Validators.required);
expect(c.valid).toEqual(true); expect(c.valid).toEqual(true);
@ -203,8 +207,7 @@ import {FormArray} from '@angular/forms/src/model';
}); });
it('should support multiple validators from options obj', () => { it('should support multiple validators from options obj', () => {
const c = const c = new FormControl(null, {validators: [Validators.required, Validators.minLength(3)]});
new FormControl(null, {validators: [Validators.required, Validators.minLength(3)]});
expect(c.valid).toEqual(false); expect(c.valid).toEqual(false);
expect(c.errors).toEqual({required: true}); expect(c.errors).toEqual({required: true});
@ -332,8 +335,8 @@ import {FormArray} from '@angular/forms/src/model';
})); }));
it('should only use the latest async validation run', fakeAsync(() => { it('should only use the latest async validation run', fakeAsync(() => {
const c = new FormControl( const c =
'', null !, asyncValidator('expected', {'long': 200, 'expected': 100})); new FormControl('', null!, asyncValidator('expected', {'long': 200, 'expected': 100}));
c.setValue('long'); c.setValue('long');
c.setValue('expected'); c.setValue('expected');
@ -493,14 +496,18 @@ import {FormArray} from '@angular/forms/src/model';
}); });
it('should fire an event', fakeAsync(() => { it('should fire an event', fakeAsync(() => {
c.valueChanges.subscribe((value) => { expect(value).toEqual('newValue'); }); c.valueChanges.subscribe((value) => {
expect(value).toEqual('newValue');
});
c.setValue('newValue'); c.setValue('newValue');
tick(); tick();
})); }));
it('should not fire an event when explicitly specified', fakeAsync(() => { it('should not fire an event when explicitly specified', fakeAsync(() => {
c.valueChanges.subscribe((value) => { throw 'Should not happen'; }); c.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c.setValue('newValue', {emitEvent: false}); c.setValue('newValue', {emitEvent: false});
tick(); tick();
@ -556,14 +563,18 @@ import {FormArray} from '@angular/forms/src/model';
}); });
it('should fire an event', fakeAsync(() => { it('should fire an event', fakeAsync(() => {
c.valueChanges.subscribe((value) => { expect(value).toEqual('newValue'); }); c.valueChanges.subscribe((value) => {
expect(value).toEqual('newValue');
});
c.patchValue('newValue'); c.patchValue('newValue');
tick(); tick();
})); }));
it('should not fire an event when explicitly specified', fakeAsync(() => { it('should not fire an event when explicitly specified', fakeAsync(() => {
c.valueChanges.subscribe((value) => { throw 'Should not happen'; }); c.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c.patchValue('newValue', {emitEvent: false}); c.patchValue('newValue', {emitEvent: false});
@ -583,7 +594,9 @@ import {FormArray} from '@angular/forms/src/model';
describe('reset()', () => { describe('reset()', () => {
let c: FormControl; let c: FormControl;
beforeEach(() => { c = new FormControl('initial value'); }); beforeEach(() => {
c = new FormControl('initial value');
});
it('should reset to a specific value if passed', () => { it('should reset to a specific value if passed', () => {
c.setValue('new value'); c.setValue('new value');
@ -720,9 +733,15 @@ import {FormArray} from '@angular/forms/src/model';
}); });
it('should not fire an event when explicitly specified', fakeAsync(() => { it('should not fire an event when explicitly specified', fakeAsync(() => {
g.valueChanges.subscribe((value) => { throw 'Should not happen'; }); g.valueChanges.subscribe((value) => {
c.valueChanges.subscribe((value) => { throw 'Should not happen'; }); throw 'Should not happen';
c2.valueChanges.subscribe((value) => { throw 'Should not happen'; }); });
c.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c2.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c.reset(null, {emitEvent: false}); c.reset(null, {emitEvent: false});
@ -747,13 +766,14 @@ import {FormArray} from '@angular/forms/src/model';
expect(logger).toEqual(['control1', 'group']); expect(logger).toEqual(['control1', 'group']);
}); });
}); });
}); });
describe('valueChanges & statusChanges', () => { describe('valueChanges & statusChanges', () => {
let c: FormControl; let c: FormControl;
beforeEach(() => { c = new FormControl('old', Validators.required); }); beforeEach(() => {
c = new FormControl('old', Validators.required);
});
it('should fire an event after the value has been updated', it('should fire an event after the value has been updated',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
@ -887,7 +907,6 @@ import {FormArray} from '@angular/forms/src/model';
}); });
describe('disable() & enable()', () => { describe('disable() & enable()', () => {
it('should mark the control as disabled', () => { it('should mark the control as disabled', () => {
const c = new FormControl(null); const c = new FormControl(null);
expect(c.disabled).toBe(false); expect(c.disabled).toBe(false);
@ -1176,7 +1195,6 @@ import {FormArray} from '@angular/forms/src/model';
c.enable(); c.enable();
expect(logger).toEqual(['DISABLED', 'INVALID']); expect(logger).toEqual(['DISABLED', 'INVALID']);
}); });
it('should emit status change events in correct order', () => { it('should emit status change events in correct order', () => {
@ -1214,15 +1232,18 @@ import {FormArray} from '@angular/forms/src/model';
c.enable({emitEvent: false}); c.enable({emitEvent: false});
expect(logger).toEqual([]); expect(logger).toEqual([]);
}); });
}); });
}); });
describe('pending', () => { describe('pending', () => {
let c: FormControl; let c: FormControl;
beforeEach(() => { c = new FormControl('value'); }); beforeEach(() => {
c = new FormControl('value');
});
it('should be false after creating a control', () => { expect(c.pending).toEqual(false); }); it('should be false after creating a control', () => {
expect(c.pending).toEqual(false);
});
it('should be true after changing the value of the control', () => { it('should be true after changing the value of the control', () => {
c.markAsPending(); c.markAsPending();
@ -1246,7 +1267,6 @@ import {FormArray} from '@angular/forms/src/model';
c.markAsPending({emitEvent: false}); c.markAsPending({emitEvent: false});
expect(logger).toEqual([]); expect(logger).toEqual([]);
}); });
}); });
}); });
}); });

View File

@ -21,14 +21,18 @@ import {of } from 'rxjs';
function asyncValidator(expected: string, timeouts = {}) { function asyncValidator(expected: string, timeouts = {}) {
return (c: AbstractControl) => { return (c: AbstractControl) => {
let resolve: (result: any) => void = undefined!; let resolve: (result: any) => void = undefined!;
const promise = new Promise<ValidationErrors|null>(res => { resolve = res; }); const promise = new Promise<ValidationErrors|null>(res => {
resolve = res;
});
const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0; const t = (timeouts as any)[c.value] != null ? (timeouts as any)[c.value] : 0;
const res = c.value != expected ? {'async': true} : null; const res = c.value != expected ? {'async': true} : null;
if (t == 0) { if (t == 0) {
resolve(res); resolve(res);
} else { } else {
setTimeout(() => { resolve(res); }, t); setTimeout(() => {
resolve(res);
}, t);
} }
return promise; return promise;
@ -37,11 +41,15 @@ import {of } from 'rxjs';
function asyncValidatorReturningObservable(c: AbstractControl) { function asyncValidatorReturningObservable(c: AbstractControl) {
const e = new EventEmitter(); const e = new EventEmitter();
Promise.resolve(null).then(() => { e.emit({'async': true}); }); Promise.resolve(null).then(() => {
e.emit({'async': true});
});
return e; return e;
} }
function otherObservableValidator() { return of ({'other': true}); } function otherObservableValidator() {
return of({'other': true});
}
describe('FormGroup', () => { describe('FormGroup', () => {
describe('value', () => { describe('value', () => {
@ -83,7 +91,6 @@ import {of } from 'rxjs';
expect(fg.getRawValue()) expect(fg.getRawValue())
.toEqual({'c1': 'v1', 'group': {'c2': 'v2', 'c3': 'v3'}, 'array': ['v4', 'v5']}); .toEqual({'c1': 'v1', 'group': {'c2': 'v2', 'c3': 'v3'}, 'array': ['v4', 'v5']});
}); });
}); });
describe('markAllAsTouched', () => { describe('markAllAsTouched', () => {
@ -92,8 +99,7 @@ import {of } from 'rxjs';
'c1': new FormControl('v1'), 'c1': new FormControl('v1'),
'group': new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}), 'group': new FormGroup({'c2': new FormControl('v2'), 'c3': new FormControl('v3')}),
'array': new FormArray([ 'array': new FormArray([
new FormControl('v4'), new FormControl('v5'), new FormControl('v4'), new FormControl('v5'), new FormGroup({'c4': new FormControl('v4')})
new FormGroup({'c4': new FormControl('v4')})
]) ])
}); });
@ -180,7 +186,9 @@ import {of } from 'rxjs';
g = new FormGroup({'one': c}); g = new FormGroup({'one': c});
}); });
it('should be false after creating a control', () => { expect(g.dirty).toEqual(false); }); it('should be false after creating a control', () => {
expect(g.dirty).toEqual(false);
});
it('should be true after changing the value of the control', () => { it('should be true after changing the value of the control', () => {
c.markAsDirty(); c.markAsDirty();
@ -198,7 +206,9 @@ import {of } from 'rxjs';
g = new FormGroup({'one': c}); g = new FormGroup({'one': c});
}); });
it('should be false after creating a control', () => { expect(g.touched).toEqual(false); }); it('should be false after creating a control', () => {
expect(g.touched).toEqual(false);
});
it('should be true after control is marked as touched', () => { it('should be true after control is marked as touched', () => {
c.markAsTouched(); c.markAsTouched();
@ -302,9 +312,15 @@ import {of } from 'rxjs';
}); });
it('should not fire an event when explicitly specified', fakeAsync(() => { it('should not fire an event when explicitly specified', fakeAsync(() => {
form.valueChanges.subscribe((value) => { throw 'Should not happen'; }); form.valueChanges.subscribe((value) => {
g.valueChanges.subscribe((value) => { throw 'Should not happen'; }); throw 'Should not happen';
c.valueChanges.subscribe((value) => { throw 'Should not happen'; }); });
g.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
g.setValue({'one': 'one', 'two': 'two'}, {emitEvent: false}); g.setValue({'one': 'one', 'two': 'two'}, {emitEvent: false});
tick(); tick();
@ -416,9 +432,15 @@ import {of } from 'rxjs';
}); });
it('should not fire an event when explicitly specified', fakeAsync(() => { it('should not fire an event when explicitly specified', fakeAsync(() => {
form.valueChanges.subscribe((value) => { throw 'Should not happen'; }); form.valueChanges.subscribe((value) => {
g.valueChanges.subscribe((value) => { throw 'Should not happen'; }); throw 'Should not happen';
c.valueChanges.subscribe((value) => { throw 'Should not happen'; }); });
g.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
g.patchValue({'one': 'one', 'two': 'two'}, {emitEvent: false}); g.patchValue({'one': 'one', 'two': 'two'}, {emitEvent: false});
tick(); tick();
@ -625,9 +647,15 @@ import {of } from 'rxjs';
}); });
it('should not fire an event when explicitly specified', fakeAsync(() => { it('should not fire an event when explicitly specified', fakeAsync(() => {
form.valueChanges.subscribe((value) => { throw 'Should not happen'; }); form.valueChanges.subscribe((value) => {
g.valueChanges.subscribe((value) => { throw 'Should not happen'; }); throw 'Should not happen';
c.valueChanges.subscribe((value) => { throw 'Should not happen'; }); });
g.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
c.valueChanges.subscribe((value) => {
throw 'Should not happen';
});
g.reset({}, {emitEvent: false}); g.reset({}, {emitEvent: false});
tick(); tick();
@ -672,7 +700,6 @@ import {of } from 'rxjs';
form.reset(); form.reset();
}); });
}); });
}); });
describe('contains', () => { describe('contains', () => {
@ -685,11 +712,13 @@ import {of } from 'rxjs';
}); });
}); });
it('should return false when the component is disabled', it('should return false when the component is disabled', () => {
() => { expect(group.contains('optional')).toEqual(false); }); expect(group.contains('optional')).toEqual(false);
});
it('should return false when there is no component with the given name', it('should return false when there is no component with the given name', () => {
() => { expect(group.contains('something else')).toEqual(false); }); expect(group.contains('something else')).toEqual(false);
});
it('should return true when the component is enabled', () => { it('should return true when the component is enabled', () => {
expect(group.contains('required')).toEqual(true); expect(group.contains('required')).toEqual(true);
@ -716,8 +745,9 @@ import {of } from 'rxjs';
}); });
}); });
it('should not get inherited properties', it('should not get inherited properties', () => {
() => { expect(group.get('constructor')).toBe(null); }); expect(group.get('constructor')).toBe(null);
});
}); });
describe('statusChanges', () => { describe('statusChanges', () => {
@ -829,7 +859,6 @@ import {of } from 'rxjs';
}); });
describe('validator', () => { describe('validator', () => {
function containsValidator(c: AbstractControl): ValidationErrors|null { function containsValidator(c: AbstractControl): ValidationErrors|null {
return c.get('one')!.value && c.get('one')!.value.indexOf('c') !== -1 ? null : return c.get('one')!.value && c.get('one')!.value.indexOf('c') !== -1 ? null :
{'missing': true}; {'missing': true};
@ -885,7 +914,6 @@ import {of } from 'rxjs';
g.setValue({one: 'correct'}); g.setValue({one: 'correct'});
expect(g.valid).toEqual(true); expect(g.valid).toEqual(true);
}); });
}); });
describe('asyncValidator', () => { describe('asyncValidator', () => {
@ -944,8 +972,7 @@ import {of } from 'rxjs';
expect(g.pending).toEqual(false); expect(g.pending).toEqual(false);
})); }));
it('should run the parent group\'s async validator when children are pending', it('should run the parent group\'s async validator when children are pending', fakeAsync(() => {
fakeAsync(() => {
const c = new FormControl('value', null!, asyncValidator('expected')); const c = new FormControl('value', null!, asyncValidator('expected'));
const g = new FormGroup({'one': c}, null!, asyncValidator('expected')); const g = new FormGroup({'one': c}, null!, asyncValidator('expected'));
@ -1140,8 +1167,7 @@ import {of } from 'rxjs';
}); });
it('should clear out async group errors when disabled', fakeAsync(() => { it('should clear out async group errors when disabled', fakeAsync(() => {
const g = const g = new FormGroup({'one': new FormControl()}, null!, asyncValidator('expected'));
new FormGroup({'one': new FormControl()}, null !, asyncValidator('expected'));
tick(); tick();
expect(g.errors).toEqual({'async': true}); expect(g.errors).toEqual({'async': true});
@ -1154,8 +1180,7 @@ import {of } from 'rxjs';
})); }));
it('should re-populate async group errors when enabled from a child', fakeAsync(() => { it('should re-populate async group errors when enabled from a child', fakeAsync(() => {
const g = const g = new FormGroup({'one': new FormControl()}, null!, asyncValidator('expected'));
new FormGroup({'one': new FormControl()}, null !, asyncValidator('expected'));
tick(); tick();
expect(g.errors).toEqual({'async': true}); expect(g.errors).toEqual({'async': true});
@ -1220,9 +1245,7 @@ import {of } from 'rxjs';
g.enable({emitEvent: false}); g.enable({emitEvent: false});
expect(logger).toEqual([]); expect(logger).toEqual([]);
}); });
}); });
}); });
describe('updateTreeValidity()', () => { describe('updateTreeValidity()', () => {
@ -1254,7 +1277,6 @@ import {of } from 'rxjs';
(form as any)._updateTreeValidity({emitEvent: false}); (form as any)._updateTreeValidity({emitEvent: false});
expect(logger).toEqual([]); expect(logger).toEqual([]);
}); });
}); });
describe('setControl()', () => { describe('setControl()', () => {
@ -1297,7 +1319,6 @@ import {of } from 'rxjs';
g.setControl('one', c2); g.setControl('one', c2);
expect(logger).toEqual(['change!']); expect(logger).toEqual(['change!']);
}); });
}); });
describe('pending', () => { describe('pending', () => {
@ -1309,7 +1330,9 @@ import {of } from 'rxjs';
g = new FormGroup({'one': c}); g = new FormGroup({'one': c});
}); });
it('should be false after creating a control', () => { expect(g.pending).toEqual(false); }); it('should be false after creating a control', () => {
expect(g.pending).toEqual(false);
});
it('should be true after changing the value of the control', () => { it('should be true after changing the value of the control', () => {
c.markAsPending(); c.markAsPending();
@ -1350,6 +1373,5 @@ import {of } from 'rxjs';
}); });
}); });
}); });
}); });
})(); })();

View File

@ -7,8 +7,8 @@
*/ */
import {ɵgetDOM as getDOM} from '@angular/common'; import {ɵgetDOM as getDOM} from '@angular/common';
import {Component, Directive, Input, Type, forwardRef} from '@angular/core'; import {Component, Directive, forwardRef, Input, Type} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; import {ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {AbstractControl, AsyncValidator, AsyncValidatorFn, COMPOSITION_BUFFER_MODE, FormArray, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormsModule, NG_ASYNC_VALIDATORS, NG_VALIDATORS, ReactiveFormsModule, Validators} from '@angular/forms'; import {AbstractControl, AsyncValidator, AsyncValidatorFn, COMPOSITION_BUFFER_MODE, FormArray, FormControl, FormControlDirective, FormControlName, FormGroup, FormGroupDirective, FormsModule, NG_ASYNC_VALIDATORS, NG_VALIDATORS, ReactiveFormsModule, Validators} from '@angular/forms';
import {By} from '@angular/platform-browser/src/dom/debug/by'; import {By} from '@angular/platform-browser/src/dom/debug/by';
import {dispatchEvent, sortedClassList} from '@angular/platform-browser/testing/src/browser_util'; import {dispatchEvent, sortedClassList} from '@angular/platform-browser/testing/src/browser_util';
@ -19,7 +19,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
{ {
describe('reactive forms integration tests', () => { describe('reactive forms integration tests', () => {
function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> { function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [component, ...directives], imports: [FormsModule, ReactiveFormsModule]}); {declarations: [component, ...directives], imports: [FormsModule, ReactiveFormsModule]});
@ -74,11 +73,9 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(form.value).toEqual({'login': 'updatedValue'}); expect(form.value).toEqual({'login': 'updatedValue'});
}); });
}); });
describe('re-bound form groups', () => { describe('re-bound form groups', () => {
it('should update DOM elements initially', () => { it('should update DOM elements initially', () => {
const fixture = initTest(FormGroupComp); const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('oldValue')}); fixture.componentInstance.form = new FormGroup({'login': new FormControl('oldValue')});
@ -167,16 +164,14 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
it('should pick up dir validators from nested form groups', () => { it('should pick up dir validators from nested form groups', () => {
const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator); const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator);
const form = new FormGroup({ const form = new FormGroup({
'signin': 'signin': new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
}); });
fixture.componentInstance.form = form; fixture.componentInstance.form = form;
fixture.detectChanges(); fixture.detectChanges();
expect(form.get('signin')!.valid).toBe(false); expect(form.get('signin')!.valid).toBe(false);
const newForm = new FormGroup({ const newForm = new FormGroup({
'signin': 'signin': new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
}); });
fixture.componentInstance.form = newForm; fixture.componentInstance.form = newForm;
fixture.detectChanges(); fixture.detectChanges();
@ -187,8 +182,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
it('should strip named controls that are not found', () => { it('should strip named controls that are not found', () => {
const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator); const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator);
const form = new FormGroup({ const form = new FormGroup({
'signin': 'signin': new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
}); });
fixture.componentInstance.form = form; fixture.componentInstance.form = form;
fixture.detectChanges(); fixture.detectChanges();
@ -200,8 +194,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(emailInput.nativeElement.value).toEqual('email'); expect(emailInput.nativeElement.value).toEqual('email');
const newForm = new FormGroup({ const newForm = new FormGroup({
'signin': 'signin': new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
}); });
fixture.componentInstance.form = newForm; fixture.componentInstance.form = newForm;
fixture.detectChanges(); fixture.detectChanges();
@ -237,7 +230,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
}); });
describe('nested control rebinding', () => { describe('nested control rebinding', () => {
it('should attach dir to control when leaf control changes', () => { it('should attach dir to control when leaf control changes', () => {
const form = new FormGroup({'login': new FormControl('oldValue')}); const form = new FormGroup({'login': new FormControl('oldValue')});
const fixture = initTest(FormGroupComp); const fixture = initTest(FormGroupComp);
@ -421,9 +413,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(lastInput.value).toEqual('NY'); expect(lastInput.value).toEqual('NY');
}); });
}); });
}); });
describe('form arrays', () => { describe('form arrays', () => {
@ -494,7 +484,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
cities: [{town: 'LA', state: 'CA'}, {town: 'NY', state: 'NY'}] cities: [{town: 'LA', state: 'CA'}, {town: 'NY', state: 'NY'}]
}); });
}); });
}); });
describe('programmatic changes', () => { describe('programmatic changes', () => {
@ -592,13 +581,10 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
const input = fixture.debugElement.query(By.css('my-input')); const input = fixture.debugElement.query(By.css('my-input'));
expect(input.nativeElement.getAttribute('disabled')).toBe(null); expect(input.nativeElement.getAttribute('disabled')).toBe(null);
}); });
}); });
}); });
describe('user input', () => { describe('user input', () => {
it('should mark controls as touched after interacting with the DOM control', () => { it('should mark controls as touched after interacting with the DOM control', () => {
const fixture = initTest(FormGroupComp); const fixture = initTest(FormGroupComp);
const login = new FormControl('oldValue'); const login = new FormControl('oldValue');
@ -613,7 +599,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(login.touched).toBe(true); expect(login.touched).toBe(true);
}); });
}); });
describe('submit and reset events', () => { describe('submit and reset events', () => {
@ -672,18 +657,18 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
form.reset(); form.reset();
expect(loginEl.value).toBe(''); expect(loginEl.value).toBe('');
}); });
}); });
describe('value changes and status changes', () => { describe('value changes and status changes', () => {
it('should mark controls as dirty before emitting a value change event', () => { it('should mark controls as dirty before emitting a value change event', () => {
const fixture = initTest(FormGroupComp); const fixture = initTest(FormGroupComp);
const login = new FormControl('oldValue'); const login = new FormControl('oldValue');
fixture.componentInstance.form = new FormGroup({'login': login}); fixture.componentInstance.form = new FormGroup({'login': login});
fixture.detectChanges(); fixture.detectChanges();
login.valueChanges.subscribe(() => { expect(login.dirty).toBe(true); }); login.valueChanges.subscribe(() => {
expect(login.dirty).toBe(true);
});
const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; const loginEl = fixture.debugElement.query(By.css('input')).nativeElement;
loginEl.value = 'newValue'; loginEl.value = 'newValue';
@ -705,11 +690,12 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(login.pristine).toBe(false); expect(login.pristine).toBe(false);
login.valueChanges.subscribe(() => { expect(login.pristine).toBe(true); }); login.valueChanges.subscribe(() => {
expect(login.pristine).toBe(true);
});
form.reset(); form.reset();
}); });
}); });
describe('setting status classes', () => { describe('setting status classes', () => {
@ -831,13 +817,10 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(sortedClassList(formEl)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); expect(sortedClassList(formEl)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
}); });
}); });
describe('updateOn options', () => { describe('updateOn options', () => {
describe('on blur', () => { describe('on blur', () => {
it('should not update value or validity based on user input until blur', () => { it('should not update value or validity based on user input until blur', () => {
const fixture = initTest(FormControlComp); const fixture = initTest(FormControlComp);
const control = new FormControl('', {validators: Validators.required, updateOn: 'blur'}); const control = new FormControl('', {validators: Validators.required, updateOn: 'blur'});
@ -1141,7 +1124,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(control.value) expect(control.value)
.toEqual('Nancy', 'Expected value to change once control is blurred.'); .toEqual('Nancy', 'Expected value to change once control is blurred.');
expect(control.valid).toBe(true, 'Expected validation to run once control is blurred.'); expect(control.valid).toBe(true, 'Expected validation to run once control is blurred.');
}); });
it('should update on blur with array updateOn', () => { it('should update on blur with array updateOn', () => {
@ -1167,7 +1149,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(control.value) expect(control.value)
.toEqual('Nancy', 'Expected value to change once control is blurred.'); .toEqual('Nancy', 'Expected value to change once control is blurred.');
expect(control.valid).toBe(true, 'Expected validation to run once control is blurred.'); expect(control.valid).toBe(true, 'Expected validation to run once control is blurred.');
}); });
@ -1206,17 +1187,13 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(passwordControl.valid) expect(passwordControl.valid)
.toBe(true, 'Expected validation to run once control is blurred.'); .toBe(true, 'Expected validation to run once control is blurred.');
}); });
}); });
describe('on submit', () => { describe('on submit', () => {
it('should set initial value and validity on init', () => { it('should set initial value and validity on init', () => {
const fixture = initTest(FormGroupComp); const fixture = initTest(FormGroupComp);
const form = new FormGroup({ const form = new FormGroup({
login: login: new FormControl('Nancy', {validators: Validators.required, updateOn: 'submit'})
new FormControl('Nancy', {validators: Validators.required, updateOn: 'submit'})
}); });
fixture.componentInstance.form = form; fixture.componentInstance.form = form;
fixture.detectChanges(); fixture.detectChanges();
@ -1502,7 +1479,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(validatorSpy).not.toHaveBeenCalled(); expect(validatorSpy).not.toHaveBeenCalled();
expect(groupValidatorSpy).not.toHaveBeenCalled(); expect(groupValidatorSpy).not.toHaveBeenCalled();
}); });
it('should mark as untouched properly if pending touched', () => { it('should mark as untouched properly if pending touched', () => {
@ -1554,7 +1530,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(control.value).toEqual('Nancy', 'Expected value to change on submit.'); expect(control.value).toEqual('Nancy', 'Expected value to change on submit.');
expect(control.valid).toBe(true, 'Expected validation to run on submit.'); expect(control.valid).toBe(true, 'Expected validation to run on submit.');
}); });
it('should update on submit with array updateOn', () => { it('should update on submit with array updateOn', () => {
@ -1581,7 +1556,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(control.value).toEqual('Nancy', 'Expected value to change once control on submit'); expect(control.value).toEqual('Nancy', 'Expected value to change once control on submit');
expect(control.valid).toBe(true, 'Expected validation to run on submit.'); expect(control.valid).toBe(true, 'Expected validation to run on submit.');
}); });
it('should allow child control updateOn submit to override group updateOn', () => { it('should allow child control updateOn submit to override group updateOn', () => {
@ -1619,9 +1593,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(passwordControl.value).toEqual('Carson', 'Expected value to change on submit.'); expect(passwordControl.value).toEqual('Carson', 'Expected value to change on submit.');
expect(passwordControl.valid).toBe(true, 'Expected validation to run on submit.'); expect(passwordControl.valid).toBe(true, 'Expected validation to run on submit.');
}); });
}); });
}); });
describe('ngModel interactions', () => { describe('ngModel interactions', () => {
@ -1636,7 +1608,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
}); });
describe('deprecation warnings', () => { describe('deprecation warnings', () => {
it('should warn once by default when using ngModel with formControlName', fakeAsync(() => { it('should warn once by default when using ngModel with formControlName', fakeAsync(() => {
const fixture = initTest(FormGroupNgModel); const fixture = initTest(FormGroupNgModel);
fixture.componentInstance.form = fixture.componentInstance.form =
@ -1679,8 +1650,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
fakeAsync(() => { fakeAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [FormControlNgModel], declarations: [FormControlNgModel],
imports: imports: [ReactiveFormsModule.withConfig({warnOnNgModelWithFormControl: 'always'})]
[ReactiveFormsModule.withConfig({warnOnNgModelWithFormControl: 'always'})]
}); });
const fixture = TestBed.createComponent(FormControlNgModel); const fixture = TestBed.createComponent(FormControlNgModel);
@ -1710,7 +1680,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(warnSpy).not.toHaveBeenCalled(); expect(warnSpy).not.toHaveBeenCalled();
})); }));
}); });
it('should support ngModel for complex forms', fakeAsync(() => { it('should support ngModel for complex forms', fakeAsync(() => {
@ -1794,9 +1763,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(fixture.componentInstance.login) expect(fixture.componentInstance.login)
.toEqual('Nancy', 'Expected ngModel value to update on submit.'); .toEqual('Nancy', 'Expected ngModel value to update on submit.');
})); }));
}); });
describe('validations', () => { describe('validations', () => {
@ -2130,12 +2097,9 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(resultArr.length) expect(resultArr.length)
.toEqual(2, `Expected original observable to be canceled on the next value change.`); .toEqual(2, `Expected original observable to be canceled on the next value change.`);
})); }));
}); });
describe('errors', () => { describe('errors', () => {
it('should throw if a form isn\'t passed into formGroup', () => { it('should throw if a form isn\'t passed into formGroup', () => {
const fixture = initTest(FormGroupComp); const fixture = initTest(FormGroupComp);
@ -2335,11 +2299,9 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
expect(() => fixture.detectChanges()) expect(() => fixture.detectChanges())
.toThrowError(new RegExp('If you define both a name and a formControlName')); .toThrowError(new RegExp('If you define both a name and a formControlName'));
}); });
}); });
describe('IME events', () => { describe('IME events', () => {
it('should determine IME event handling depending on platform by default', () => { it('should determine IME event handling depending on platform by default', () => {
const fixture = initTest(FormControlComp); const fixture = initTest(FormControlComp);
fixture.componentInstance.control = new FormControl('oldValue'); fixture.componentInstance.control = new FormControl('oldValue');
@ -2417,16 +2379,16 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
// formControl should update normally // formControl should update normally
expect(fixture.componentInstance.control.value).toEqual('updatedValue'); expect(fixture.componentInstance.control.value).toEqual('updatedValue');
}); });
}); });
}); });
} }
function uniqLoginAsyncValidator(expectedValue: string, timeout: number = 0) { function uniqLoginAsyncValidator(expectedValue: string, timeout: number = 0) {
return (c: AbstractControl) => { return (c: AbstractControl) => {
let resolve: (result: any) => void; let resolve: (result: any) => void;
const promise = new Promise<any>(res => { resolve = res; }); const promise = new Promise<any>(res => {
resolve = res;
});
const res = (c.value == expectedValue) ? null : {'uniqLogin': true}; const res = (c.value == expectedValue) ? null : {'uniqLogin': true};
setTimeout(() => resolve(res), timeout); setTimeout(() => resolve(res), timeout);
return promise; return promise;
@ -2452,16 +2414,16 @@ class LoginIsEmptyValidator {
@Directive({ @Directive({
selector: '[uniq-login-validator]', selector: '[uniq-login-validator]',
providers: [{ providers: [
provide: NG_ASYNC_VALIDATORS, {provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => UniqLoginValidator), multi: true}
useExisting: forwardRef(() => UniqLoginValidator), ]
multi: true
}]
}) })
class UniqLoginValidator implements AsyncValidator { class UniqLoginValidator implements AsyncValidator {
@Input('uniq-login-validator') expected: any; @Input('uniq-login-validator') expected: any;
validate(c: AbstractControl) { return uniqLoginAsyncValidator(this.expected)(c); } validate(c: AbstractControl) {
return uniqLoginAsyncValidator(this.expected)(c);
}
} }
@Component({selector: 'form-control-comp', template: `<input type="text" [formControl]="control">`}) @Component({selector: 'form-control-comp', template: `<input type="text" [formControl]="control">`})

View File

@ -16,6 +16,10 @@ export class SpyChangeDetectorRef extends SpyObject {
} }
} }
export class SpyNgControl extends SpyObject { path = []; } export class SpyNgControl extends SpyObject {
path = [];
}
export class SpyValueAccessor extends SpyObject { writeValue: any; } export class SpyValueAccessor extends SpyObject {
writeValue: any;
}

View File

@ -7,8 +7,8 @@
*/ */
import {ɵgetDOM as getDOM} from '@angular/common'; import {ɵgetDOM as getDOM} from '@angular/common';
import {Component, Directive, Type, forwardRef} from '@angular/core'; import {Component, Directive, forwardRef, Type} from '@angular/core';
import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/testing'; import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {AbstractControl, AsyncValidator, COMPOSITION_BUFFER_MODE, FormControl, FormsModule, NG_ASYNC_VALIDATORS, NgForm, NgModel} from '@angular/forms'; import {AbstractControl, AsyncValidator, COMPOSITION_BUFFER_MODE, FormControl, FormsModule, NG_ASYNC_VALIDATORS, NgForm, NgModel} from '@angular/forms';
import {By} from '@angular/platform-browser/src/dom/debug/by'; import {By} from '@angular/platform-browser/src/dom/debug/by';
import {dispatchEvent, sortedClassList} from '@angular/platform-browser/testing/src/browser_util'; import {dispatchEvent, sortedClassList} from '@angular/platform-browser/testing/src/browser_util';
@ -18,7 +18,6 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
{ {
describe('template-driven forms integration tests', () => { describe('template-driven forms integration tests', () => {
function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> { function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [component, ...directives], imports: [FormsModule]}); {declarations: [component, ...directives], imports: [FormsModule]});
@ -192,7 +191,6 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
})); }));
it('should set status classes with ngModel and async validators', fakeAsync(() => { it('should set status classes with ngModel and async validators', fakeAsync(() => {
const fixture = initTest(NgModelAsyncValidation, NgAsyncValidator); const fixture = initTest(NgModelAsyncValidation, NgAsyncValidator);
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
@ -304,9 +302,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
}); });
describe('updateOn', () => { describe('updateOn', () => {
describe('blur', () => { describe('blur', () => {
it('should default updateOn to change', fakeAsync(() => { it('should default updateOn to change', fakeAsync(() => {
const fixture = initTest(NgModelForm); const fixture = initTest(NgModelForm);
fixture.componentInstance.name = ''; fixture.componentInstance.name = '';
@ -484,8 +480,8 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
const values: any[] = []; const values: any[] = [];
const form = fixture.debugElement.children[0].injector.get(NgForm); const form = fixture.debugElement.children[0].injector.get(NgForm);
const sub = merge(form.valueChanges !, form.statusChanges !) const sub =
.subscribe(val => values.push(val)); merge(form.valueChanges!, form.statusChanges!).subscribe(val => values.push(val));
const input = fixture.debugElement.query(By.css('input')).nativeElement; const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew'; input.value = 'Nancy Drew';
@ -560,13 +556,10 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
.toEqual( .toEqual(
['fired', 'fired'], ['fired', 'fired'],
'Expected ngModelChanges to fire again on blur if value changed.'); 'Expected ngModelChanges to fire again on blur if value changed.');
})); }));
}); });
describe('submit', () => { describe('submit', () => {
it('should set control updateOn to submit properly', fakeAsync(() => { it('should set control updateOn to submit properly', fakeAsync(() => {
const fixture = initTest(NgModelForm); const fixture = initTest(NgModelForm);
fixture.componentInstance.name = ''; fixture.componentInstance.name = '';
@ -816,8 +809,8 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
const values: any[] = []; const values: any[] = [];
const form = fixture.debugElement.children[0].injector.get(NgForm); const form = fixture.debugElement.children[0].injector.get(NgForm);
const sub = merge(form.valueChanges !, form.statusChanges !) const sub =
.subscribe(val => values.push(val)); merge(form.valueChanges!, form.statusChanges!).subscribe(val => values.push(val));
const input = fixture.debugElement.query(By.css('input')).nativeElement; const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew'; input.value = 'Nancy Drew';
@ -898,11 +891,9 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
['fired', 'fired'], ['fired', 'fired'],
'Expected ngModelChanges to fire again on submit if value changed.'); 'Expected ngModelChanges to fire again on submit if value changed.');
})); }));
}); });
describe('ngFormOptions', () => { describe('ngFormOptions', () => {
it('should use ngFormOptions value when ngModelOptions are not set', fakeAsync(() => { it('should use ngFormOptions value when ngModelOptions are not set', fakeAsync(() => {
const fixture = initTest(NgModelOptionsStandalone); const fixture = initTest(NgModelOptionsStandalone);
fixture.componentInstance.options = {name: 'two'}; fixture.componentInstance.options = {name: 'two'};
@ -1016,9 +1007,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
expect(fixture.componentInstance.two) expect(fixture.componentInstance.two)
.toEqual('Nancy Drew', 'Expected standalone ngModel not to inherit blur update.'); .toEqual('Nancy Drew', 'Expected standalone ngModel not to inherit blur update.');
})); }));
}); });
}); });
describe('submit and reset events', () => { describe('submit and reset events', () => {
@ -1116,8 +1105,9 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
fixture.detectChanges(); fixture.detectChanges();
tick(); tick();
form.get('name') !.valueChanges.subscribe( form.get('name')!.valueChanges.subscribe(() => {
() => { expect(form.get('name') !.dirty).toBe(true); }); expect(form.get('name')!.dirty).toBe(true);
});
const inputEl = fixture.debugElement.query(By.css('input')).nativeElement; const inputEl = fixture.debugElement.query(By.css('input')).nativeElement;
inputEl.value = 'newValue'; inputEl.value = 'newValue';
@ -1140,8 +1130,9 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
expect(form.get('name')!.pristine).toBe(false); expect(form.get('name')!.pristine).toBe(false);
form.get('name') !.valueChanges.subscribe( form.get('name')!.valueChanges.subscribe(() => {
() => { expect(form.get('name') !.pristine).toBe(true); }); expect(form.get('name')!.pristine).toBe(true);
});
dispatchEvent(formEl, 'reset'); dispatchEvent(formEl, 'reset');
})); }));
@ -1229,11 +1220,9 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
tick(); tick();
expect(input.nativeElement.disabled).toEqual(false); expect(input.nativeElement.disabled).toEqual(false);
})); }));
}); });
describe('validation directives', () => { describe('validation directives', () => {
it('required validator should validate checkbox', fakeAsync(() => { it('required validator should validate checkbox', fakeAsync(() => {
const fixture = initTest(NgModelCheckboxRequiredValidator); const fixture = initTest(NgModelCheckboxRequiredValidator);
fixture.detectChanges(); fixture.detectChanges();
@ -1522,7 +1511,6 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
expect(onNgModelChange).toHaveBeenCalledTimes(2); expect(onNgModelChange).toHaveBeenCalledTimes(2);
tick(); tick();
})); }));
}); });
describe('IME events', () => { describe('IME events', () => {
@ -1611,7 +1599,6 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
// ngModel should update normally // ngModel should update normally
expect(fixture.componentInstance.name).toEqual('updatedValue'); expect(fixture.componentInstance.name).toEqual('updatedValue');
})); }));
}); });
describe('ngModel corner cases', () => { describe('ngModel corner cases', () => {
@ -1650,7 +1637,6 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
expect(() => fixture.detectChanges()).not.toThrowError(); expect(() => fixture.detectChanges()).not.toThrowError();
})); }));
}); });
}); });
} }
@ -1847,12 +1833,13 @@ class NgModelEmailValidator {
@Directive({ @Directive({
selector: '[ng-async-validator]', selector: '[ng-async-validator]',
providers: [ providers:
{provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => NgAsyncValidator), multi: true} [{provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => NgAsyncValidator), multi: true}]
]
}) })
class NgAsyncValidator implements AsyncValidator { class NgAsyncValidator implements AsyncValidator {
validate(c: AbstractControl) { return Promise.resolve(null); } validate(c: AbstractControl) {
return Promise.resolve(null);
}
} }
@Component({ @Component({
@ -1877,7 +1864,9 @@ class NgModelChangesForm {
events: string[] = []; events: string[] = [];
options: any; options: any;
log() { this.events.push('fired'); } log() {
this.events.push('fired');
}
} }
@Component({ @Component({

View File

@ -37,17 +37,21 @@ import {first, map} from 'rxjs/operators';
describe('Validators', () => { describe('Validators', () => {
describe('min', () => { describe('min', () => {
it('should not error on an empty string', it('should not error on an empty string', () => {
() => { expect(Validators.min(2)(new FormControl(''))).toBeNull(); }); expect(Validators.min(2)(new FormControl(''))).toBeNull();
});
it('should not error on null', it('should not error on null', () => {
() => { expect(Validators.min(2)(new FormControl(null))).toBeNull(); }); expect(Validators.min(2)(new FormControl(null))).toBeNull();
});
it('should not error on undefined', it('should not error on undefined', () => {
() => { expect(Validators.min(2)(new FormControl(undefined))).toBeNull(); }); expect(Validators.min(2)(new FormControl(undefined))).toBeNull();
});
it('should return null if NaN after parsing', it('should return null if NaN after parsing', () => {
() => { expect(Validators.min(2)(new FormControl('a'))).toBeNull(); }); expect(Validators.min(2)(new FormControl('a'))).toBeNull();
});
it('should return a validation error on small values', () => { it('should return a validation error on small values', () => {
expect(Validators.min(2)(new FormControl(1))).toEqual({'min': {'min': 2, 'actual': 1}}); expect(Validators.min(2)(new FormControl(1))).toEqual({'min': {'min': 2, 'actual': 1}});
@ -57,14 +61,17 @@ import {first, map} from 'rxjs/operators';
expect(Validators.min(2)(new FormControl('1'))).toEqual({'min': {'min': 2, 'actual': '1'}}); expect(Validators.min(2)(new FormControl('1'))).toEqual({'min': {'min': 2, 'actual': '1'}});
}); });
it('should not error on big values', it('should not error on big values', () => {
() => { expect(Validators.min(2)(new FormControl(3))).toBeNull(); }); expect(Validators.min(2)(new FormControl(3))).toBeNull();
});
it('should not error on equal values', it('should not error on equal values', () => {
() => { expect(Validators.min(2)(new FormControl(2))).toBeNull(); }); expect(Validators.min(2)(new FormControl(2))).toBeNull();
});
it('should not error on equal values when value is string', it('should not error on equal values when value is string', () => {
() => { expect(Validators.min(2)(new FormControl('2'))).toBeNull(); }); expect(Validators.min(2)(new FormControl('2'))).toBeNull();
});
it('should validate as expected when min value is a string', () => { it('should validate as expected when min value is a string', () => {
expect(Validators.min('2' as any)(new FormControl(1))).toEqual({ expect(Validators.min('2' as any)(new FormControl(1))).toEqual({
@ -72,25 +79,31 @@ import {first, map} from 'rxjs/operators';
}); });
}); });
it('should return null if min value is undefined', it('should return null if min value is undefined', () => {
() => { expect(Validators.min(undefined as any)(new FormControl(3))).toBeNull(); }); expect(Validators.min(undefined as any)(new FormControl(3))).toBeNull();
});
it('should return null if min value is null', it('should return null if min value is null', () => {
() => { expect(Validators.min(null as any)(new FormControl(3))).toBeNull(); }); expect(Validators.min(null as any)(new FormControl(3))).toBeNull();
});
}); });
describe('max', () => { describe('max', () => {
it('should not error on an empty string', it('should not error on an empty string', () => {
() => { expect(Validators.max(2)(new FormControl(''))).toBeNull(); }); expect(Validators.max(2)(new FormControl(''))).toBeNull();
});
it('should not error on null', it('should not error on null', () => {
() => { expect(Validators.max(2)(new FormControl(null))).toBeNull(); }); expect(Validators.max(2)(new FormControl(null))).toBeNull();
});
it('should not error on undefined', it('should not error on undefined', () => {
() => { expect(Validators.max(2)(new FormControl(undefined))).toBeNull(); }); expect(Validators.max(2)(new FormControl(undefined))).toBeNull();
});
it('should return null if NaN after parsing', it('should return null if NaN after parsing', () => {
() => { expect(Validators.max(2)(new FormControl('aaa'))).toBeNull(); }); expect(Validators.max(2)(new FormControl('aaa'))).toBeNull();
});
it('should return a validation error on big values', () => { it('should return a validation error on big values', () => {
expect(Validators.max(2)(new FormControl(3))).toEqual({'max': {'max': 2, 'actual': 3}}); expect(Validators.max(2)(new FormControl(3))).toEqual({'max': {'max': 2, 'actual': 3}});
@ -100,14 +113,17 @@ import {first, map} from 'rxjs/operators';
expect(Validators.max(2)(new FormControl('3'))).toEqual({'max': {'max': 2, 'actual': '3'}}); expect(Validators.max(2)(new FormControl('3'))).toEqual({'max': {'max': 2, 'actual': '3'}});
}); });
it('should not error on small values', it('should not error on small values', () => {
() => { expect(Validators.max(2)(new FormControl(1))).toBeNull(); }); expect(Validators.max(2)(new FormControl(1))).toBeNull();
});
it('should not error on equal values', it('should not error on equal values', () => {
() => { expect(Validators.max(2)(new FormControl(2))).toBeNull(); }); expect(Validators.max(2)(new FormControl(2))).toBeNull();
});
it('should not error on equal values when value is string', it('should not error on equal values when value is string', () => {
() => { expect(Validators.max(2)(new FormControl('2'))).toBeNull(); }); expect(Validators.max(2)(new FormControl('2'))).toBeNull();
});
it('should validate as expected when max value is a string', () => { it('should validate as expected when max value is a string', () => {
expect(Validators.max('2' as any)(new FormControl(3))).toEqual({ expect(Validators.max('2' as any)(new FormControl(3))).toEqual({
@ -115,30 +131,36 @@ import {first, map} from 'rxjs/operators';
}); });
}); });
it('should return null if max value is undefined', it('should return null if max value is undefined', () => {
() => { expect(Validators.max(undefined as any)(new FormControl(3))).toBeNull(); }); expect(Validators.max(undefined as any)(new FormControl(3))).toBeNull();
});
it('should return null if max value is null', it('should return null if max value is null', () => {
() => { expect(Validators.max(null as any)(new FormControl(3))).toBeNull(); }); expect(Validators.max(null as any)(new FormControl(3))).toBeNull();
});
}); });
describe('required', () => { describe('required', () => {
it('should error on an empty string', it('should error on an empty string', () => {
() => { expect(Validators.required(new FormControl(''))).toEqual({'required': true}); }); expect(Validators.required(new FormControl(''))).toEqual({'required': true});
});
it('should error on null', it('should error on null', () => {
() => { expect(Validators.required(new FormControl(null))).toEqual({'required': true}); }); expect(Validators.required(new FormControl(null))).toEqual({'required': true});
});
it('should not error on undefined', () => { it('should not error on undefined', () => {
expect(Validators.required(new FormControl(undefined))).toEqual({'required': true}); expect(Validators.required(new FormControl(undefined))).toEqual({'required': true});
}); });
it('should not error on a non-empty string', it('should not error on a non-empty string', () => {
() => { expect(Validators.required(new FormControl('not empty'))).toBeNull(); }); expect(Validators.required(new FormControl('not empty'))).toBeNull();
});
it('should accept zero as valid', it('should accept zero as valid', () => {
() => { expect(Validators.required(new FormControl(0))).toBeNull(); }); expect(Validators.required(new FormControl(0))).toBeNull();
});
it('should error on an empty array', it('should error on an empty array',
() => expect(Validators.required(new FormControl([]))).toEqual({'required': true})); () => expect(Validators.required(new FormControl([]))).toEqual({'required': true}));
@ -170,17 +192,21 @@ import {first, map} from 'rxjs/operators';
}); });
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();
});
it('should not error on null', it('should not error on null', () => {
() => { expect(Validators.minLength(2)(new FormControl(null))).toBeNull(); }); expect(Validators.minLength(2)(new FormControl(null))).toBeNull();
});
it('should not error on undefined', it('should not error on undefined', () => {
() => { expect(Validators.minLength(2)(new FormControl(undefined))).toBeNull(); }); expect(Validators.minLength(2)(new FormControl(undefined))).toBeNull();
});
it('should not error on valid strings', it('should not error on valid strings', () => {
() => { expect(Validators.minLength(2)(new FormControl('aa'))).toBeNull(); }); expect(Validators.minLength(2)(new FormControl('aa'))).toBeNull();
});
it('should error on short strings', () => { it('should error on short strings', () => {
expect(Validators.minLength(2)(new FormControl('a'))).toEqual({ expect(Validators.minLength(2)(new FormControl('a'))).toEqual({
@ -202,17 +228,21 @@ import {first, map} from 'rxjs/operators';
}); });
describe('maxLength', () => { describe('maxLength', () => {
it('should not error on an empty string', it('should not error on an empty string', () => {
() => { expect(Validators.maxLength(2)(new FormControl(''))).toBeNull(); }); expect(Validators.maxLength(2)(new FormControl(''))).toBeNull();
});
it('should not error on null', it('should not error on null', () => {
() => { expect(Validators.maxLength(2)(new FormControl(null))).toBeNull(); }); expect(Validators.maxLength(2)(new FormControl(null))).toBeNull();
});
it('should not error on undefined', it('should not error on undefined', () => {
() => { expect(Validators.maxLength(2)(new FormControl(undefined))).toBeNull(); }); expect(Validators.maxLength(2)(new FormControl(undefined))).toBeNull();
});
it('should not error on valid strings', it('should not error on valid strings', () => {
() => { expect(Validators.maxLength(2)(new FormControl('aa'))).toBeNull(); }); expect(Validators.maxLength(2)(new FormControl('aa'))).toBeNull();
});
it('should error on long strings', () => { it('should error on long strings', () => {
expect(Validators.maxLength(2)(new FormControl('aaa'))).toEqual({ expect(Validators.maxLength(2)(new FormControl('aaa'))).toEqual({
@ -234,18 +264,21 @@ import {first, map} from 'rxjs/operators';
}); });
describe('pattern', () => { describe('pattern', () => {
it('should not error on an empty string', it('should not error on an empty string', () => {
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(''))).toBeNull(); }); expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(''))).toBeNull();
});
it('should not error on null', it('should not error on null', () => {
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); }); expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull();
});
it('should not error on undefined', () => { it('should not error on undefined', () => {
expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(undefined))).toBeNull(); expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(undefined))).toBeNull();
}); });
it('should not error on null value and "null" pattern', it('should not error on null value and "null" pattern', () => {
() => { expect(Validators.pattern('null')(new FormControl(null))).toBeNull(); }); expect(Validators.pattern('null')(new FormControl(null))).toBeNull();
});
it('should not error on valid strings', it('should not error on valid strings',
() => expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaAA'))).toBeNull()); () => expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaAA'))).toBeNull());
@ -288,8 +321,9 @@ import {first, map} from 'rxjs/operators';
}); });
describe('compose', () => { describe('compose', () => {
it('should return null when given null', it('should return null when given null', () => {
() => { expect(Validators.compose(null !)).toBe(null); }); expect(Validators.compose(null!)).toBe(null);
});
it('should collect errors from all the validators', () => { it('should collect errors from all the validators', () => {
const c = Validators.compose([validator('a', true), validator('b', true)])!; const c = Validators.compose([validator('a', true), validator('b', true)])!;
@ -313,7 +347,6 @@ import {first, map} from 'rxjs/operators';
}); });
describe('composeAsync', () => { describe('composeAsync', () => {
describe('promises', () => { describe('promises', () => {
function promiseValidator(response: {[key: string]: any}): AsyncValidatorFn { function promiseValidator(response: {[key: string]: any}): AsyncValidatorFn {
return (c: AbstractControl) => { return (c: AbstractControl) => {
@ -322,8 +355,9 @@ import {first, map} from 'rxjs/operators';
}; };
} }
it('should return null when given null', it('should return null when given null', () => {
() => { expect(Validators.composeAsync(null !)).toBeNull(); }); expect(Validators.composeAsync(null!)).toBeNull();
});
it('should collect errors from all the validators', fakeAsync(() => { it('should collect errors from all the validators', fakeAsync(() => {
const v = Validators.composeAsync( const v = Validators.composeAsync(
@ -339,8 +373,8 @@ import {first, map} from 'rxjs/operators';
})); }));
it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => { it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => {
const v = Validators.composeAsync([normalizeAsyncValidator( const v = Validators.composeAsync(
new AsyncValidatorDirective('expected', {'one': true}))]) !; [normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))])!;
let errorMap: {[key: string]: any}|null = undefined!; let errorMap: {[key: string]: any}|null = undefined!;
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>) (v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
@ -384,8 +418,9 @@ import {first, map} from 'rxjs/operators';
}; };
} }
it('should return null when given null', it('should return null when given null', () => {
() => { expect(Validators.composeAsync(null !)).toBeNull(); }); expect(Validators.composeAsync(null!)).toBeNull();
});
it('should collect errors from all the validators', () => { it('should collect errors from all the validators', () => {
const v = Validators.composeAsync( const v = Validators.composeAsync(
@ -435,7 +470,9 @@ import {first, map} from 'rxjs/operators';
it('should wait for all validators before setting errors', fakeAsync(() => { it('should wait for all validators before setting errors', fakeAsync(() => {
function getTimerObs(time: number, errorMap: {[key: string]: any}): AsyncValidatorFn { function getTimerObs(time: number, errorMap: {[key: string]: any}): AsyncValidatorFn {
return (c: AbstractControl) => { return timer(time).pipe(map(() => errorMap)); }; return (c: AbstractControl) => {
return timer(time).pipe(map(() => errorMap));
};
} }
const v = Validators.composeAsync( const v = Validators.composeAsync(
@ -451,13 +488,10 @@ import {first, map} from 'rxjs/operators';
`Expected errors not to be set until all validators came back.`); `Expected errors not to be set until all validators came back.`);
tick(100); tick(100);
expect(errorMap !) expect(errorMap!).toEqual(
.toEqual( {one: true, two: true}, `Expected errors to merge once all validators resolved.`);
{one: true, two: true},
`Expected errors to merge once all validators resolved.`);
})); }));
}); });
}); });
}); });
})(); })();

View File

@ -7,14 +7,13 @@
*/ */
import {Component, Directive, EventEmitter, Input, Output, Type, ViewChild} from '@angular/core'; import {Component, Directive, EventEmitter, Input, Output, Type, ViewChild} from '@angular/core';
import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/testing'; import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, NgForm, NgModel, ReactiveFormsModule, Validators} from '@angular/forms'; import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, NgForm, NgModel, ReactiveFormsModule, Validators} from '@angular/forms';
import {By} from '@angular/platform-browser/src/dom/debug/by'; import {By} from '@angular/platform-browser/src/dom/debug/by';
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'; import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
{ {
describe('value accessors', () => { describe('value accessors', () => {
function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> { function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [component, ...directives], imports: [FormsModule, ReactiveFormsModule]}); {declarations: [component, ...directives], imports: [FormsModule, ReactiveFormsModule]});
@ -64,7 +63,11 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
fixture.detectChanges(); fixture.detectChanges();
const input = fixture.debugElement.query(By.css('input')); const input = fixture.debugElement.query(By.css('input'));
form.valueChanges.subscribe({next: (value) => { throw 'Should not happen'; }}); form.valueChanges.subscribe({
next: (value) => {
throw 'Should not happen';
}
});
input.nativeElement.value = 'updatedValue'; input.nativeElement.value = 'updatedValue';
dispatchEvent(input.nativeElement, 'change'); dispatchEvent(input.nativeElement, 'change');
@ -160,9 +163,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
}); });
describe('select controls', () => { describe('select controls', () => {
describe('in reactive forms', () => { describe('in reactive forms', () => {
it(`should support primitive values`, () => { it(`should support primitive values`, () => {
if (isNode) return; if (isNode) return;
const fixture = initTest(FormControlNameSelect); const fixture = initTest(FormControlNameSelect);
@ -238,7 +239,6 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
expect(select.nativeElement.value).toEqual('3: Object'); expect(select.nativeElement.value).toEqual('3: Object');
expect(nyOption.nativeElement.selected).toBe(true); expect(nyOption.nativeElement.selected).toBe(true);
}); });
}); });
describe('in template-driven forms', () => { describe('in template-driven forms', () => {
@ -398,16 +398,11 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
expect(select.nativeElement.value).toEqual('3: Object'); expect(select.nativeElement.value).toEqual('3: Object');
expect(nyOption.nativeElement.selected).toBe(true); expect(nyOption.nativeElement.selected).toBe(true);
})); }));
}); });
}); });
describe('select multiple controls', () => { describe('select multiple controls', () => {
describe('in reactive forms', () => { describe('in reactive forms', () => {
it('should support primitive values', () => { it('should support primitive values', () => {
if (isNode) return; if (isNode) return;
const fixture = initTest(FormControlSelectMultiple); const fixture = initTest(FormControlSelectMultiple);
@ -448,7 +443,6 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
expect(select.nativeElement.value).toEqual('0: Object'); expect(select.nativeElement.value).toEqual('0: Object');
expect(sfOption.nativeElement.selected).toBe(true); expect(sfOption.nativeElement.selected).toBe(true);
})); }));
}); });
describe('in template-driven forms', () => { describe('in template-driven forms', () => {
@ -539,13 +533,10 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
expect(select.nativeElement.value).toEqual('0: Object'); expect(select.nativeElement.value).toEqual('0: Object');
expect(sfOption.nativeElement.selected).toBe(true); expect(sfOption.nativeElement.selected).toBe(true);
})); }));
}); });
describe('should support <type=radio>', () => { describe('should support <type=radio>', () => {
describe('in reactive forms', () => { describe('in reactive forms', () => {
it('should support basic functionality', () => { it('should support basic functionality', () => {
const fixture = initTest(FormControlRadioButtons); const fixture = initTest(FormControlRadioButtons);
const form = const form =
@ -712,7 +703,6 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
expect(inputs[1].nativeElement.checked).toEqual(false); expect(inputs[1].nativeElement.checked).toEqual(false);
expect(inputs[2].nativeElement.checked).toEqual(false); expect(inputs[2].nativeElement.checked).toEqual(false);
expect(inputs[3].nativeElement.checked).toEqual(true); expect(inputs[3].nativeElement.checked).toEqual(true);
}); });
it('should disable all radio buttons when disable() is called', () => { it('should disable all radio buttons when disable() is called', () => {
@ -780,7 +770,6 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
expect(inputs[0].nativeElement.checked).toBe(false); expect(inputs[0].nativeElement.checked).toBe(false);
expect(inputs[1].nativeElement.checked).toBe(true); expect(inputs[1].nativeElement.checked).toBe(true);
}); });
}); });
describe('in template-driven forms', () => { describe('in template-driven forms', () => {
@ -911,15 +900,11 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
expect(inputs[2].nativeElement.disabled).toBe(false); expect(inputs[2].nativeElement.disabled).toBe(false);
expect(inputs[3].nativeElement.disabled).toBe(false); expect(inputs[3].nativeElement.disabled).toBe(false);
})); }));
}); });
}); });
describe('should support <type=range>', () => { describe('should support <type=range>', () => {
describe('in reactive forms', () => { describe('in reactive forms', () => {
it('with basic use case', () => { it('with basic use case', () => {
const fixture = initTest(FormControlRangeInput); const fixture = initTest(FormControlRangeInput);
const control = new FormControl(10); const control = new FormControl(10);
@ -968,7 +953,6 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
const input = fixture.debugElement.query(By.css('input')); const input = fixture.debugElement.query(By.css('input'));
expect(input.nativeElement.value).toEqual(''); expect(input.nativeElement.value).toEqual('');
}); });
}); });
describe('in template-driven forms', () => { describe('in template-driven forms', () => {
@ -989,13 +973,10 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
fixture.detectChanges(); fixture.detectChanges();
expect(typeof (fixture.componentInstance.val)).toBe('number'); expect(typeof (fixture.componentInstance.val)).toBe('number');
})); }));
}); });
}); });
describe('custom value accessors', () => { describe('custom value accessors', () => {
describe('in reactive forms', () => { describe('in reactive forms', () => {
it('should support basic functionality', () => { it('should support basic functionality', () => {
const fixture = initTest(WrappedValueForm, WrappedValue); const fixture = initTest(WrappedValueForm, WrappedValue);
@ -1089,9 +1070,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
}); });
})); }));
}); });
}); });
}); });
} }
@ -1353,14 +1332,22 @@ class WrappedValue implements ControlValueAccessor {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
onChange!: Function; onChange!: Function;
writeValue(value: any) { this.value = `!${value}!`; } writeValue(value: any) {
this.value = `!${value}!`;
}
registerOnChange(fn: (value: any) => void) { this.onChange = fn; } registerOnChange(fn: (value: any) => void) {
this.onChange = fn;
}
registerOnTouched(fn: any) {} registerOnTouched(fn: any) {}
handleOnInput(value: any) { this.onChange(value.substring(1, value.length - 1)); } handleOnInput(value: any) {
this.onChange(value.substring(1, value.length - 1));
}
validate(c: AbstractControl) { return c.value === 'expected' ? null : {'err': true}; } validate(c: AbstractControl) {
return c.value === 'expected' ? null : {'err': true};
}
} }
@Component({selector: 'my-input', template: ''}) @Component({selector: 'my-input', template: ''})
@ -1371,17 +1358,27 @@ export class MyInput implements ControlValueAccessor {
control: AbstractControl|null = null; control: AbstractControl|null = null;
constructor(public controlDir: NgControl) { controlDir.valueAccessor = this; } constructor(public controlDir: NgControl) {
controlDir.valueAccessor = this;
}
ngOnInit() { this.control = this.controlDir.control; } ngOnInit() {
this.control = this.controlDir.control;
}
writeValue(value: any) { this.value = `!${value}!`; } writeValue(value: any) {
this.value = `!${value}!`;
}
registerOnChange(fn: (value: any) => void) { this.onInput.subscribe({next: fn}); } registerOnChange(fn: (value: any) => void) {
this.onInput.subscribe({next: fn});
}
registerOnTouched(fn: any) {} registerOnTouched(fn: any) {}
dispatchChangeEvent() { this.onInput.emit(this.value.substring(1, this.value.length - 1)); } dispatchChangeEvent() {
this.onInput.emit(this.value.substring(1, this.value.length - 1));
}
} }
@Component({ @Component({
@ -1423,13 +1420,19 @@ export class NgModelCustomComp implements ControlValueAccessor {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
changeFn!: (value: any) => void; changeFn!: (value: any) => void;
writeValue(value: any) { this.model = value; } writeValue(value: any) {
this.model = value;
}
registerOnChange(fn: (value: any) => void) { this.changeFn = fn; } registerOnChange(fn: (value: any) => void) {
this.changeFn = fn;
}
registerOnTouched() {} registerOnTouched() {}
setDisabledState(isDisabled: boolean) { this.isDisabled = isDisabled; } setDisabledState(isDisabled: boolean) {
this.isDisabled = isDisabled;
}
} }
@Component({ @Component({