style(forms): reformat of the `forms` package after clang update (#36466)
PR Close #36466
This commit is contained in:
parent
416c786774
commit
7d0af179e3
|
@ -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
|
||||||
|
|
|
@ -31,7 +31,7 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
_parent !: ControlContainer;
|
_parent!: ControlContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -40,7 +40,7 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
_validators !: any[];
|
_validators!: any[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -49,7 +49,7 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
_asyncValidators !: any[];
|
_asyncValidators!: any[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -58,7 +58,7 @@ export class AbstractFormGroupDirective extends ControlContainer implements OnIn
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this._checkParentType();
|
this._checkParentType();
|
||||||
this.formDirective !.addFormGroup(this);
|
this.formDirective!.addFormGroup(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -23,17 +23,21 @@ export abstract class ControlContainer extends AbstractControlDirective {
|
||||||
* The name for the control
|
* The name for the control
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
name !: string | number | null;
|
name!: string|number|null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -128,7 +127,7 @@ export class NgForm extends ControlContainer implements Form,
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input('ngFormOptions') options !: {updateOn?: FormHooks};
|
@Input('ngFormOptions') options!: {updateOn?: FormHooks};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
|
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
|
||||||
|
@ -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
|
||||||
|
@ -179,7 +188,7 @@ export class NgForm extends ControlContainer implements Form,
|
||||||
addControl(dir: NgModel): void {
|
addControl(dir: NgModel): void {
|
||||||
resolvedPromise.then(() => {
|
resolvedPromise.then(() => {
|
||||||
const container = this._findContainer(dir.path);
|
const container = this._findContainer(dir.path);
|
||||||
(dir as{control: FormControl}).control =
|
(dir as {control: FormControl}).control =
|
||||||
<FormControl>container.registerControl(dir.name, dir.control);
|
<FormControl>container.registerControl(dir.name, dir.control);
|
||||||
setUpControl(dir.control, dir);
|
setUpControl(dir.control, dir);
|
||||||
dir.control.updateValueAndValidity({emitEvent: false});
|
dir.control.updateValueAndValidity({emitEvent: false});
|
||||||
|
@ -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.
|
||||||
|
@ -258,7 +271,7 @@ export class NgForm extends ControlContainer implements Form,
|
||||||
*/
|
*/
|
||||||
updateModel(dir: NgControl, value: any): void {
|
updateModel(dir: NgControl, value: any): void {
|
||||||
resolvedPromise.then(() => {
|
resolvedPromise.then(() => {
|
||||||
const ctrl = <FormControl>this.form.get(dir.path !);
|
const ctrl = <FormControl>this.form.get(dir.path!);
|
||||||
ctrl.setValue(value);
|
ctrl.setValue(value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -279,7 +294,7 @@ export class NgForm extends ControlContainer implements Form,
|
||||||
* @param $event The "submit" event object
|
* @param $event The "submit" event object
|
||||||
*/
|
*/
|
||||||
onSubmit($event: Event): boolean {
|
onSubmit($event: Event): boolean {
|
||||||
(this as{submitted: boolean}).submitted = true;
|
(this as {submitted: boolean}).submitted = true;
|
||||||
syncPendingControls(this.form, this._directives);
|
syncPendingControls(this.form, this._directives);
|
||||||
this.ngSubmit.emit($event);
|
this.ngSubmit.emit($event);
|
||||||
return false;
|
return false;
|
||||||
|
@ -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
|
||||||
|
@ -299,7 +316,7 @@ export class NgForm extends ControlContainer implements Form,
|
||||||
*/
|
*/
|
||||||
resetForm(value: any = undefined): void {
|
resetForm(value: any = undefined): void {
|
||||||
this.form.reset(value);
|
this.form.reset(value);
|
||||||
(this as{submitted: boolean}).submitted = false;
|
(this as {submitted: boolean}).submitted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setUpdateStrategy() {
|
private _setUpdateStrategy() {
|
||||||
|
|
|
@ -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".
|
||||||
|
@ -161,14 +160,14 @@ export class NgModel extends NgControl implements OnChanges,
|
||||||
* uses this name as a key to retrieve this control's value.
|
* uses this name as a key to retrieve this control's value.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input() name !: string;
|
@Input() name!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* Tracks whether the control is disabled.
|
* Tracks whether the control is disabled.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input('disabled') isDisabled !: boolean;
|
@Input('disabled') isDisabled!: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -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,151 +200,156 @@ export class NgModel extends NgControl implements OnChanges,
|
||||||
*/
|
*/
|
||||||
@Output('ngModelChange') update = new EventEmitter();
|
@Output('ngModelChange') update = new EventEmitter();
|
||||||
|
|
||||||
constructor(@Optional() @Host() parent: ControlContainer,
|
constructor(
|
||||||
@Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>,
|
@Optional() @Host() parent: ControlContainer,
|
||||||
@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>,
|
||||||
super();
|
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
|
||||||
this._parent = parent;
|
super();
|
||||||
this._rawValidators = validators || [];
|
this._parent = parent;
|
||||||
this._rawAsyncValidators = asyncValidators || [];
|
this._rawValidators = validators || [];
|
||||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
this._rawAsyncValidators = asyncValidators || [];
|
||||||
}
|
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* A lifecycle method called when the directive's inputs change. For internal use
|
* A lifecycle method called when the directive's inputs change. For internal use
|
||||||
* only.
|
* only.
|
||||||
*
|
*
|
||||||
* @param changes A object of key/value pairs for the set of changed inputs.
|
* @param changes A object of key/value pairs for the set of changed inputs.
|
||||||
*/
|
*/
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
this._checkForErrors();
|
this._checkForErrors();
|
||||||
if (!this._registered) this._setUpControl();
|
if (!this._registered) this._setUpControl();
|
||||||
if ('isDisabled' in changes) {
|
if ('isDisabled' in changes) {
|
||||||
this._updateDisabled(changes);
|
this._updateDisabled(changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||||
this._updateValue(this.model);
|
this._updateValue(this.model);
|
||||||
this.viewModel = this.model;
|
this.viewModel = this.model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* 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
|
||||||
* 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[] {
|
get path(): string[] {
|
||||||
return this._parent ? controlPath(this.name, this._parent) : [this.name];
|
return this._parent ? controlPath(this.name, this._parent) : [this.name];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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
|
||||||
* Async validator function composed of all the async validators registered with this
|
* Async validator function composed of all the async validators registered with this
|
||||||
* directive.
|
* directive.
|
||||||
*/
|
*/
|
||||||
get asyncValidator(): AsyncValidatorFn|null {
|
get asyncValidator(): AsyncValidatorFn|null {
|
||||||
return composeAsyncValidators(this._rawAsyncValidators);
|
return composeAsyncValidators(this._rawAsyncValidators);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* Sets the new value for the view model and emits an `ngModelChange` event.
|
* Sets the new value for the view model and emits an `ngModelChange` event.
|
||||||
*
|
*
|
||||||
* @param newValue The new value emitted by `ngModelChange`.
|
* @param newValue The new value emitted by `ngModelChange`.
|
||||||
*/
|
*/
|
||||||
viewToModelUpdate(newValue: any): void {
|
viewToModelUpdate(newValue: any): void {
|
||||||
this.viewModel = newValue;
|
this.viewModel = newValue;
|
||||||
this.update.emit(newValue);
|
this.update.emit(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private _setUpdateStrategy(): void {
|
private _setUpdateStrategy(): void {
|
||||||
if (this.options && this.options.updateOn != null) {
|
if (this.options && this.options.updateOn != null) {
|
||||||
this.control._updateOn = this.options.updateOn;
|
this.control._updateOn = this.options.updateOn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isStandalone(): boolean {
|
private _isStandalone(): boolean {
|
||||||
return !this._parent || !!(this.options && this.options.standalone);
|
return !this._parent || !!(this.options && this.options.standalone);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setUpStandalone(): void {
|
private _setUpStandalone(): void {
|
||||||
setUpControl(this.control, this);
|
setUpControl(this.control, this);
|
||||||
this.control.updateValueAndValidity({emitEvent: false});
|
this.control.updateValueAndValidity({emitEvent: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _checkForErrors(): void {
|
private _checkForErrors(): void {
|
||||||
if (!this._isStandalone()) {
|
if (!this._isStandalone()) {
|
||||||
this._checkParentType();
|
this._checkParentType();
|
||||||
}
|
}
|
||||||
this._checkName();
|
this._checkName();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _checkParentType(): void {
|
private _checkParentType(): void {
|
||||||
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();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private _checkName(): void {
|
private _checkName(): void {
|
||||||
if (this.options && this.options.name) this.name = this.options.name;
|
if (this.options && this.options.name) this.name = this.options.name;
|
||||||
|
|
||||||
if (!this._isStandalone() && !this.name) {
|
if (!this._isStandalone() && !this.name) {
|
||||||
TemplateDrivenErrors.missingNameException();
|
TemplateDrivenErrors.missingNameException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
this.control.disable();
|
this.control.disable();
|
||||||
} else if (!isDisabled && this.control.disabled) {
|
} else if (!isDisabled && this.control.disabled) {
|
||||||
this.control.enable();
|
this.control.enable();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ export class NgModelGroup extends AbstractFormGroupDirective implements OnInit,
|
||||||
* to a key in the parent `NgForm`.
|
* to a key in the parent `NgForm`.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input('ngModelGroup') name !: string;
|
@Input('ngModelGroup') name!: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Host() @SkipSelf() parent: ControlContainer,
|
@Host() @SkipSelf() parent: ControlContainer,
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import {AbstractControl} from '../model';
|
import {AbstractControl} from '../model';
|
||||||
import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators';
|
import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators';
|
||||||
|
|
||||||
export function normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
|
export function normalizeValidator(validator: ValidatorFn|Validator): ValidatorFn {
|
||||||
if ((<Validator>validator).validate) {
|
if ((<Validator>validator).validate) {
|
||||||
return (c: AbstractControl) => (<Validator>validator).validate(c);
|
return (c: AbstractControl) => (<Validator>validator).validate(c);
|
||||||
} else {
|
} else {
|
||||||
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,17 +93,16 @@ 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;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
_control !: NgControl;
|
_control!: NgControl;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
_fn !: Function;
|
_fn!: Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -122,7 +121,7 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
|
||||||
* Tracks the name of the radio input element.
|
* Tracks the name of the radio input element.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input() name !: string;
|
@Input() name!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -130,7 +129,7 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
|
||||||
* to a key in the parent `FormGroup` or `FormArray`.
|
* to a key in the parent `FormGroup` or `FormArray`.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input() formControlName !: string;
|
@Input() formControlName!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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';
|
||||||
|
@ -129,14 +129,16 @@ export class FormControlDirective extends NgControl implements OnChanges {
|
||||||
* Tracks the `FormControl` instance bound to the directive.
|
* Tracks the `FormControl` instance bound to the directive.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input('formControl') form !: FormControl;
|
@Input('formControl') form!: FormControl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* 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,81 +166,88 @@ 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[],
|
||||||
super();
|
@Optional() @Inject(NG_MODEL_WITH_FORM_CONTROL_WARNING) private _ngModelWarningConfig: string|
|
||||||
this._rawValidators = validators || [];
|
null) {
|
||||||
this._rawAsyncValidators = asyncValidators || [];
|
super();
|
||||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
this._rawValidators = validators || [];
|
||||||
}
|
this._rawAsyncValidators = asyncValidators || [];
|
||||||
|
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* A lifecycle method called when the directive's inputs change. For internal use
|
* A lifecycle method called when the directive's inputs change. For internal use
|
||||||
* only.
|
* only.
|
||||||
*
|
*
|
||||||
* @param changes A object of key/value pairs for the set of changed inputs.
|
* @param changes A object of key/value pairs for the set of changed inputs.
|
||||||
*/
|
*/
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
if (this._isControlChanged(changes)) {
|
if (this._isControlChanged(changes)) {
|
||||||
setUpControl(this.form, this);
|
setUpControl(this.form, this);
|
||||||
if (this.control.disabled && this.valueAccessor !.setDisabledState) {
|
if (this.control.disabled && this.valueAccessor!.setDisabledState) {
|
||||||
this.valueAccessor !.setDisabledState !(true);
|
this.valueAccessor!.setDisabledState!(true);
|
||||||
}
|
}
|
||||||
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;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* 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
|
||||||
* Async validator function composed of all the async validators registered with this
|
* Async validator function composed of all the async validators registered with this
|
||||||
* directive.
|
* directive.
|
||||||
*/
|
*/
|
||||||
get asyncValidator(): AsyncValidatorFn|null {
|
get asyncValidator(): AsyncValidatorFn|null {
|
||||||
return composeAsyncValidators(this._rawAsyncValidators);
|
return composeAsyncValidators(this._rawAsyncValidators);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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
|
||||||
* Sets the new value for the view model and emits an `ngModelChange` event.
|
* Sets the new value for the view model and emits an `ngModelChange` event.
|
||||||
*
|
*
|
||||||
* @param newValue The new value for the view model.
|
* @param newValue The new value for the view model.
|
||||||
*/
|
*/
|
||||||
viewToModelUpdate(newValue: any): void {
|
viewToModelUpdate(newValue: any): void {
|
||||||
this.viewModel = newValue;
|
this.viewModel = newValue;
|
||||||
this.update.emit(newValue);
|
this.update.emit(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isControlChanged(changes: {[key: string]: any}): boolean {
|
private _isControlChanged(changes: {[key: string]: any}): boolean {
|
||||||
return changes.hasOwnProperty('form');
|
return changes.hasOwnProperty('form');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
@ -140,7 +140,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
||||||
* Tracks the `FormControl` instance bound to the directive.
|
* Tracks the `FormControl` instance bound to the directive.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
readonly control !: FormControl;
|
readonly control!: FormControl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -152,14 +152,16 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
||||||
* to indices when iterating over controls in a `FormArray`.
|
* to indices when iterating over controls in a `FormArray`.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input('formControlName') name !: string | number | null;
|
@Input('formControlName') name!: string|number|null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* 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
|
||||||
|
|
||||||
|
@ -244,21 +246,25 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
||||||
* 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[] {
|
get path(): string[] {
|
||||||
return controlPath(this.name == null ? this.name : this.name.toString(), this._parent !);
|
return controlPath(this.name == null ? this.name : this.name.toString(), this._parent!);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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
|
||||||
|
@ -266,7 +272,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
||||||
* directive.
|
* directive.
|
||||||
*/
|
*/
|
||||||
get asyncValidator(): AsyncValidatorFn {
|
get asyncValidator(): AsyncValidatorFn {
|
||||||
return composeAsyncValidators(this._rawAsyncValidators) !;
|
return composeAsyncValidators(this._rawAsyncValidators)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _checkParentType(): void {
|
private _checkParentType(): void {
|
||||||
|
@ -282,9 +288,9 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
||||||
|
|
||||||
private _setUpControl() {
|
private _setUpControl() {
|
||||||
this._checkParentType();
|
this._checkParentType();
|
||||||
(this as{control: FormControl}).control = this.formDirective.addControl(this);
|
(this as {control: FormControl}).control = this.formDirective.addControl(this);
|
||||||
if (this.control.disabled && this.valueAccessor !.setDisabledState) {
|
if (this.control.disabled && this.valueAccessor!.setDisabledState) {
|
||||||
this.valueAccessor !.setDisabledState !(true);
|
this.valueAccessor!.setDisabledState!(true);
|
||||||
}
|
}
|
||||||
this._added = true;
|
this._added = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -60,7 +60,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
public readonly submitted: boolean = false;
|
public readonly submitted: boolean = false;
|
||||||
|
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
private _oldForm !: FormGroup;
|
private _oldForm!: FormGroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -72,7 +72,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
* @description
|
* @description
|
||||||
* Tracks the `FormGroup` bound to this directive.
|
* Tracks the `FormGroup` bound to this directive.
|
||||||
*/
|
*/
|
||||||
@Input('formGroup') form: FormGroup = null !;
|
@Input('formGroup') form: FormGroup = null!;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -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.
|
||||||
|
@ -222,7 +236,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
* @param $event The "submit" event object
|
* @param $event The "submit" event object
|
||||||
*/
|
*/
|
||||||
onSubmit($event: Event): boolean {
|
onSubmit($event: Event): boolean {
|
||||||
(this as{submitted: boolean}).submitted = true;
|
(this as {submitted: boolean}).submitted = true;
|
||||||
syncPendingControls(this.form, this.directives);
|
syncPendingControls(this.form, this.directives);
|
||||||
this.ngSubmit.emit($event);
|
this.ngSubmit.emit($event);
|
||||||
return false;
|
return false;
|
||||||
|
@ -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
|
||||||
|
@ -242,7 +258,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
*/
|
*/
|
||||||
resetForm(value: any = undefined): void {
|
resetForm(value: any = undefined): void {
|
||||||
this.form.reset(value);
|
this.form.reset(value);
|
||||||
(this as{submitted: boolean}).submitted = false;
|
(this as {submitted: boolean}).submitted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -253,7 +269,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
if (dir.control !== newCtrl) {
|
if (dir.control !== newCtrl) {
|
||||||
cleanUpControl(dir.control, dir);
|
cleanUpControl(dir.control, dir);
|
||||||
if (newCtrl) setUpControl(newCtrl, dir);
|
if (newCtrl) setUpControl(newCtrl, dir);
|
||||||
(dir as{control: FormControl}).control = newCtrl;
|
(dir as {control: FormControl}).control = newCtrl;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -268,10 +284,10 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
|
|
||||||
private _updateValidators() {
|
private _updateValidators() {
|
||||||
const sync = composeValidators(this._validators);
|
const sync = composeValidators(this._validators);
|
||||||
this.form.validator = Validators.compose([this.form.validator !, sync !]);
|
this.form.validator = Validators.compose([this.form.validator!, sync!]);
|
||||||
|
|
||||||
const async = composeAsyncValidators(this._asyncValidators);
|
const async = composeAsyncValidators(this._asyncValidators);
|
||||||
this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator !, async !]);
|
this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator!, async!]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _checkFormPresent() {
|
private _checkFormPresent() {
|
||||||
|
|
|
@ -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';
|
||||||
|
@ -82,7 +82,7 @@ export class FormGroupName extends AbstractFormGroupDirective implements OnInit,
|
||||||
* to indices when iterating over groups in a `FormArray`.
|
* to indices when iterating over groups in a `FormArray`.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input('formGroupName') name !: string | number | null;
|
@Input('formGroupName') name!: string|number|null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Optional() @Host() @SkipSelf() parent: ControlContainer,
|
@Optional() @Host() @SkipSelf() parent: ControlContainer,
|
||||||
|
@ -152,7 +152,7 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy
|
||||||
* to indices when iterating over arrays in a `FormArray`.
|
* to indices when iterating over arrays in a `FormArray`.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input('formArrayName') name !: string | number | null;
|
@Input('formArrayName') name!: string|number|null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Optional() @Host() @SkipSelf() parent: ControlContainer,
|
@Optional() @Host() @SkipSelf() parent: ControlContainer,
|
||||||
|
@ -172,7 +172,7 @@ export class FormArrayName extends ControlContainer implements OnInit, OnDestroy
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this._checkParentType();
|
this._checkParentType();
|
||||||
this.formDirective !.addFormArray(this);
|
this.formDirective!.addFormArray(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ export const SELECT_VALUE_ACCESSOR: StaticProvider = {
|
||||||
multi: true
|
multi: true
|
||||||
};
|
};
|
||||||
|
|
||||||
function _buildValueString(id: string | null, value: any): string {
|
function _buildValueString(id: string|null, value: any): string {
|
||||||
if (id == null) return `${value}`;
|
if (id == null) return `${value}`;
|
||||||
if (value && typeof value === 'object') value = 'Object';
|
if (value && typeof value === 'object') value = 'Object';
|
||||||
return `${id}: ${value}`.slice(0, 50);
|
return `${id}: ${value}`.slice(0, 50);
|
||||||
|
@ -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 {
|
||||||
|
@ -206,7 +210,7 @@ export class NgSelectOption implements OnDestroy {
|
||||||
* ID of the option element
|
* ID of the option element
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
id !: string;
|
id!: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _element: ElementRef, private _renderer: Renderer2,
|
private _element: ElementRef, private _renderer: Renderer2,
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
@ -36,15 +36,15 @@ interface HTMLOption {
|
||||||
/** Mock interface for HTMLCollection */
|
/** Mock interface for HTMLCollection */
|
||||||
abstract class HTMLCollection {
|
abstract class HTMLCollection {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
length !: number;
|
length!: number;
|
||||||
abstract item(_: number): HTMLOption;
|
abstract item(_: number): HTMLOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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.
|
||||||
|
@ -203,7 +209,7 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_getOptionId(value: any): string|null {
|
_getOptionId(value: any): string|null {
|
||||||
for (const id of Array.from(this._optionMap.keys())) {
|
for (const id of Array.from(this._optionMap.keys())) {
|
||||||
if (this._compareWith(this._optionMap.get(id) !._value, value)) return id;
|
if (this._compareWith(this._optionMap.get(id)!._value, value)) return id;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -211,7 +217,7 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_getOptionValue(valueString: string): any {
|
_getOptionValue(valueString: string): any {
|
||||||
const id: string = _extractId(valueString);
|
const id: string = _extractId(valueString);
|
||||||
return this._optionMap.has(id) ? this._optionMap.get(id) !._value : valueString;
|
return this._optionMap.has(id) ? this._optionMap.get(id)!._value : valueString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +234,7 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
|
||||||
@Directive({selector: 'option'})
|
@Directive({selector: 'option'})
|
||||||
export class ɵNgSelectMultipleOption implements OnDestroy {
|
export class ɵNgSelectMultipleOption implements OnDestroy {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
id !: string;
|
id!: string;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_value: any;
|
_value: any;
|
||||||
|
|
||||||
|
|
|
@ -28,43 +28,44 @@ import {SelectMultipleControlValueAccessor} from './select_multiple_control_valu
|
||||||
import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators';
|
import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from './validators';
|
||||||
|
|
||||||
|
|
||||||
export function controlPath(name: string | null, parent: ControlContainer): string[] {
|
export function controlPath(name: string|null, parent: ControlContainer): string[] {
|
||||||
return [...parent.path !, name !];
|
return [...parent.path!, name!];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setUpControl(control: FormControl, dir: NgControl): void {
|
export function setUpControl(control: FormControl, dir: NgControl): void {
|
||||||
if (!control) _throwError(dir, 'Cannot find control with');
|
if (!control) _throwError(dir, 'Cannot find control with');
|
||||||
if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');
|
if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');
|
||||||
|
|
||||||
control.validator = Validators.compose([control.validator !, dir.validator]);
|
control.validator = Validators.compose([control.validator!, dir.validator]);
|
||||||
control.asyncValidator = Validators.composeAsync([control.asyncValidator !, dir.asyncValidator]);
|
control.asyncValidator = Validators.composeAsync([control.asyncValidator!, dir.asyncValidator]);
|
||||||
dir.valueAccessor !.writeValue(control.value);
|
dir.valueAccessor!.writeValue(control.value);
|
||||||
|
|
||||||
setUpViewChangePipeline(control, dir);
|
setUpViewChangePipeline(control, dir);
|
||||||
setUpModelChangePipeline(control, dir);
|
setUpModelChangePipeline(control, dir);
|
||||||
|
|
||||||
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
|
||||||
dir._rawValidators.forEach((validator: Validator | ValidatorFn) => {
|
dir._rawValidators.forEach((validator: Validator|ValidatorFn) => {
|
||||||
if ((<Validator>validator).registerOnValidatorChange)
|
if ((<Validator>validator).registerOnValidatorChange)
|
||||||
(<Validator>validator).registerOnValidatorChange !(() => control.updateValueAndValidity());
|
(<Validator>validator).registerOnValidatorChange!(() => control.updateValueAndValidity());
|
||||||
});
|
});
|
||||||
|
|
||||||
dir._rawAsyncValidators.forEach((validator: AsyncValidator | AsyncValidatorFn) => {
|
dir._rawAsyncValidators.forEach((validator: AsyncValidator|AsyncValidatorFn) => {
|
||||||
if ((<Validator>validator).registerOnValidatorChange)
|
if ((<Validator>validator).registerOnValidatorChange)
|
||||||
(<Validator>validator).registerOnValidatorChange !(() => control.updateValueAndValidity());
|
(<Validator>validator).registerOnValidatorChange!(() => control.updateValueAndValidity());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cleanUpControl(control: FormControl, dir: NgControl) {
|
export function cleanUpControl(control: FormControl, dir: NgControl) {
|
||||||
dir.valueAccessor !.registerOnChange(() => _noControlError(dir));
|
dir.valueAccessor!.registerOnChange(() => _noControlError(dir));
|
||||||
dir.valueAccessor !.registerOnTouched(() => _noControlError(dir));
|
dir.valueAccessor!.registerOnTouched(() => _noControlError(dir));
|
||||||
|
|
||||||
dir._rawValidators.forEach((validator: any) => {
|
dir._rawValidators.forEach((validator: any) => {
|
||||||
if (validator.registerOnValidatorChange) {
|
if (validator.registerOnValidatorChange) {
|
||||||
|
@ -82,7 +83,7 @@ export function cleanUpControl(control: FormControl, dir: NgControl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
|
function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
|
||||||
dir.valueAccessor !.registerOnChange((newValue: any) => {
|
dir.valueAccessor!.registerOnChange((newValue: any) => {
|
||||||
control._pendingValue = newValue;
|
control._pendingValue = newValue;
|
||||||
control._pendingChange = true;
|
control._pendingChange = true;
|
||||||
control._pendingDirty = true;
|
control._pendingDirty = true;
|
||||||
|
@ -92,7 +93,7 @@ function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUpBlurPipeline(control: FormControl, dir: NgControl): void {
|
function setUpBlurPipeline(control: FormControl, dir: NgControl): void {
|
||||||
dir.valueAccessor !.registerOnTouched(() => {
|
dir.valueAccessor!.registerOnTouched(() => {
|
||||||
control._pendingTouched = true;
|
control._pendingTouched = true;
|
||||||
|
|
||||||
if (control.updateOn === 'blur' && control._pendingChange) updateControl(control, dir);
|
if (control.updateOn === 'blur' && control._pendingChange) updateControl(control, dir);
|
||||||
|
@ -110,7 +111,7 @@ function updateControl(control: FormControl, dir: NgControl): void {
|
||||||
function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
|
function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
|
||||||
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
|
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
|
||||||
// control -> view
|
// control -> view
|
||||||
dir.valueAccessor !.writeValue(newValue);
|
dir.valueAccessor!.writeValue(newValue);
|
||||||
|
|
||||||
// control -> ngModel
|
// control -> ngModel
|
||||||
if (emitModelEvent) dir.viewToModelUpdate(newValue);
|
if (emitModelEvent) dir.viewToModelUpdate(newValue);
|
||||||
|
@ -118,7 +119,7 @@ function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setUpFormContainer(
|
export function setUpFormContainer(
|
||||||
control: FormGroup | FormArray, dir: AbstractFormGroupDirective | FormArrayName) {
|
control: FormGroup|FormArray, dir: AbstractFormGroupDirective|FormArrayName) {
|
||||||
if (control == null) _throwError(dir, 'Cannot find control with');
|
if (control == null) _throwError(dir, 'Cannot find control with');
|
||||||
control.validator = Validators.compose([control.validator, dir.validator]);
|
control.validator = Validators.compose([control.validator, dir.validator]);
|
||||||
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
|
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
|
||||||
|
@ -130,9 +131,9 @@ function _noControlError(dir: NgControl) {
|
||||||
|
|
||||||
function _throwError(dir: AbstractControlDirective, message: string): void {
|
function _throwError(dir: AbstractControlDirective, message: string): void {
|
||||||
let messageEnd: string;
|
let messageEnd: string;
|
||||||
if (dir.path !.length > 1) {
|
if (dir.path!.length > 1) {
|
||||||
messageEnd = `path: '${dir.path!.join(' -> ')}'`;
|
messageEnd = `path: '${dir.path!.join(' -> ')}'`;
|
||||||
} else if (dir.path ![0]) {
|
} else if (dir.path![0]) {
|
||||||
messageEnd = `name: '${dir.path}'`;
|
messageEnd = `name: '${dir.path}'`;
|
||||||
} else {
|
} else {
|
||||||
messageEnd = 'unspecified name attribute';
|
messageEnd = 'unspecified name attribute';
|
||||||
|
@ -226,7 +227,7 @@ export function removeDir<T>(list: T[], el: T): void {
|
||||||
// TODO(kara): remove after deprecation period
|
// TODO(kara): remove after deprecation period
|
||||||
export function _ngModelWarning(
|
export function _ngModelWarning(
|
||||||
name: string, type: {_ngModelWarningSentOnce: boolean},
|
name: string, type: {_ngModelWarningSentOnce: boolean},
|
||||||
instance: {_ngModelWarningSent: boolean}, warningConfig: string | null) {
|
instance: {_ngModelWarningSent: boolean}, warningConfig: string|null) {
|
||||||
if (!isDevMode() || warningConfig === 'never') return;
|
if (!isDevMode() || warningConfig === 'never') return;
|
||||||
|
|
||||||
if (((warningConfig === null || warningConfig === 'once') && !type._ngModelWarningSentOnce) ||
|
if (((warningConfig === null || warningConfig === 'once') && !type._ngModelWarningSentOnce) ||
|
||||||
|
|
|
@ -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';
|
||||||
|
@ -159,16 +159,18 @@ export const CHECKBOX_REQUIRED_VALIDATOR: StaticProvider = {
|
||||||
})
|
})
|
||||||
export class RequiredValidator implements Validator {
|
export class RequiredValidator implements Validator {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
private _required !: boolean;
|
private _required!: boolean;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
private _onChange !: () => void;
|
private _onChange!: () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* 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>
|
||||||
|
@ -269,9 +275,9 @@ export const EMAIL_VALIDATOR: any = {
|
||||||
})
|
})
|
||||||
export class EmailValidator implements Validator {
|
export class EmailValidator implements Validator {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
private _enabled !: boolean;
|
private _enabled!: boolean;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
private _onChange !: () => void;
|
private _onChange!: () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -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,19 +367,18 @@ 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 '!'.
|
||||||
private _onChange !: () => void;
|
private _onChange!: () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* Tracks changes to the the minimum length bound to this directive.
|
* Tracks changes to the the minimum length bound to this directive.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input() minlength !: string | number;
|
@Input() minlength!: string|number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -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,19 +455,18 @@ 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 '!'.
|
||||||
private _onChange !: () => void;
|
private _onChange!: () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* Tracks changes to the the maximum length bound to this directive.
|
* Tracks changes to the the maximum length bound to this directive.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input() maxlength !: string | number;
|
@Input() maxlength!: string|number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -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,19 +546,18 @@ 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 '!'.
|
||||||
private _onChange !: () => void;
|
private _onChange!: () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
* Tracks changes to the pattern bound to this directive.
|
* Tracks changes to the pattern bound to this directive.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
@Input() pattern !: string | RegExp;
|
@Input() pattern!: string|RegExp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description
|
* @description
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -52,14 +52,13 @@ export class ReactiveFormsModule {
|
||||||
* binding is used with reactive form directives.
|
* binding is used with reactive form directives.
|
||||||
*/
|
*/
|
||||||
static withConfig(opts: {
|
static withConfig(opts: {
|
||||||
/** @deprecated as of v6 */ warnOnNgModelWithFormControl: 'never' | 'once' | 'always'
|
/** @deprecated as of v6 */ warnOnNgModelWithFormControl: 'never'|'once'|'always'
|
||||||
}): 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
|
]
|
||||||
}]
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ export const PENDING = 'PENDING';
|
||||||
*/
|
*/
|
||||||
export const DISABLED = 'DISABLED';
|
export const DISABLED = 'DISABLED';
|
||||||
|
|
||||||
function _find(control: AbstractControl, path: Array<string|number>| string, delimiter: string) {
|
function _find(control: AbstractControl, path: Array<string|number>|string, delimiter: string) {
|
||||||
if (path == null) return null;
|
if (path == null) return null;
|
||||||
|
|
||||||
if (!Array.isArray(path)) {
|
if (!Array.isArray(path)) {
|
||||||
|
@ -55,7 +55,7 @@ function _find(control: AbstractControl, path: Array<string|number>| string, del
|
||||||
// Not using Array.reduce here due to a Chrome 80 bug
|
// Not using Array.reduce here due to a Chrome 80 bug
|
||||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
|
||||||
let controlToFind: AbstractControl|null = control;
|
let controlToFind: AbstractControl|null = control;
|
||||||
path.forEach((name: string | number) => {
|
path.forEach((name: string|number) => {
|
||||||
if (controlToFind instanceof FormGroup) {
|
if (controlToFind instanceof FormGroup) {
|
||||||
controlToFind = controlToFind.controls.hasOwnProperty(name as string) ?
|
controlToFind = controlToFind.controls.hasOwnProperty(name as string) ?
|
||||||
controlToFind.controls[name] :
|
controlToFind.controls[name] :
|
||||||
|
@ -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 |
|
||||||
|
@ -92,7 +92,7 @@ function coerceToAsyncValidator(
|
||||||
origAsyncValidator || null;
|
origAsyncValidator || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FormHooks = 'change' | 'blur' | 'submit';
|
export type FormHooks = 'change'|'blur'|'submit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for options provided to an `AbstractControl`.
|
* Interface for options provided to an `AbstractControl`.
|
||||||
|
@ -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';
|
||||||
}
|
}
|
||||||
|
@ -142,21 +142,21 @@ function isOptionsObj(
|
||||||
export abstract class AbstractControl {
|
export abstract class AbstractControl {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
_pendingDirty !: boolean;
|
_pendingDirty!: boolean;
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
_pendingTouched !: boolean;
|
_pendingTouched!: boolean;
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_onCollectionChange = () => {};
|
_onCollectionChange = () => {};
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
_updateOn !: FormHooks;
|
_updateOn!: FormHooks;
|
||||||
|
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
private _parent !: FormGroup | FormArray;
|
private _parent!: FormGroup|FormArray;
|
||||||
private _asyncValidationSubscription: any;
|
private _asyncValidationSubscription: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
@ -199,7 +201,7 @@ export abstract class AbstractControl {
|
||||||
* both valid AND invalid or invalid AND disabled.
|
* both valid AND invalid or invalid AND disabled.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
public readonly status !: string;
|
public readonly status!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A control is `valid` when its `status` is `VALID`.
|
* A control is `valid` when its `status` is `VALID`.
|
||||||
|
@ -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,14 +263,16 @@ 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,
|
||||||
* or null if there are no errors.
|
* or null if there are no errors.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
public readonly errors !: ValidationErrors | null;
|
public readonly errors!: ValidationErrors|null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A control is `pristine` if the user has not yet changed
|
* A control is `pristine` if the user has not yet changed
|
||||||
|
@ -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
|
||||||
|
@ -302,7 +318,7 @@ export abstract class AbstractControl {
|
||||||
* without passing along {emitEvent: false} as a function argument.
|
* without passing along {emitEvent: false} as a function argument.
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
public readonly valueChanges !: Observable<any>;
|
public readonly valueChanges!: Observable<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A multicasting observable that emits an event every time the validation `status` of the control
|
* A multicasting observable that emits an event every time the validation `status` of the control
|
||||||
|
@ -312,7 +328,7 @@ export abstract class AbstractControl {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
public readonly statusChanges !: Observable<any>;
|
public readonly statusChanges!: Observable<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reports the update strategy of the `AbstractControl` (meaning
|
* Reports the update strategy of the `AbstractControl` (meaning
|
||||||
|
@ -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
|
||||||
|
@ -380,7 +400,7 @@ export abstract class AbstractControl {
|
||||||
* marks all direct ancestors. Default is false.
|
* marks all direct ancestors. Default is false.
|
||||||
*/
|
*/
|
||||||
markAsTouched(opts: {onlySelf?: boolean} = {}): void {
|
markAsTouched(opts: {onlySelf?: boolean} = {}): void {
|
||||||
(this as{touched: boolean}).touched = true;
|
(this as {touched: boolean}).touched = true;
|
||||||
|
|
||||||
if (this._parent && !opts.onlySelf) {
|
if (this._parent && !opts.onlySelf) {
|
||||||
this._parent.markAsTouched(opts);
|
this._parent.markAsTouched(opts);
|
||||||
|
@ -413,11 +433,12 @@ export abstract class AbstractControl {
|
||||||
* marks all direct ancestors. Default is false.
|
* marks all direct ancestors. Default is false.
|
||||||
*/
|
*/
|
||||||
markAsUntouched(opts: {onlySelf?: boolean} = {}): void {
|
markAsUntouched(opts: {onlySelf?: boolean} = {}): void {
|
||||||
(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);
|
||||||
|
@ -438,7 +459,7 @@ export abstract class AbstractControl {
|
||||||
* marks all direct ancestors. Default is false.
|
* marks all direct ancestors. Default is false.
|
||||||
*/
|
*/
|
||||||
markAsDirty(opts: {onlySelf?: boolean} = {}): void {
|
markAsDirty(opts: {onlySelf?: boolean} = {}): void {
|
||||||
(this as{pristine: boolean}).pristine = false;
|
(this as {pristine: boolean}).pristine = false;
|
||||||
|
|
||||||
if (this._parent && !opts.onlySelf) {
|
if (this._parent && !opts.onlySelf) {
|
||||||
this._parent.markAsDirty(opts);
|
this._parent.markAsDirty(opts);
|
||||||
|
@ -462,10 +483,12 @@ export abstract class AbstractControl {
|
||||||
* marks all direct ancestors. Default is false..
|
* marks all direct ancestors. Default is false..
|
||||||
*/
|
*/
|
||||||
markAsPristine(opts: {onlySelf?: boolean} = {}): void {
|
markAsPristine(opts: {onlySelf?: boolean} = {}): void {
|
||||||
(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);
|
||||||
|
@ -489,7 +512,7 @@ export abstract class AbstractControl {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
markAsPending(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
markAsPending(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||||
(this as{status: string}).status = PENDING;
|
(this as {status: string}).status = PENDING;
|
||||||
|
|
||||||
if (opts.emitEvent !== false) {
|
if (opts.emitEvent !== false) {
|
||||||
(this.statusChanges as EventEmitter<any>).emit(this.status);
|
(this.statusChanges as EventEmitter<any>).emit(this.status);
|
||||||
|
@ -522,10 +545,11 @@ export abstract class AbstractControl {
|
||||||
// parent's dirtiness based on the children.
|
// parent's dirtiness based on the children.
|
||||||
const skipPristineCheck = this._parentMarkedDirty(opts.onlySelf);
|
const skipPristineCheck = this._parentMarkedDirty(opts.onlySelf);
|
||||||
|
|
||||||
(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) {
|
||||||
|
@ -560,9 +584,10 @@ export abstract class AbstractControl {
|
||||||
// parent's dirtiness based on the children.
|
// parent's dirtiness based on the children.
|
||||||
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).
|
||||||
|
@ -620,8 +647,8 @@ export abstract class AbstractControl {
|
||||||
|
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
this._cancelExistingSubscription();
|
this._cancelExistingSubscription();
|
||||||
(this as{errors: ValidationErrors | null}).errors = this._runValidator();
|
(this as {errors: ValidationErrors | null}).errors = this._runValidator();
|
||||||
(this as{status: string}).status = this._calculateStatus();
|
(this as {status: string}).status = this._calculateStatus();
|
||||||
|
|
||||||
if (this.status === VALID || this.status === PENDING) {
|
if (this.status === VALID || this.status === PENDING) {
|
||||||
this._runAsyncValidator(opts.emitEvent);
|
this._runAsyncValidator(opts.emitEvent);
|
||||||
|
@ -645,7 +672,7 @@ export abstract class AbstractControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setInitialStatus() {
|
private _setInitialStatus() {
|
||||||
(this as{status: string}).status = this._allControlsDisabled() ? DISABLED : VALID;
|
(this as {status: string}).status = this._allControlsDisabled() ? DISABLED : VALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _runValidator(): ValidationErrors|null {
|
private _runValidator(): ValidationErrors|null {
|
||||||
|
@ -654,10 +681,10 @@ export abstract class AbstractControl {
|
||||||
|
|
||||||
private _runAsyncValidator(emitEvent?: boolean): void {
|
private _runAsyncValidator(emitEvent?: boolean): void {
|
||||||
if (this.asyncValidator) {
|
if (this.asyncValidator) {
|
||||||
(this as{status: string}).status = PENDING;
|
(this as {status: string}).status = PENDING;
|
||||||
const obs = toObservable(this.asyncValidator(this));
|
const obs = toObservable(this.asyncValidator(this));
|
||||||
this._asyncValidationSubscription =
|
this._asyncValidationSubscription =
|
||||||
obs.subscribe((errors: ValidationErrors | null) => this.setErrors(errors, {emitEvent}));
|
obs.subscribe((errors: ValidationErrors|null) => this.setErrors(errors, {emitEvent}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,7 +717,7 @@ export abstract class AbstractControl {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
setErrors(errors: ValidationErrors|null, opts: {emitEvent?: boolean} = {}): void {
|
setErrors(errors: ValidationErrors|null, opts: {emitEvent?: boolean} = {}): void {
|
||||||
(this as{errors: ValidationErrors | null}).errors = errors;
|
(this as {errors: ValidationErrors | null}).errors = errors;
|
||||||
this._updateControlsErrors(opts.emitEvent !== false);
|
this._updateControlsErrors(opts.emitEvent !== false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -794,7 +823,7 @@ export abstract class AbstractControl {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_updateControlsErrors(emitEvent: boolean): void {
|
_updateControlsErrors(emitEvent: boolean): void {
|
||||||
(this as{status: string}).status = this._calculateStatus();
|
(this as {status: string}).status = this._calculateStatus();
|
||||||
|
|
||||||
if (emitEvent) {
|
if (emitEvent) {
|
||||||
(this.statusChanges as EventEmitter<string>).emit(this.status);
|
(this.statusChanges as EventEmitter<string>).emit(this.status);
|
||||||
|
@ -807,8 +836,8 @@ export abstract class AbstractControl {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_initObservables() {
|
_initObservables() {
|
||||||
(this as{valueChanges: Observable<any>}).valueChanges = new EventEmitter();
|
(this as {valueChanges: Observable<any>}).valueChanges = new EventEmitter();
|
||||||
(this as{statusChanges: Observable<any>}).statusChanges = new EventEmitter();
|
(this as {statusChanges: Observable<any>}).statusChanges = new EventEmitter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -852,7 +881,7 @@ export abstract class AbstractControl {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_updatePristine(opts: {onlySelf?: boolean} = {}): void {
|
_updatePristine(opts: {onlySelf?: boolean} = {}): void {
|
||||||
(this as{pristine: boolean}).pristine = !this._anyControlsDirty();
|
(this as {pristine: boolean}).pristine = !this._anyControlsDirty();
|
||||||
|
|
||||||
if (this._parent && !opts.onlySelf) {
|
if (this._parent && !opts.onlySelf) {
|
||||||
this._parent._updatePristine(opts);
|
this._parent._updatePristine(opts);
|
||||||
|
@ -861,7 +890,7 @@ export abstract class AbstractControl {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_updateTouched(opts: {onlySelf?: boolean} = {}): void {
|
_updateTouched(opts: {onlySelf?: boolean} = {}): void {
|
||||||
(this as{touched: boolean}).touched = this._anyControlsTouched();
|
(this as {touched: boolean}).touched = this._anyControlsTouched();
|
||||||
|
|
||||||
if (this._parent && !opts.onlySelf) {
|
if (this._parent && !opts.onlySelf) {
|
||||||
this._parent._updateTouched(opts);
|
this._parent._updateTouched(opts);
|
||||||
|
@ -878,12 +907,14 @@ 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 {
|
||||||
if (isOptionsObj(opts) && (opts as AbstractControlOptions).updateOn != null) {
|
if (isOptionsObj(opts) && (opts as AbstractControlOptions).updateOn != null) {
|
||||||
this._updateOn = (opts as AbstractControlOptions).updateOn !;
|
this._updateOn = (opts as AbstractControlOptions).updateOn!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1006,18 +1037,18 @@ export class FormControl extends AbstractControl {
|
||||||
_pendingChange: any;
|
_pendingChange: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new `FormControl` instance.
|
* Creates a new `FormControl` instance.
|
||||||
*
|
*
|
||||||
* @param formState Initializes the control with an initial value,
|
* @param formState Initializes the control with an initial value,
|
||||||
* or an object that defines the initial value and disabled state.
|
* or an object that defines the initial value and disabled state.
|
||||||
*
|
*
|
||||||
* @param validatorOrOpts A synchronous validator function, or an array of
|
* @param validatorOrOpts A synchronous validator function, or an array of
|
||||||
* such functions, or an `AbstractControlOptions` object that contains validation functions
|
* such functions, or an `AbstractControlOptions` object that contains validation functions
|
||||||
* and a validation trigger.
|
* and a validation trigger.
|
||||||
*
|
*
|
||||||
* @param asyncValidator A single async validator or array of async validator functions
|
* @param asyncValidator A single async validator or array of async validator functions
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
formState: any = null,
|
formState: any = null,
|
||||||
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
|
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
|
||||||
|
@ -1060,7 +1091,7 @@ export class FormControl extends AbstractControl {
|
||||||
emitModelToViewChange?: boolean,
|
emitModelToViewChange?: boolean,
|
||||||
emitViewToModelChange?: boolean
|
emitViewToModelChange?: boolean
|
||||||
} = {}): void {
|
} = {}): void {
|
||||||
(this as{value: any}).value = this._pendingValue = value;
|
(this as {value: any}).value = this._pendingValue = value;
|
||||||
if (this._onChange.length && options.emitModelToViewChange !== false) {
|
if (this._onChange.length && options.emitModelToViewChange !== false) {
|
||||||
this._onChange.forEach(
|
this._onChange.forEach(
|
||||||
(changeFn) => changeFn(this.value, options.emitViewToModelChange !== false));
|
(changeFn) => changeFn(this.value, options.emitViewToModelChange !== false));
|
||||||
|
@ -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
|
||||||
|
@ -1172,11 +1209,11 @@ export class FormControl extends AbstractControl {
|
||||||
|
|
||||||
private _applyFormState(formState: any) {
|
private _applyFormState(formState: any) {
|
||||||
if (this._isBoxedValue(formState)) {
|
if (this._isBoxedValue(formState)) {
|
||||||
(this as{value: any}).value = this._pendingValue = formState.value;
|
(this as {value: any}).value = this._pendingValue = formState.value;
|
||||||
formState.disabled ? this.disable({onlySelf: true, emitEvent: false}) :
|
formState.disabled ? this.disable({onlySelf: true, emitEvent: false}) :
|
||||||
this.enable({onlySelf: true, emitEvent: false});
|
this.enable({onlySelf: true, emitEvent: false});
|
||||||
} else {
|
} else {
|
||||||
(this as{value: any}).value = this._pendingValue = formState;
|
(this as {value: any}).value = this._pendingValue = formState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1255,18 +1292,18 @@ export class FormControl extends AbstractControl {
|
||||||
*/
|
*/
|
||||||
export class FormGroup extends AbstractControl {
|
export class FormGroup extends AbstractControl {
|
||||||
/**
|
/**
|
||||||
* Creates a new `FormGroup` instance.
|
* Creates a new `FormGroup` instance.
|
||||||
*
|
*
|
||||||
* @param controls A collection of child controls. The key for each child is the name
|
* @param controls A collection of child controls. The key for each child is the name
|
||||||
* under which it is registered.
|
* under which it is registered.
|
||||||
*
|
*
|
||||||
* @param validatorOrOpts A synchronous validator function, or an array of
|
* @param validatorOrOpts A synchronous validator function, or an array of
|
||||||
* such functions, or an `AbstractControlOptions` object that contains validation functions
|
* such functions, or an `AbstractControlOptions` object that contains validation functions
|
||||||
* and a validation trigger.
|
* and a validation trigger.
|
||||||
*
|
*
|
||||||
* @param asyncValidator A single async validator or array of async validator functions
|
* @param asyncValidator A single async validator or array of async validator functions
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
public controls: {[key: string]: AbstractControl},
|
public controls: {[key: string]: AbstractControl},
|
||||||
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
|
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1647,7 +1687,7 @@ export class FormGroup extends AbstractControl {
|
||||||
* ], {validators: myValidator, asyncValidators: myAsyncValidator});
|
* ], {validators: myValidator, asyncValidators: myAsyncValidator});
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ### Set the updateOn property for all controls in a form array
|
* ### Set the updateOn property for all controls in a form array
|
||||||
*
|
*
|
||||||
* The options object is used to set a default value for each child
|
* The options object is used to set a default value for each child
|
||||||
* control's `updateOn` property. If you set `updateOn` to `'blur'` at the
|
* control's `updateOn` property. If you set `updateOn` to `'blur'` at the
|
||||||
|
@ -1672,18 +1712,18 @@ export class FormGroup extends AbstractControl {
|
||||||
*/
|
*/
|
||||||
export class FormArray extends AbstractControl {
|
export class FormArray extends AbstractControl {
|
||||||
/**
|
/**
|
||||||
* Creates a new `FormArray` instance.
|
* Creates a new `FormArray` instance.
|
||||||
*
|
*
|
||||||
* @param controls An array of child controls. Each child control is given an index
|
* @param controls An array of child controls. Each child control is given an index
|
||||||
* where it is registered.
|
* where it is registered.
|
||||||
*
|
*
|
||||||
* @param validatorOrOpts A synchronous validator function, or an array of
|
* @param validatorOrOpts A synchronous validator function, or an array of
|
||||||
* such functions, or an `AbstractControlOptions` object that contains validation functions
|
* such functions, or an `AbstractControlOptions` object that contains validation functions
|
||||||
* and a validation trigger.
|
* and a validation trigger.
|
||||||
*
|
*
|
||||||
* @param asyncValidator A single async validator or array of async validator functions
|
* @param asyncValidator A single async validator or array of async validator functions
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
public controls: AbstractControl[],
|
public controls: AbstractControl[],
|
||||||
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
|
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
|
||||||
|
@ -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,12 +2023,14 @@ 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 */
|
||||||
_updateValue(): void {
|
_updateValue(): void {
|
||||||
(this as{value: any}).value =
|
(this as {value: any}).value =
|
||||||
this.controls.filter((control) => control.enabled || this.disabled)
|
this.controls.filter((control) => control.enabled || this.disabled)
|
||||||
.map((control) => control.value);
|
.map((control) => control.value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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`
|
||||||
*
|
*
|
||||||
|
@ -124,7 +127,7 @@ export class Validators {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static min(min: number): ValidatorFn {
|
static min(min: number): ValidatorFn {
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
return (control: AbstractControl): ValidationErrors|null => {
|
||||||
if (isEmptyInputValue(control.value) || isEmptyInputValue(min)) {
|
if (isEmptyInputValue(control.value) || isEmptyInputValue(min)) {
|
||||||
return null; // don't validate empty values to allow optional controls
|
return null; // don't validate empty values to allow optional controls
|
||||||
}
|
}
|
||||||
|
@ -157,7 +160,7 @@ export class Validators {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static max(max: number): ValidatorFn {
|
static max(max: number): ValidatorFn {
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
return (control: AbstractControl): ValidationErrors|null => {
|
||||||
if (isEmptyInputValue(control.value) || isEmptyInputValue(max)) {
|
if (isEmptyInputValue(control.value) || isEmptyInputValue(max)) {
|
||||||
return null; // don't validate empty values to allow optional controls
|
return null; // don't validate empty values to allow optional controls
|
||||||
}
|
}
|
||||||
|
@ -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 (`.`).
|
||||||
|
@ -285,7 +290,7 @@ export class Validators {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static minLength(minLength: number): ValidatorFn {
|
static minLength(minLength: number): ValidatorFn {
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
return (control: AbstractControl): ValidationErrors|null => {
|
||||||
if (isEmptyInputValue(control.value)) {
|
if (isEmptyInputValue(control.value)) {
|
||||||
return null; // don't validate empty values to allow optional controls
|
return null; // don't validate empty values to allow optional controls
|
||||||
}
|
}
|
||||||
|
@ -323,7 +328,7 @@ export class Validators {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static maxLength(maxLength: number): ValidatorFn {
|
static maxLength(maxLength: number): ValidatorFn {
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
return (control: AbstractControl): ValidationErrors|null => {
|
||||||
const length: number = control.value ? control.value.length : 0;
|
const length: number = control.value ? control.value.length : 0;
|
||||||
return length > maxLength ?
|
return length > maxLength ?
|
||||||
{'maxlength': {'requiredLength': maxLength, 'actualLength': length}} :
|
{'maxlength': {'requiredLength': maxLength, 'actualLength': length}} :
|
||||||
|
@ -379,7 +384,7 @@ export class Validators {
|
||||||
regexStr = pattern.toString();
|
regexStr = pattern.toString();
|
||||||
regex = pattern;
|
regex = pattern;
|
||||||
}
|
}
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
return (control: AbstractControl): ValidationErrors|null => {
|
||||||
if (isEmptyInputValue(control.value)) {
|
if (isEmptyInputValue(control.value)) {
|
||||||
return null; // don't validate empty values to allow optional controls
|
return null; // don't validate empty values to allow optional controls
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -469,8 +476,8 @@ function _mergeErrors(arrayOfErrors: ValidationErrors[]): ValidationErrors|null
|
||||||
|
|
||||||
// Not using Array.reduce here due to a Chrome 80 bug
|
// Not using Array.reduce here due to a Chrome 80 bug
|
||||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=1049982
|
||||||
arrayOfErrors.forEach((errors: ValidationErrors | null) => {
|
arrayOfErrors.forEach((errors: ValidationErrors|null) => {
|
||||||
res = errors != null ? {...res !, ...errors} : res !;
|
res = errors != null ? {...res!, ...errors} : res!;
|
||||||
});
|
});
|
||||||
|
|
||||||
return Object.keys(res).length === 0 ? null : res;
|
return Object.keys(res).length === 0 ? null : res;
|
||||||
|
|
|
@ -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,49 +74,51 @@ 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!);
|
||||||
expect(selectValueAccessor(dir, [
|
expect(selectValueAccessor(dir, [
|
||||||
defaultAccessor, checkboxAccessor
|
defaultAccessor, checkboxAccessor
|
||||||
])).toEqual(checkboxAccessor);
|
])).toEqual(checkboxAccessor);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return select accessor when provided', () => {
|
it('should return select accessor when provided', () => {
|
||||||
const selectAccessor = new SelectControlValueAccessor(null !, null !);
|
const selectAccessor = new SelectControlValueAccessor(null!, null!);
|
||||||
expect(selectValueAccessor(dir, [
|
expect(selectValueAccessor(dir, [
|
||||||
defaultAccessor, selectAccessor
|
defaultAccessor, selectAccessor
|
||||||
])).toEqual(selectAccessor);
|
])).toEqual(selectAccessor);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return select multiple accessor when provided', () => {
|
it('should return select multiple accessor when provided', () => {
|
||||||
const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null !, null !);
|
const selectMultipleAccessor = new SelectMultipleControlValueAccessor(null!, null!);
|
||||||
expect(selectValueAccessor(dir, [
|
expect(selectValueAccessor(dir, [
|
||||||
defaultAccessor, selectMultipleAccessor
|
defaultAccessor, selectMultipleAccessor
|
||||||
])).toEqual(selectMultipleAccessor);
|
])).toEqual(selectMultipleAccessor);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when more than one build-in accessor is provided', () => {
|
it('should throw when more than one build-in accessor is provided', () => {
|
||||||
const checkboxAccessor = new CheckboxControlValueAccessor(null !, null !);
|
const checkboxAccessor = new CheckboxControlValueAccessor(null!, null!);
|
||||||
const selectAccessor = new SelectControlValueAccessor(null !, null !);
|
const selectAccessor = new SelectControlValueAccessor(null!, null!);
|
||||||
expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError();
|
expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
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', () => {
|
||||||
|
@ -116,13 +131,13 @@ function asyncValidator(expected: any, timeout = 0) {
|
||||||
it('should compose functions', () => {
|
it('should compose functions', () => {
|
||||||
const dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
|
const dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
|
||||||
const dummy2 = (_: any /** TODO #9100 */) => ({'dummy2': true});
|
const dummy2 = (_: any /** TODO #9100 */) => ({'dummy2': true});
|
||||||
const v = composeValidators([dummy1, dummy2]) !;
|
const v = composeValidators([dummy1, dummy2])!;
|
||||||
expect(v(new FormControl(''))).toEqual({'dummy1': true, 'dummy2': true});
|
expect(v(new FormControl(''))).toEqual({'dummy1': true, 'dummy2': true});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should compose validator directives', () => {
|
it('should compose validator directives', () => {
|
||||||
const dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
|
const dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
|
||||||
const v = composeValidators([dummy1, new CustomValidatorDirective()]) !;
|
const v = composeValidators([dummy1, new CustomValidatorDirective()])!;
|
||||||
expect(v(new FormControl(''))).toEqual({'dummy1': true, 'custom': true});
|
expect(v(new FormControl(''))).toEqual({'dummy1': true, 'custom': true});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -174,7 +189,7 @@ function asyncValidator(expected: any, timeout = 0) {
|
||||||
|
|
||||||
describe('addControl', () => {
|
describe('addControl', () => {
|
||||||
it('should throw when no control found', () => {
|
it('should throw when no control found', () => {
|
||||||
const dir = new FormControlName(form, null !, null !, [defaultAccessor], null);
|
const dir = new FormControlName(form, null!, null!, [defaultAccessor], null);
|
||||||
dir.name = 'invalidName';
|
dir.name = 'invalidName';
|
||||||
|
|
||||||
expect(() => form.addControl(dir))
|
expect(() => form.addControl(dir))
|
||||||
|
@ -182,7 +197,7 @@ function asyncValidator(expected: any, timeout = 0) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw for a named control when no value accessor', () => {
|
it('should throw for a named control when no value accessor', () => {
|
||||||
const dir = new FormControlName(form, null !, null !, null !, null);
|
const dir = new FormControlName(form, null!, null!, null!, null);
|
||||||
dir.name = 'login';
|
dir.name = 'login';
|
||||||
|
|
||||||
expect(() => form.addControl(dir))
|
expect(() => form.addControl(dir))
|
||||||
|
@ -190,8 +205,8 @@ function asyncValidator(expected: any, timeout = 0) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when no value accessor with path', () => {
|
it('should throw when no value accessor with path', () => {
|
||||||
const group = new FormGroupName(form, null !, null !);
|
const group = new FormGroupName(form, null!, null!);
|
||||||
const dir = new FormControlName(group, null !, null !, null !, null);
|
const dir = new FormControlName(group, null!, null!, null!, null);
|
||||||
group.name = 'passwords';
|
group.name = 'passwords';
|
||||||
dir.name = 'password';
|
dir.name = 'password';
|
||||||
|
|
||||||
|
@ -321,7 +336,7 @@ function asyncValidator(expected: any, timeout = 0) {
|
||||||
personControlGroupDir = new NgModelGroup(form, [], []);
|
personControlGroupDir = new NgModelGroup(form, [], []);
|
||||||
personControlGroupDir.name = 'person';
|
personControlGroupDir.name = 'person';
|
||||||
|
|
||||||
loginControlDir = new NgModel(personControlGroupDir, null !, null !, [defaultAccessor]);
|
loginControlDir = new NgModel(personControlGroupDir, null!, null!, [defaultAccessor]);
|
||||||
loginControlDir.name = 'login';
|
loginControlDir.name = 'login';
|
||||||
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
loginControlDir.valueAccessor = new DummyControlValueAccessor();
|
||||||
});
|
});
|
||||||
|
@ -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'));
|
||||||
|
@ -544,7 +561,7 @@ function asyncValidator(expected: any, timeout = 0) {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ngModel = new NgModel(
|
ngModel = new NgModel(
|
||||||
null !, [Validators.required], [asyncValidator('expected')], [defaultAccessor]);
|
null!, [Validators.required], [asyncValidator('expected')], [defaultAccessor]);
|
||||||
ngModel.valueAccessor = new DummyControlValueAccessor();
|
ngModel.valueAccessor = new DummyControlValueAccessor();
|
||||||
control = ngModel.control;
|
control = ngModel.control;
|
||||||
});
|
});
|
||||||
|
@ -577,7 +594,7 @@ function asyncValidator(expected: any, timeout = 0) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when no value accessor with named control', () => {
|
it('should throw when no value accessor with named control', () => {
|
||||||
const namedDir = new NgModel(null !, null !, null !, null !);
|
const namedDir = new NgModel(null!, null!, null!, null!);
|
||||||
namedDir.name = 'one';
|
namedDir.name = 'one';
|
||||||
|
|
||||||
expect(() => namedDir.ngOnChanges({}))
|
expect(() => namedDir.ngOnChanges({}))
|
||||||
|
@ -585,7 +602,7 @@ function asyncValidator(expected: any, timeout = 0) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when no value accessor with unnamed control', () => {
|
it('should throw when no value accessor with unnamed control', () => {
|
||||||
const unnamedDir = new NgModel(null !, null !, null !, null !);
|
const unnamedDir = new NgModel(null!, null!, null!, null!);
|
||||||
|
|
||||||
expect(() => unnamedDir.ngOnChanges({}))
|
expect(() => unnamedDir.ngOnChanges({}))
|
||||||
.toThrowError(
|
.toThrowError(
|
||||||
|
@ -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);
|
||||||
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -656,7 +672,7 @@ function asyncValidator(expected: any, timeout = 0) {
|
||||||
parent.form = new FormGroup({'name': formModel});
|
parent.form = new FormGroup({'name': formModel});
|
||||||
controlNameDir = new FormControlName(parent, [], [], [defaultAccessor], null);
|
controlNameDir = new FormControlName(parent, [], [], [defaultAccessor], null);
|
||||||
controlNameDir.name = 'name';
|
controlNameDir.name = 'name';
|
||||||
(controlNameDir as{control: FormControl}).control = formModel;
|
(controlNameDir as {control: FormControl}).control = formModel;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reexport control properties', () => {
|
it('should reexport control properties', () => {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,195 +8,208 @@
|
||||||
import {fakeAsync, tick} from '@angular/core/testing';
|
import {fakeAsync, tick} from '@angular/core/testing';
|
||||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
|
import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
|
||||||
import {FormBuilder, Validators} from '@angular/forms';
|
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'});
|
||||||
|
|
||||||
expect(g.controls['login'].value).toEqual('some value');
|
expect(g.controls['login'].value).toEqual('some value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create controls from a boxed value', () => {
|
||||||
|
const g = b.group({'login': {value: 'some value', disabled: true}});
|
||||||
|
|
||||||
|
expect(g.controls['login'].value).toEqual('some value');
|
||||||
|
expect(g.controls['login'].disabled).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create controls from an array', () => {
|
||||||
|
const g = b.group(
|
||||||
|
{'login': ['some value'], 'password': ['some value', syncValidator, asyncValidator]});
|
||||||
|
|
||||||
|
expect(g.controls['login'].value).toEqual('some value');
|
||||||
|
expect(g.controls['password'].value).toEqual('some value');
|
||||||
|
expect(g.controls['password'].validator).toEqual(syncValidator);
|
||||||
|
expect(g.controls['password'].asyncValidator).toEqual(asyncValidator);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use controls whose form state is a primitive value', () => {
|
||||||
|
const g = b.group({'login': b.control('some value', syncValidator, asyncValidator)});
|
||||||
|
|
||||||
|
expect(g.controls['login'].value).toEqual('some value');
|
||||||
|
expect(g.controls['login'].validator).toBe(syncValidator);
|
||||||
|
expect(g.controls['login'].asyncValidator).toBe(asyncValidator);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support controls with no validators and whose form state is null', () => {
|
||||||
|
const g = b.group({'login': b.control(null)});
|
||||||
|
expect(g.controls['login'].value).toBeNull();
|
||||||
|
expect(g.controls['login'].validator).toBeNull();
|
||||||
|
expect(g.controls['login'].asyncValidator).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support controls with validators and whose form state is null', () => {
|
||||||
|
const g = b.group({'login': b.control(null, syncValidator, asyncValidator)});
|
||||||
|
expect(g.controls['login'].value).toBeNull();
|
||||||
|
expect(g.controls['login'].validator).toBe(syncValidator);
|
||||||
|
expect(g.controls['login'].asyncValidator).toBe(asyncValidator);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support controls with no validators and whose form state is undefined', () => {
|
||||||
|
const g = b.group({'login': b.control(undefined)});
|
||||||
|
expect(g.controls['login'].value).toBeNull();
|
||||||
|
expect(g.controls['login'].validator).toBeNull();
|
||||||
|
expect(g.controls['login'].asyncValidator).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support controls with validators and whose form state is undefined', () => {
|
||||||
|
const g = b.group({'login': b.control(undefined, syncValidator, asyncValidator)});
|
||||||
|
expect(g.controls['login'].value).toBeNull();
|
||||||
|
expect(g.controls['login'].validator).toBe(syncValidator);
|
||||||
|
expect(g.controls['login'].asyncValidator).toBe(asyncValidator);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create groups with a custom validator', () => {
|
||||||
|
const g = b.group(
|
||||||
|
{'login': 'some value'}, {'validator': syncValidator, 'asyncValidator': asyncValidator});
|
||||||
|
|
||||||
|
expect(g.validator).toBe(syncValidator);
|
||||||
|
expect(g.asyncValidator).toBe(asyncValidator);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create control arrays', () => {
|
||||||
|
const c = b.control('three');
|
||||||
|
const e = b.control(null);
|
||||||
|
const f = b.control(undefined);
|
||||||
|
const a = b.array(
|
||||||
|
['one', ['two', syncValidator], c, b.array(['four']), e, f], syncValidator, asyncValidator);
|
||||||
|
|
||||||
|
expect(a.value).toEqual(['one', 'two', 'three', ['four'], null, null]);
|
||||||
|
expect(a.validator).toBe(syncValidator);
|
||||||
|
expect(a.asyncValidator).toBe(asyncValidator);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create control arrays with multiple async validators', fakeAsync(() => {
|
||||||
|
function asyncValidator1() {
|
||||||
|
return of({'async1': true});
|
||||||
|
}
|
||||||
|
function asyncValidator2() {
|
||||||
|
return of({'async2': true});
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = b.array(['one', 'two'], null, [asyncValidator1, asyncValidator2]);
|
||||||
|
expect(a.value).toEqual(['one', 'two']);
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(a.errors).toEqual({'async1': true, 'async2': true});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create control arrays with multiple sync validators', () => {
|
||||||
|
function syncValidator1() {
|
||||||
|
return {'sync1': true};
|
||||||
|
}
|
||||||
|
function syncValidator2() {
|
||||||
|
return {'sync2': true};
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = b.array(['one', 'two'], [syncValidator1, syncValidator2]);
|
||||||
|
expect(a.value).toEqual(['one', 'two']);
|
||||||
|
expect(a.errors).toEqual({'sync1': true, 'sync2': true});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateOn', () => {
|
||||||
|
it('should default to on change', () => {
|
||||||
|
const c = b.control('');
|
||||||
|
expect(c.updateOn).toEqual('change');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create controls from a boxed value', () => {
|
it('should default to on change with an options obj', () => {
|
||||||
const g = b.group({'login': {value: 'some value', disabled: true}});
|
const c = b.control('', {validators: Validators.required});
|
||||||
|
expect(c.updateOn).toEqual('change');
|
||||||
expect(g.controls['login'].value).toEqual('some value');
|
|
||||||
expect(g.controls['login'].disabled).toEqual(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create controls from an array', () => {
|
it('should set updateOn when updating on blur', () => {
|
||||||
const g = b.group(
|
const c = b.control('', {updateOn: 'blur'});
|
||||||
{'login': ['some value'], 'password': ['some value', syncValidator, asyncValidator]});
|
expect(c.updateOn).toEqual('blur');
|
||||||
|
|
||||||
expect(g.controls['login'].value).toEqual('some value');
|
|
||||||
expect(g.controls['password'].value).toEqual('some value');
|
|
||||||
expect(g.controls['password'].validator).toEqual(syncValidator);
|
|
||||||
expect(g.controls['password'].asyncValidator).toEqual(asyncValidator);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use controls whose form state is a primitive value', () => {
|
describe('in groups and arrays', () => {
|
||||||
const g = b.group({'login': b.control('some value', syncValidator, asyncValidator)});
|
it('should default to group updateOn when not set in control', () => {
|
||||||
|
const g = b.group({one: b.control(''), two: b.control('')}, {updateOn: 'blur'});
|
||||||
|
|
||||||
expect(g.controls['login'].value).toEqual('some value');
|
expect(g.get('one')!.updateOn).toEqual('blur');
|
||||||
expect(g.controls['login'].validator).toBe(syncValidator);
|
expect(g.get('two')!.updateOn).toEqual('blur');
|
||||||
expect(g.controls['login'].asyncValidator).toBe(asyncValidator);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support controls with no validators and whose form state is null', () => {
|
|
||||||
const g = b.group({'login': b.control(null)});
|
|
||||||
expect(g.controls['login'].value).toBeNull();
|
|
||||||
expect(g.controls['login'].validator).toBeNull();
|
|
||||||
expect(g.controls['login'].asyncValidator).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support controls with validators and whose form state is null', () => {
|
|
||||||
const g = b.group({'login': b.control(null, syncValidator, asyncValidator)});
|
|
||||||
expect(g.controls['login'].value).toBeNull();
|
|
||||||
expect(g.controls['login'].validator).toBe(syncValidator);
|
|
||||||
expect(g.controls['login'].asyncValidator).toBe(asyncValidator);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support controls with no validators and whose form state is undefined', () => {
|
|
||||||
const g = b.group({'login': b.control(undefined)});
|
|
||||||
expect(g.controls['login'].value).toBeNull();
|
|
||||||
expect(g.controls['login'].validator).toBeNull();
|
|
||||||
expect(g.controls['login'].asyncValidator).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support controls with validators and whose form state is undefined', () => {
|
|
||||||
const g = b.group({'login': b.control(undefined, syncValidator, asyncValidator)});
|
|
||||||
expect(g.controls['login'].value).toBeNull();
|
|
||||||
expect(g.controls['login'].validator).toBe(syncValidator);
|
|
||||||
expect(g.controls['login'].asyncValidator).toBe(asyncValidator);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create groups with a custom validator', () => {
|
|
||||||
const g = b.group(
|
|
||||||
{'login': 'some value'}, {'validator': syncValidator, 'asyncValidator': asyncValidator});
|
|
||||||
|
|
||||||
expect(g.validator).toBe(syncValidator);
|
|
||||||
expect(g.asyncValidator).toBe(asyncValidator);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create control arrays', () => {
|
|
||||||
const c = b.control('three');
|
|
||||||
const e = b.control(null);
|
|
||||||
const f = b.control(undefined);
|
|
||||||
const a = b.array(
|
|
||||||
['one', ['two', syncValidator], c, b.array(['four']), e, f], syncValidator,
|
|
||||||
asyncValidator);
|
|
||||||
|
|
||||||
expect(a.value).toEqual(['one', 'two', 'three', ['four'], null, null]);
|
|
||||||
expect(a.validator).toBe(syncValidator);
|
|
||||||
expect(a.asyncValidator).toBe(asyncValidator);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create control arrays with multiple async validators', fakeAsync(() => {
|
|
||||||
function asyncValidator1() { return of ({'async1': true}); }
|
|
||||||
function asyncValidator2() { return of ({'async2': true}); }
|
|
||||||
|
|
||||||
const a = b.array(['one', 'two'], null, [asyncValidator1, asyncValidator2]);
|
|
||||||
expect(a.value).toEqual(['one', 'two']);
|
|
||||||
|
|
||||||
tick();
|
|
||||||
|
|
||||||
expect(a.errors).toEqual({'async1': true, 'async2': true});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should create control arrays with multiple sync validators', () => {
|
|
||||||
function syncValidator1() { return {'sync1': true}; }
|
|
||||||
function syncValidator2() { return {'sync2': true}; }
|
|
||||||
|
|
||||||
const a = b.array(['one', 'two'], [syncValidator1, syncValidator2]);
|
|
||||||
expect(a.value).toEqual(['one', 'two']);
|
|
||||||
expect(a.errors).toEqual({'sync1': true, 'sync2': true});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('updateOn', () => {
|
|
||||||
it('should default to on change', () => {
|
|
||||||
const c = b.control('');
|
|
||||||
expect(c.updateOn).toEqual('change');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should default to on change with an options obj', () => {
|
it('should default to array updateOn when not set in control', () => {
|
||||||
const c = b.control('', {validators: Validators.required});
|
const a = b.array([b.control(''), b.control('')], {updateOn: 'blur'});
|
||||||
expect(c.updateOn).toEqual('change');
|
|
||||||
|
expect(a.get([0])!.updateOn).toEqual('blur');
|
||||||
|
expect(a.get([1])!.updateOn).toEqual('blur');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set updateOn when updating on blur', () => {
|
it('should set updateOn with nested groups', () => {
|
||||||
const c = b.control('', {updateOn: 'blur'});
|
const g = b.group(
|
||||||
expect(c.updateOn).toEqual('blur');
|
{
|
||||||
|
group: b.group({one: b.control(''), two: b.control('')}),
|
||||||
|
},
|
||||||
|
{updateOn: 'blur'});
|
||||||
|
|
||||||
|
expect(g.get('group.one')!.updateOn).toEqual('blur');
|
||||||
|
expect(g.get('group.two')!.updateOn).toEqual('blur');
|
||||||
|
expect(g.get('group')!.updateOn).toEqual('blur');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('in groups and arrays', () => {
|
it('should set updateOn with nested arrays', () => {
|
||||||
it('should default to group updateOn when not set in control', () => {
|
const g = b.group(
|
||||||
const g = b.group({one: b.control(''), two: b.control('')}, {updateOn: 'blur'});
|
{
|
||||||
|
arr: b.array([b.control(''), b.control('')]),
|
||||||
|
},
|
||||||
|
{updateOn: 'blur'});
|
||||||
|
|
||||||
expect(g.get('one') !.updateOn).toEqual('blur');
|
expect(g.get(['arr', 0])!.updateOn).toEqual('blur');
|
||||||
expect(g.get('two') !.updateOn).toEqual('blur');
|
expect(g.get(['arr', 1])!.updateOn).toEqual('blur');
|
||||||
|
expect(g.get('arr')!.updateOn).toEqual('blur');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow control updateOn to override group updateOn', () => {
|
||||||
|
const g = b.group(
|
||||||
|
{one: b.control('', {updateOn: 'change'}), two: b.control('')}, {updateOn: 'blur'});
|
||||||
|
|
||||||
|
expect(g.get('one')!.updateOn).toEqual('change');
|
||||||
|
expect(g.get('two')!.updateOn).toEqual('blur');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set updateOn with complex setup', () => {
|
||||||
|
const g = b.group({
|
||||||
|
group: b.group(
|
||||||
|
{one: b.control('', {updateOn: 'change'}), two: b.control('')}, {updateOn: 'blur'}),
|
||||||
|
groupTwo: b.group({one: b.control('')}, {updateOn: 'submit'}),
|
||||||
|
three: b.control('')
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should default to array updateOn when not set in control', () => {
|
expect(g.get('group.one')!.updateOn).toEqual('change');
|
||||||
const a = b.array([b.control(''), b.control('')], {updateOn: 'blur'});
|
expect(g.get('group.two')!.updateOn).toEqual('blur');
|
||||||
|
expect(g.get('groupTwo.one')!.updateOn).toEqual('submit');
|
||||||
expect(a.get([0]) !.updateOn).toEqual('blur');
|
expect(g.get('three')!.updateOn).toEqual('change');
|
||||||
expect(a.get([1]) !.updateOn).toEqual('blur');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set updateOn with nested groups', () => {
|
|
||||||
const g = b.group(
|
|
||||||
{
|
|
||||||
group: b.group({one: b.control(''), two: b.control('')}),
|
|
||||||
},
|
|
||||||
{updateOn: 'blur'});
|
|
||||||
|
|
||||||
expect(g.get('group.one') !.updateOn).toEqual('blur');
|
|
||||||
expect(g.get('group.two') !.updateOn).toEqual('blur');
|
|
||||||
expect(g.get('group') !.updateOn).toEqual('blur');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set updateOn with nested arrays', () => {
|
|
||||||
const g = b.group(
|
|
||||||
{
|
|
||||||
arr: b.array([b.control(''), b.control('')]),
|
|
||||||
},
|
|
||||||
{updateOn: 'blur'});
|
|
||||||
|
|
||||||
expect(g.get(['arr', 0]) !.updateOn).toEqual('blur');
|
|
||||||
expect(g.get(['arr', 1]) !.updateOn).toEqual('blur');
|
|
||||||
expect(g.get('arr') !.updateOn).toEqual('blur');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow control updateOn to override group updateOn', () => {
|
|
||||||
const g = b.group(
|
|
||||||
{one: b.control('', {updateOn: 'change'}), two: b.control('')}, {updateOn: 'blur'});
|
|
||||||
|
|
||||||
expect(g.get('one') !.updateOn).toEqual('change');
|
|
||||||
expect(g.get('two') !.updateOn).toEqual('blur');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set updateOn with complex setup', () => {
|
|
||||||
const g = b.group({
|
|
||||||
group: b.group(
|
|
||||||
{one: b.control('', {updateOn: 'change'}), two: b.control('')}, {updateOn: 'blur'}),
|
|
||||||
groupTwo: b.group({one: b.control('')}, {updateOn: 'submit'}),
|
|
||||||
three: b.control('')
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(g.get('group.one') !.updateOn).toEqual('change');
|
|
||||||
expect(g.get('group.two') !.updateOn).toEqual('blur');
|
|
||||||
expect(g.get('groupTwo.one') !.updateOn).toEqual('submit');
|
|
||||||
expect(g.get('three') !.updateOn).toEqual('change');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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')});
|
||||||
|
@ -150,7 +147,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
||||||
});
|
});
|
||||||
fixture.componentInstance.form = form;
|
fixture.componentInstance.form = form;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(form.get('login') !.errors).toEqual({required: true});
|
expect(form.get('login')!.errors).toEqual({required: true});
|
||||||
|
|
||||||
const newForm = new FormGroup({
|
const newForm = new FormGroup({
|
||||||
'login': new FormControl(''),
|
'login': new FormControl(''),
|
||||||
|
@ -161,34 +158,31 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
||||||
fixture.componentInstance.form = newForm;
|
fixture.componentInstance.form = newForm;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(newForm.get('login') !.errors).toEqual({required: true});
|
expect(newForm.get('login')!.errors).toEqual({required: true});
|
||||||
});
|
});
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
expect(form.get('signin') !.valid).toBe(false);
|
expect(form.get('signin')!.valid).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
|
@ -359,7 +351,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
||||||
|
|
||||||
expect(newArr.value).toEqual(['last one']);
|
expect(newArr.value).toEqual(['last one']);
|
||||||
|
|
||||||
newArr.get([0]) !.setValue('set value');
|
newArr.get([0])!.setValue('set value');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
firstInput = fixture.debugElement.query(By.css('input')).nativeElement;
|
firstInput = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||||
|
@ -416,14 +408,12 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
||||||
|
|
||||||
expect(newArr.value).toEqual(['SF', 'LA', 'Tulsa']);
|
expect(newArr.value).toEqual(['SF', 'LA', 'Tulsa']);
|
||||||
|
|
||||||
newArr.get([2]) !.setValue('NY');
|
newArr.get([2])!.setValue('NY');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
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,14 +599,13 @@ 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', () => {
|
||||||
it('should emit ngSubmit event with the original submit event on submit', () => {
|
it('should emit ngSubmit event with the original submit event on submit', () => {
|
||||||
const fixture = initTest(FormGroupComp);
|
const fixture = initTest(FormGroupComp);
|
||||||
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
|
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
|
||||||
fixture.componentInstance.event = null !;
|
fixture.componentInstance.event = null!;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
|
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
|
||||||
|
@ -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', () => {
|
||||||
|
@ -736,7 +722,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
||||||
|
|
||||||
it('should work with single fields and async validators', fakeAsync(() => {
|
it('should work with single fields and async validators', fakeAsync(() => {
|
||||||
const fixture = initTest(FormControlComp);
|
const fixture = initTest(FormControlComp);
|
||||||
const control = new FormControl('', null !, uniqLoginAsyncValidator('good'));
|
const control = new FormControl('', null!, uniqLoginAsyncValidator('good'));
|
||||||
fixture.debugElement.componentInstance.control = control;
|
fixture.debugElement.componentInstance.control = control;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -1430,7 +1407,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
||||||
fixture.componentInstance.form = formGroup;
|
fixture.componentInstance.form = formGroup;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const values: (string | {[key: string]: string})[] = [];
|
const values: (string|{[key: string]: string})[] = [];
|
||||||
const streams = merge(
|
const streams = merge(
|
||||||
control.valueChanges, control.statusChanges, formGroup.valueChanges,
|
control.valueChanges, control.statusChanges, formGroup.valueChanges,
|
||||||
formGroup.statusChanges);
|
formGroup.statusChanges);
|
||||||
|
@ -1493,8 +1470,8 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
||||||
fixture.componentInstance.form = formGroup;
|
fixture.componentInstance.form = formGroup;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
formGroup.get('signin.login') !.setValidators(validatorSpy);
|
formGroup.get('signin.login')!.setValidators(validatorSpy);
|
||||||
formGroup.get('signin') !.setValidators(groupValidatorSpy);
|
formGroup.get('signin')!.setValidators(groupValidatorSpy);
|
||||||
|
|
||||||
const form = fixture.debugElement.query(By.css('form')).nativeElement;
|
const form = fixture.debugElement.query(By.css('form')).nativeElement;
|
||||||
dispatchEvent(form, 'submit');
|
dispatchEvent(form, 'submit');
|
||||||
|
@ -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', () => {
|
||||||
|
@ -1969,9 +1936,9 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
||||||
.toEqual(pattern.nativeElement.getAttribute('pattern'));
|
.toEqual(pattern.nativeElement.getAttribute('pattern'));
|
||||||
|
|
||||||
fixture.componentInstance.required = false;
|
fixture.componentInstance.required = false;
|
||||||
fixture.componentInstance.minLen = null !;
|
fixture.componentInstance.minLen = null!;
|
||||||
fixture.componentInstance.maxLen = null !;
|
fixture.componentInstance.maxLen = null!;
|
||||||
fixture.componentInstance.pattern = null !;
|
fixture.componentInstance.pattern = null!;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(form.hasError('required', ['login'])).toEqual(false);
|
expect(form.hasError('required', ['login'])).toEqual(false);
|
||||||
|
@ -2011,9 +1978,9 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
fixture.componentInstance.required = false;
|
fixture.componentInstance.required = false;
|
||||||
fixture.componentInstance.minLen = null !;
|
fixture.componentInstance.minLen = null!;
|
||||||
fixture.componentInstance.maxLen = null !;
|
fixture.componentInstance.maxLen = null!;
|
||||||
fixture.componentInstance.pattern = null !;
|
fixture.componentInstance.pattern = null!;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(newForm.hasError('required', ['login'])).toEqual(false);
|
expect(newForm.hasError('required', ['login'])).toEqual(false);
|
||||||
|
@ -2111,7 +2078,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
||||||
const fixture = initTest(FormControlComp);
|
const fixture = initTest(FormControlComp);
|
||||||
const resultArr: number[] = [];
|
const resultArr: number[] = [];
|
||||||
fixture.componentInstance.control =
|
fixture.componentInstance.control =
|
||||||
new FormControl('', null !, observableValidator(resultArr));
|
new FormControl('', null!, observableValidator(resultArr));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick(100);
|
tick(100);
|
||||||
|
|
||||||
|
@ -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,22 +2414,22 @@ 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">`})
|
||||||
class FormControlComp {
|
class FormControlComp {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
control !: FormControl;
|
control!: FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -2479,11 +2441,11 @@ class FormControlComp {
|
||||||
})
|
})
|
||||||
class FormGroupComp {
|
class FormGroupComp {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
control !: FormControl;
|
control!: FormControl;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
event !: Event;
|
event!: Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -2499,7 +2461,7 @@ class FormGroupComp {
|
||||||
})
|
})
|
||||||
class NestedFormGroupComp {
|
class NestedFormGroupComp {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -2515,9 +2477,9 @@ class NestedFormGroupComp {
|
||||||
})
|
})
|
||||||
class FormArrayComp {
|
class FormArrayComp {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
cityArray !: FormArray;
|
cityArray!: FormArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -2534,9 +2496,9 @@ class FormArrayComp {
|
||||||
})
|
})
|
||||||
class FormArrayNestedGroup {
|
class FormArrayNestedGroup {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
cityArray !: FormArray;
|
cityArray!: FormArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -2549,11 +2511,11 @@ class FormArrayNestedGroup {
|
||||||
})
|
})
|
||||||
class FormGroupNgModel {
|
class FormGroupNgModel {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
login !: string;
|
login!: string;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
password !: string;
|
password!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -2565,13 +2527,13 @@ class FormGroupNgModel {
|
||||||
})
|
})
|
||||||
class FormControlNgModel {
|
class FormControlNgModel {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
control !: FormControl;
|
control!: FormControl;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
login !: string;
|
login!: string;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
passwordControl !: FormControl;
|
passwordControl!: FormControl;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
password !: string;
|
password!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -2586,7 +2548,7 @@ class FormControlNgModel {
|
||||||
})
|
})
|
||||||
class LoginIsEmptyWrapper {
|
class LoginIsEmptyWrapper {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -2601,15 +2563,15 @@ class LoginIsEmptyWrapper {
|
||||||
})
|
})
|
||||||
class ValidationBindingsForm {
|
class ValidationBindingsForm {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
required !: boolean;
|
required!: boolean;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
minLen !: number;
|
minLen!: number;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
maxLen !: number;
|
maxLen!: number;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
pattern !: string;
|
pattern!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -2618,7 +2580,7 @@ class ValidationBindingsForm {
|
||||||
})
|
})
|
||||||
class FormControlCheckboxRequiredValidator {
|
class FormControlCheckboxRequiredValidator {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
control !: FormControl;
|
control!: FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -2630,5 +2592,5 @@ class FormControlCheckboxRequiredValidator {
|
||||||
})
|
})
|
||||||
class UniqLoginWrapper {
|
class UniqLoginWrapper {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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]});
|
||||||
|
@ -57,7 +56,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
expect(form.valid).toBe(false);
|
expect(form.valid).toBe(false);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should report properties which are written outside of template bindings', async() => {
|
it('should report properties which are written outside of template bindings', async () => {
|
||||||
// For example ngModel writes to `checked` property programmatically
|
// For example ngModel writes to `checked` property programmatically
|
||||||
// (template does not contain binding to `checked` explicitly)
|
// (template does not contain binding to `checked` explicitly)
|
||||||
// https://github.com/angular/angular/issues/33695
|
// https://github.com/angular/angular/issues/33695
|
||||||
|
@ -131,9 +130,9 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||||
expect(form.control.get('name') !.value).toEqual({first: 'Nancy', last: 'Drew'});
|
expect(form.control.get('name')!.value).toEqual({first: 'Nancy', last: 'Drew'});
|
||||||
expect(form.control.get('name.first') !.value).toEqual('Nancy');
|
expect(form.control.get('name.first')!.value).toEqual('Nancy');
|
||||||
expect(form.control.get('email') !.value).toEqual('some email');
|
expect(form.control.get('email')!.value).toEqual('some email');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should remove controls and control groups from form control model', fakeAsync(() => {
|
it('should remove controls and control groups from form control model', fakeAsync(() => {
|
||||||
|
@ -145,7 +144,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||||
expect(form.control.get('email') !.value).toEqual('some email');
|
expect(form.control.get('email')!.value).toEqual('some email');
|
||||||
expect(form.value).toEqual({name: {first: 'Nancy'}, email: 'some email'});
|
expect(form.value).toEqual({name: {first: 'Nancy'}, email: 'some email'});
|
||||||
|
|
||||||
// should remove individual control successfully
|
// should remove individual control successfully
|
||||||
|
@ -156,8 +155,8 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
expect(form.control.get('email')).toBe(null);
|
expect(form.control.get('email')).toBe(null);
|
||||||
expect(form.value).toEqual({name: {first: 'Nancy'}});
|
expect(form.value).toEqual({name: {first: 'Nancy'}});
|
||||||
|
|
||||||
expect(form.control.get('name') !.value).toEqual({first: 'Nancy'});
|
expect(form.control.get('name')!.value).toEqual({first: 'Nancy'});
|
||||||
expect(form.control.get('name.first') !.value).toEqual('Nancy');
|
expect(form.control.get('name.first')!.value).toEqual('Nancy');
|
||||||
|
|
||||||
// should remove form group successfully
|
// should remove form group successfully
|
||||||
fixture.componentInstance.groupShowing = false;
|
fixture.componentInstance.groupShowing = false;
|
||||||
|
@ -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();
|
||||||
|
@ -252,7 +250,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
it('should not create a template-driven form when ngNoForm is used', () => {
|
it('should not create a template-driven form when ngNoForm is used', () => {
|
||||||
const fixture = initTest(NgNoFormComp);
|
const fixture = initTest(NgNoFormComp);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.debugElement.children[0].providerTokens !.length).toEqual(0);
|
expect(fixture.debugElement.children[0].providerTokens!.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not add novalidate when ngNoForm is used', () => {
|
it('should not add novalidate when ngNoForm is used', () => {
|
||||||
|
@ -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 = '';
|
||||||
|
@ -700,8 +693,8 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||||
form.control.get('name') !.setValidators(groupValidatorSpy);
|
form.control.get('name')!.setValidators(groupValidatorSpy);
|
||||||
form.control.get('name.last') !.setValidators(validatorSpy);
|
form.control.get('name.last')!.setValidators(validatorSpy);
|
||||||
|
|
||||||
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
|
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
|
||||||
dispatchEvent(formEl, 'submit');
|
dispatchEvent(formEl, 'submit');
|
||||||
|
@ -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'};
|
||||||
|
@ -911,12 +902,12 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||||
const controlOne = form.control.get('one') !as FormControl;
|
const controlOne = form.control.get('one')! as FormControl;
|
||||||
expect((controlOne as any)._updateOn).toBeUndefined();
|
expect((controlOne as any)._updateOn).toBeUndefined();
|
||||||
expect(controlOne.updateOn)
|
expect(controlOne.updateOn)
|
||||||
.toEqual('blur', 'Expected first control to inherit updateOn from parent form.');
|
.toEqual('blur', 'Expected first control to inherit updateOn from parent form.');
|
||||||
|
|
||||||
const controlTwo = form.control.get('two') !as FormControl;
|
const controlTwo = form.control.get('two')! as FormControl;
|
||||||
expect((controlTwo as any)._updateOn).toBeUndefined();
|
expect((controlTwo as any)._updateOn).toBeUndefined();
|
||||||
expect(controlTwo.updateOn)
|
expect(controlTwo.updateOn)
|
||||||
.toEqual('blur', 'Expected last control to inherit updateOn from parent form.');
|
.toEqual('blur', 'Expected last control to inherit updateOn from parent form.');
|
||||||
|
@ -952,12 +943,12 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||||
const controlOne = form.control.get('one') !as FormControl;
|
const controlOne = form.control.get('one')! as FormControl;
|
||||||
expect((controlOne as any)._updateOn).toBeUndefined();
|
expect((controlOne as any)._updateOn).toBeUndefined();
|
||||||
expect(controlOne.updateOn)
|
expect(controlOne.updateOn)
|
||||||
.toEqual('change', 'Expected control updateOn to inherit form updateOn.');
|
.toEqual('change', 'Expected control updateOn to inherit form updateOn.');
|
||||||
|
|
||||||
const controlTwo = form.control.get('two') !as FormControl;
|
const controlTwo = form.control.get('two')! as FormControl;
|
||||||
expect((controlTwo as any)._updateOn)
|
expect((controlTwo as any)._updateOn)
|
||||||
.toEqual('blur', 'Expected control to set blur override.');
|
.toEqual('blur', 'Expected control to set blur override.');
|
||||||
expect(controlTwo.updateOn)
|
expect(controlTwo.updateOn)
|
||||||
|
@ -1016,15 +1007,13 @@ 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', () => {
|
||||||
it('should emit ngSubmit event with the original submit event on submit', fakeAsync(() => {
|
it('should emit ngSubmit event with the original submit event on submit', fakeAsync(() => {
|
||||||
const fixture = initTest(NgModelForm);
|
const fixture = initTest(NgModelForm);
|
||||||
fixture.componentInstance.event = null !;
|
fixture.componentInstance.event = null!;
|
||||||
|
|
||||||
const form = fixture.debugElement.query(By.css('form'));
|
const form = fixture.debugElement.query(By.css('form'));
|
||||||
dispatchEvent(form.nativeElement, 'submit');
|
dispatchEvent(form.nativeElement, 'submit');
|
||||||
|
@ -1097,11 +1086,11 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
expect(form.valid).toEqual(true);
|
expect(form.valid).toEqual(true);
|
||||||
expect(form.value).toEqual({});
|
expect(form.value).toEqual({});
|
||||||
|
|
||||||
let formValidity: string = undefined !;
|
let formValidity: string = undefined!;
|
||||||
let formValue: Object = undefined !;
|
let formValue: Object = undefined!;
|
||||||
|
|
||||||
form.statusChanges !.subscribe((status: string) => formValidity = status);
|
form.statusChanges!.subscribe((status: string) => formValidity = status);
|
||||||
form.valueChanges !.subscribe((value: string) => formValue = value);
|
form.valueChanges!.subscribe((value: string) => formValue = value);
|
||||||
|
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
|
@ -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';
|
||||||
|
@ -1138,10 +1128,11 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
inputEl.value = 'newValue';
|
inputEl.value = 'newValue';
|
||||||
dispatchEvent(inputEl, 'input');
|
dispatchEvent(inputEl, 'input');
|
||||||
|
|
||||||
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');
|
||||||
}));
|
}));
|
||||||
|
@ -1160,7 +1151,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||||
expect(form.value).toEqual({name: {first: '', last: 'Drew'}, email: 'some email'});
|
expect(form.value).toEqual({name: {first: '', last: 'Drew'}, email: 'some email'});
|
||||||
expect(form.valid).toBe(false);
|
expect(form.valid).toBe(false);
|
||||||
expect(form.control.get('name.first') !.disabled).toBe(false);
|
expect(form.control.get('name.first')!.disabled).toBe(false);
|
||||||
|
|
||||||
fixture.componentInstance.isDisabled = true;
|
fixture.componentInstance.isDisabled = true;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
@ -1168,7 +1159,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
|
|
||||||
expect(form.value).toEqual({name: {last: 'Drew'}, email: 'some email'});
|
expect(form.value).toEqual({name: {last: 'Drew'}, email: 'some email'});
|
||||||
expect(form.valid).toBe(true);
|
expect(form.valid).toBe(true);
|
||||||
expect(form.control.get('name.first') !.disabled).toBe(true);
|
expect(form.control.get('name.first')!.disabled).toBe(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should add disabled attribute in the UI if disable() is called programmatically',
|
it('should add disabled attribute in the UI if disable() is called programmatically',
|
||||||
|
@ -1180,7 +1171,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||||
form.control.get('name.first') !.disable();
|
form.control.get('name.first')!.disable();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
|
@ -1197,7 +1188,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
fixture.whenStable().then(() => {
|
fixture.whenStable().then(() => {
|
||||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||||
expect(form.control.get('name') !.disabled).toBe(true);
|
expect(form.control.get('name')!.disabled).toBe(true);
|
||||||
|
|
||||||
const customInput = fixture.debugElement.query(By.css('[name="custom"]'));
|
const customInput = fixture.debugElement.query(By.css('[name="custom"]'));
|
||||||
expect(customInput.nativeElement.disabled).toEqual(true);
|
expect(customInput.nativeElement.disabled).toEqual(true);
|
||||||
|
@ -1219,7 +1210,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick();
|
tick();
|
||||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||||
expect(form.control.get('name') !.disabled).toBe(true);
|
expect(form.control.get('name')!.disabled).toBe(true);
|
||||||
|
|
||||||
const input = fixture.debugElement.query(By.css('input'));
|
const input = fixture.debugElement.query(By.css('input'));
|
||||||
expect(input.nativeElement.disabled).toEqual(true);
|
expect(input.nativeElement.disabled).toEqual(true);
|
||||||
|
@ -1229,18 +1220,16 @@ 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();
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const control =
|
const control =
|
||||||
fixture.debugElement.children[0].injector.get(NgForm).control.get('checkbox') !;
|
fixture.debugElement.children[0].injector.get(NgForm).control.get('checkbox')!;
|
||||||
|
|
||||||
const input = fixture.debugElement.query(By.css('input'));
|
const input = fixture.debugElement.query(By.css('input'));
|
||||||
expect(input.nativeElement.checked).toBe(false);
|
expect(input.nativeElement.checked).toBe(false);
|
||||||
|
@ -1276,7 +1265,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const control =
|
const control =
|
||||||
fixture.debugElement.children[0].injector.get(NgForm).control.get('email') !;
|
fixture.debugElement.children[0].injector.get(NgForm).control.get('email')!;
|
||||||
|
|
||||||
const input = fixture.debugElement.query(By.css('input'));
|
const input = fixture.debugElement.query(By.css('input'));
|
||||||
expect(control.hasError('email')).toBe(false);
|
expect(control.hasError('email')).toBe(false);
|
||||||
|
@ -1476,9 +1465,9 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
.toEqual(pattern.nativeElement.getAttribute('pattern'));
|
.toEqual(pattern.nativeElement.getAttribute('pattern'));
|
||||||
|
|
||||||
fixture.componentInstance.required = false;
|
fixture.componentInstance.required = false;
|
||||||
fixture.componentInstance.minLen = null !;
|
fixture.componentInstance.minLen = null!;
|
||||||
fixture.componentInstance.maxLen = null !;
|
fixture.componentInstance.maxLen = null!;
|
||||||
fixture.componentInstance.pattern = null !;
|
fixture.componentInstance.pattern = null!;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(form.control.hasError('required', ['required'])).toEqual(false);
|
expect(form.control.hasError('required', ['required'])).toEqual(false);
|
||||||
|
@ -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();
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1662,7 +1648,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
||||||
})
|
})
|
||||||
class StandaloneNgModel {
|
class StandaloneNgModel {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
name !: string;
|
name!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1675,9 +1661,9 @@ class StandaloneNgModel {
|
||||||
})
|
})
|
||||||
class NgModelForm {
|
class NgModelForm {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
name !: string | null;
|
name!: string|null;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
event !: Event;
|
event!: Event;
|
||||||
options = {};
|
options = {};
|
||||||
|
|
||||||
onReset() {}
|
onReset() {}
|
||||||
|
@ -1701,13 +1687,13 @@ class NgModelNativeValidateForm {
|
||||||
})
|
})
|
||||||
class NgModelGroupForm {
|
class NgModelGroupForm {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
first !: string;
|
first!: string;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
last !: string;
|
last!: string;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
email !: string;
|
email!: string;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
isDisabled !: boolean;
|
isDisabled!: boolean;
|
||||||
options = {updateOn: 'change'};
|
options = {updateOn: 'change'};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1724,7 +1710,7 @@ class NgModelGroupForm {
|
||||||
})
|
})
|
||||||
class NgModelValidBinding {
|
class NgModelValidBinding {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
first !: string;
|
first!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1741,11 +1727,11 @@ class NgModelValidBinding {
|
||||||
})
|
})
|
||||||
class NgModelNgIfForm {
|
class NgModelNgIfForm {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
first !: string;
|
first!: string;
|
||||||
groupShowing = true;
|
groupShowing = true;
|
||||||
emailShowing = true;
|
emailShowing = true;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
email !: string;
|
email!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1781,9 +1767,9 @@ class InvalidNgModelNoName {
|
||||||
})
|
})
|
||||||
class NgModelOptionsStandalone {
|
class NgModelOptionsStandalone {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
one !: string;
|
one!: string;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
two !: string;
|
two!: string;
|
||||||
options: {name?: string, standalone?: boolean, updateOn?: string} = {standalone: true};
|
options: {name?: string, standalone?: boolean, updateOn?: string} = {standalone: true};
|
||||||
formOptions = {};
|
formOptions = {};
|
||||||
}
|
}
|
||||||
|
@ -1801,13 +1787,13 @@ class NgModelOptionsStandalone {
|
||||||
})
|
})
|
||||||
class NgModelValidationBindings {
|
class NgModelValidationBindings {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
required !: boolean;
|
required!: boolean;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
minLen !: number;
|
minLen!: number;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
maxLen !: number;
|
maxLen!: number;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
pattern !: string;
|
pattern!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1820,11 +1806,11 @@ class NgModelValidationBindings {
|
||||||
})
|
})
|
||||||
class NgModelMultipleValidators {
|
class NgModelMultipleValidators {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
required !: boolean;
|
required!: boolean;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
minLen !: number;
|
minLen!: number;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
pattern !: string | RegExp;
|
pattern!: string|RegExp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -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({
|
||||||
|
@ -1873,11 +1860,13 @@ class NgModelAsyncValidation {
|
||||||
})
|
})
|
||||||
class NgModelChangesForm {
|
class NgModelChangesForm {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
name !: string;
|
name!: string;
|
||||||
events: string[] = [];
|
events: string[] = [];
|
||||||
options: any;
|
options: any;
|
||||||
|
|
||||||
log() { this.events.push('fired'); }
|
log() {
|
||||||
|
this.events.push('fired');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
|
@ -11,453 +11,487 @@ import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
|
||||||
import {AbstractControl, AsyncValidatorFn, FormArray, FormControl, Validators} from '@angular/forms';
|
import {AbstractControl, AsyncValidatorFn, FormArray, FormControl, Validators} from '@angular/forms';
|
||||||
import {normalizeAsyncValidator} from '@angular/forms/src/directives/normalize_validator';
|
import {normalizeAsyncValidator} from '@angular/forms/src/directives/normalize_validator';
|
||||||
import {AsyncValidator, ValidationErrors, ValidatorFn} from '@angular/forms/src/directives/validators';
|
import {AsyncValidator, ValidationErrors, ValidatorFn} from '@angular/forms/src/directives/validators';
|
||||||
import {Observable, of , timer} from 'rxjs';
|
import {Observable, of, timer} from 'rxjs';
|
||||||
import {first, map} from 'rxjs/operators';
|
import {first, map} from 'rxjs/operators';
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
function validator(key: string, error: any): ValidatorFn {
|
function validator(key: string, error: any): ValidatorFn {
|
||||||
return (c: AbstractControl) => {
|
return (c: AbstractControl) => {
|
||||||
const r: ValidationErrors = {};
|
const r: ValidationErrors = {};
|
||||||
r[key] = error;
|
r[key] = error;
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class AsyncValidatorDirective implements AsyncValidator {
|
||||||
|
constructor(private expected: string, private error: any) {}
|
||||||
|
|
||||||
|
validate(c: any): Observable<ValidationErrors> {
|
||||||
|
return Observable.create((obs: any) => {
|
||||||
|
const error = this.expected !== c.value ? this.error : null;
|
||||||
|
obs.next(error);
|
||||||
|
obs.complete();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class AsyncValidatorDirective implements AsyncValidator {
|
describe('Validators', () => {
|
||||||
constructor(private expected: string, private error: any) {}
|
describe('min', () => {
|
||||||
|
it('should not error on an empty string', () => {
|
||||||
validate(c: any): Observable<ValidationErrors> {
|
expect(Validators.min(2)(new FormControl(''))).toBeNull();
|
||||||
return Observable.create((obs: any) => {
|
|
||||||
const error = this.expected !== c.value ? this.error : null;
|
|
||||||
obs.next(error);
|
|
||||||
obs.complete();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Validators', () => {
|
|
||||||
describe('min', () => {
|
|
||||||
it('should not error on an empty string',
|
|
||||||
() => { expect(Validators.min(2)(new FormControl(''))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should not error on null',
|
|
||||||
() => { expect(Validators.min(2)(new FormControl(null))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should not error on undefined',
|
|
||||||
() => { expect(Validators.min(2)(new FormControl(undefined))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should return null if NaN after parsing',
|
|
||||||
() => { expect(Validators.min(2)(new FormControl('a'))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should return a validation error on small values', () => {
|
|
||||||
expect(Validators.min(2)(new FormControl(1))).toEqual({'min': {'min': 2, 'actual': 1}});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a validation error on small values converted from strings', () => {
|
|
||||||
expect(Validators.min(2)(new FormControl('1'))).toEqual({'min': {'min': 2, 'actual': '1'}});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not error on big values',
|
|
||||||
() => { expect(Validators.min(2)(new FormControl(3))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should not error on equal values',
|
|
||||||
() => { expect(Validators.min(2)(new FormControl(2))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should not error on equal values when value is string',
|
|
||||||
() => { expect(Validators.min(2)(new FormControl('2'))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should validate as expected when min value is a string', () => {
|
|
||||||
expect(Validators.min('2' as any)(new FormControl(1))).toEqual({
|
|
||||||
'min': {'min': '2', 'actual': 1}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null if min value is undefined',
|
|
||||||
() => { expect(Validators.min(undefined as any)(new FormControl(3))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should return null if min value is null',
|
|
||||||
() => { expect(Validators.min(null as any)(new FormControl(3))).toBeNull(); });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('max', () => {
|
it('should not error on null', () => {
|
||||||
it('should not error on an empty string',
|
expect(Validators.min(2)(new FormControl(null))).toBeNull();
|
||||||
() => { expect(Validators.max(2)(new FormControl(''))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should not error on null',
|
|
||||||
() => { expect(Validators.max(2)(new FormControl(null))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should not error on undefined',
|
|
||||||
() => { expect(Validators.max(2)(new FormControl(undefined))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should return null if NaN after parsing',
|
|
||||||
() => { expect(Validators.max(2)(new FormControl('aaa'))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should return a validation error on big values', () => {
|
|
||||||
expect(Validators.max(2)(new FormControl(3))).toEqual({'max': {'max': 2, 'actual': 3}});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a validation error on big values converted from strings', () => {
|
|
||||||
expect(Validators.max(2)(new FormControl('3'))).toEqual({'max': {'max': 2, 'actual': '3'}});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not error on small values',
|
|
||||||
() => { expect(Validators.max(2)(new FormControl(1))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should not error on equal values',
|
|
||||||
() => { expect(Validators.max(2)(new FormControl(2))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should not error on equal values when value is string',
|
|
||||||
() => { expect(Validators.max(2)(new FormControl('2'))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should validate as expected when max value is a string', () => {
|
|
||||||
expect(Validators.max('2' as any)(new FormControl(3))).toEqual({
|
|
||||||
'max': {'max': '2', 'actual': 3}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null if max value is undefined',
|
|
||||||
() => { expect(Validators.max(undefined as any)(new FormControl(3))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should return null if max value is null',
|
|
||||||
() => { expect(Validators.max(null as any)(new FormControl(3))).toBeNull(); });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not error on undefined', () => {
|
||||||
describe('required', () => {
|
expect(Validators.min(2)(new FormControl(undefined))).toBeNull();
|
||||||
it('should error on an empty string',
|
|
||||||
() => { expect(Validators.required(new FormControl(''))).toEqual({'required': true}); });
|
|
||||||
|
|
||||||
it('should error on null',
|
|
||||||
() => { expect(Validators.required(new FormControl(null))).toEqual({'required': true}); });
|
|
||||||
|
|
||||||
it('should not error on undefined', () => {
|
|
||||||
expect(Validators.required(new FormControl(undefined))).toEqual({'required': true});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not error on a non-empty string',
|
|
||||||
() => { expect(Validators.required(new FormControl('not empty'))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should accept zero as valid',
|
|
||||||
() => { expect(Validators.required(new FormControl(0))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should error on an empty array',
|
|
||||||
() => expect(Validators.required(new FormControl([]))).toEqual({'required': true}));
|
|
||||||
|
|
||||||
it('should not error on a non-empty array',
|
|
||||||
() => expect(Validators.required(new FormControl([1, 2]))).toBeNull());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('requiredTrue', () => {
|
it('should return null if NaN after parsing', () => {
|
||||||
it('should error on false',
|
expect(Validators.min(2)(new FormControl('a'))).toBeNull();
|
||||||
() => expect(Validators.requiredTrue(new FormControl(false))).toEqual({'required': true}));
|
|
||||||
|
|
||||||
it('should not error on true',
|
|
||||||
() => expect(Validators.requiredTrue(new FormControl(true))).toBeNull());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('email', () => {
|
it('should return a validation error on small values', () => {
|
||||||
it('should not error on an empty string',
|
expect(Validators.min(2)(new FormControl(1))).toEqual({'min': {'min': 2, 'actual': 1}});
|
||||||
() => expect(Validators.email(new FormControl(''))).toBeNull());
|
|
||||||
|
|
||||||
it('should not error on null',
|
|
||||||
() => expect(Validators.email(new FormControl(null))).toBeNull());
|
|
||||||
|
|
||||||
it('should error on invalid email',
|
|
||||||
() => expect(Validators.email(new FormControl('some text'))).toEqual({'email': true}));
|
|
||||||
|
|
||||||
it('should not error on valid email',
|
|
||||||
() => expect(Validators.email(new FormControl('test@gmail.com'))).toBeNull());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('minLength', () => {
|
it('should return a validation error on small values converted from strings', () => {
|
||||||
it('should not error on an empty string',
|
expect(Validators.min(2)(new FormControl('1'))).toEqual({'min': {'min': 2, 'actual': '1'}});
|
||||||
() => { expect(Validators.minLength(2)(new FormControl(''))).toBeNull(); });
|
});
|
||||||
|
|
||||||
it('should not error on null',
|
it('should not error on big values', () => {
|
||||||
() => { expect(Validators.minLength(2)(new FormControl(null))).toBeNull(); });
|
expect(Validators.min(2)(new FormControl(3))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('should not error on undefined',
|
it('should not error on equal values', () => {
|
||||||
() => { expect(Validators.minLength(2)(new FormControl(undefined))).toBeNull(); });
|
expect(Validators.min(2)(new FormControl(2))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('should not error on valid strings',
|
it('should not error on equal values when value is string', () => {
|
||||||
() => { expect(Validators.minLength(2)(new FormControl('aa'))).toBeNull(); });
|
expect(Validators.min(2)(new FormControl('2'))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('should error on short strings', () => {
|
it('should validate as expected when min value is a string', () => {
|
||||||
expect(Validators.minLength(2)(new FormControl('a'))).toEqual({
|
expect(Validators.min('2' as any)(new FormControl(1))).toEqual({
|
||||||
'minlength': {'requiredLength': 2, 'actualLength': 1}
|
'min': {'min': '2', 'actual': 1}
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not error when FormArray has valid length', () => {
|
|
||||||
const fa = new FormArray([new FormControl(''), new FormControl('')]);
|
|
||||||
expect(Validators.minLength(2)(fa)).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should error when FormArray has invalid length', () => {
|
|
||||||
const fa = new FormArray([new FormControl('')]);
|
|
||||||
expect(Validators.minLength(2)(fa)).toEqual({
|
|
||||||
'minlength': {'requiredLength': 2, 'actualLength': 1}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('maxLength', () => {
|
it('should return null if min value is undefined', () => {
|
||||||
it('should not error on an empty string',
|
expect(Validators.min(undefined as any)(new FormControl(3))).toBeNull();
|
||||||
() => { expect(Validators.maxLength(2)(new FormControl(''))).toBeNull(); });
|
});
|
||||||
|
|
||||||
it('should not error on null',
|
it('should return null if min value is null', () => {
|
||||||
() => { expect(Validators.maxLength(2)(new FormControl(null))).toBeNull(); });
|
expect(Validators.min(null as any)(new FormControl(3))).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should not error on undefined',
|
describe('max', () => {
|
||||||
() => { expect(Validators.maxLength(2)(new FormControl(undefined))).toBeNull(); });
|
it('should not error on an empty string', () => {
|
||||||
|
expect(Validators.max(2)(new FormControl(''))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('should not error on valid strings',
|
it('should not error on null', () => {
|
||||||
() => { expect(Validators.maxLength(2)(new FormControl('aa'))).toBeNull(); });
|
expect(Validators.max(2)(new FormControl(null))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('should error on long strings', () => {
|
it('should not error on undefined', () => {
|
||||||
expect(Validators.maxLength(2)(new FormControl('aaa'))).toEqual({
|
expect(Validators.max(2)(new FormControl(undefined))).toBeNull();
|
||||||
'maxlength': {'requiredLength': 2, 'actualLength': 3}
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not error when FormArray has valid length', () => {
|
it('should return null if NaN after parsing', () => {
|
||||||
const fa = new FormArray([new FormControl(''), new FormControl('')]);
|
expect(Validators.max(2)(new FormControl('aaa'))).toBeNull();
|
||||||
expect(Validators.maxLength(2)(fa)).toBeNull();
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('should error when FormArray has invalid length', () => {
|
it('should return a validation error on big values', () => {
|
||||||
const fa = new FormArray([new FormControl(''), new FormControl('')]);
|
expect(Validators.max(2)(new FormControl(3))).toEqual({'max': {'max': 2, 'actual': 3}});
|
||||||
expect(Validators.maxLength(1)(fa)).toEqual({
|
});
|
||||||
'maxlength': {'requiredLength': 1, 'actualLength': 2}
|
|
||||||
});
|
it('should return a validation error on big values converted from strings', () => {
|
||||||
|
expect(Validators.max(2)(new FormControl('3'))).toEqual({'max': {'max': 2, 'actual': '3'}});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on small values', () => {
|
||||||
|
expect(Validators.max(2)(new FormControl(1))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on equal values', () => {
|
||||||
|
expect(Validators.max(2)(new FormControl(2))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on equal values when value is string', () => {
|
||||||
|
expect(Validators.max(2)(new FormControl('2'))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate as expected when max value is a string', () => {
|
||||||
|
expect(Validators.max('2' as any)(new FormControl(3))).toEqual({
|
||||||
|
'max': {'max': '2', 'actual': 3}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('pattern', () => {
|
it('should return null if max value is undefined', () => {
|
||||||
it('should not error on an empty string',
|
expect(Validators.max(undefined as any)(new FormControl(3))).toBeNull();
|
||||||
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(''))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should not error on null',
|
|
||||||
() => { expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should not error on undefined', () => {
|
|
||||||
expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(undefined))).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not error on null value and "null" pattern',
|
|
||||||
() => { expect(Validators.pattern('null')(new FormControl(null))).toBeNull(); });
|
|
||||||
|
|
||||||
it('should not error on valid strings',
|
|
||||||
() => expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaAA'))).toBeNull());
|
|
||||||
|
|
||||||
it('should error on failure to match string', () => {
|
|
||||||
expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaa0'))).toEqual({
|
|
||||||
'pattern': {'requiredPattern': '^[a-zA-Z ]*$', 'actualValue': 'aaa0'}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should accept RegExp object', () => {
|
|
||||||
const pattern: RegExp = new RegExp('[a-zA-Z ]+');
|
|
||||||
expect(Validators.pattern(pattern)(new FormControl('aaAA'))).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should error on failure to match RegExp object', () => {
|
|
||||||
const pattern: RegExp = new RegExp('^[a-zA-Z ]*$');
|
|
||||||
expect(Validators.pattern(pattern)(new FormControl('aaa0'))).toEqual({
|
|
||||||
'pattern': {'requiredPattern': '/^[a-zA-Z ]*$/', 'actualValue': 'aaa0'}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not error on "null" pattern',
|
|
||||||
() => expect(Validators.pattern(null !)(new FormControl('aaAA'))).toBeNull());
|
|
||||||
|
|
||||||
it('should not error on "undefined" pattern',
|
|
||||||
() => expect(Validators.pattern(undefined !)(new FormControl('aaAA'))).toBeNull());
|
|
||||||
|
|
||||||
it('should work with pattern string containing both boundary symbols',
|
|
||||||
() => expect(Validators.pattern('^[aA]*$')(new FormControl('aaAA'))).toBeNull());
|
|
||||||
|
|
||||||
it('should work with pattern string containing only start boundary symbols',
|
|
||||||
() => expect(Validators.pattern('^[aA]*')(new FormControl('aaAA'))).toBeNull());
|
|
||||||
|
|
||||||
it('should work with pattern string containing only end boundary symbols',
|
|
||||||
() => expect(Validators.pattern('[aA]*$')(new FormControl('aaAA'))).toBeNull());
|
|
||||||
|
|
||||||
it('should work with pattern string not containing any boundary symbols',
|
|
||||||
() => expect(Validators.pattern('[aA]*')(new FormControl('aaAA'))).toBeNull());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('compose', () => {
|
it('should return null if max value is null', () => {
|
||||||
it('should return null when given null',
|
expect(Validators.max(null as any)(new FormControl(3))).toBeNull();
|
||||||
() => { expect(Validators.compose(null !)).toBe(null); });
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('required', () => {
|
||||||
|
it('should error on an empty string', () => {
|
||||||
|
expect(Validators.required(new FormControl(''))).toEqual({'required': true});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error on null', () => {
|
||||||
|
expect(Validators.required(new FormControl(null))).toEqual({'required': true});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on undefined', () => {
|
||||||
|
expect(Validators.required(new FormControl(undefined))).toEqual({'required': true});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on a non-empty string', () => {
|
||||||
|
expect(Validators.required(new FormControl('not empty'))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept zero as valid', () => {
|
||||||
|
expect(Validators.required(new FormControl(0))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error on an empty array',
|
||||||
|
() => expect(Validators.required(new FormControl([]))).toEqual({'required': true}));
|
||||||
|
|
||||||
|
it('should not error on a non-empty array',
|
||||||
|
() => expect(Validators.required(new FormControl([1, 2]))).toBeNull());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('requiredTrue', () => {
|
||||||
|
it('should error on false',
|
||||||
|
() => expect(Validators.requiredTrue(new FormControl(false))).toEqual({'required': true}));
|
||||||
|
|
||||||
|
it('should not error on true',
|
||||||
|
() => expect(Validators.requiredTrue(new FormControl(true))).toBeNull());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('email', () => {
|
||||||
|
it('should not error on an empty string',
|
||||||
|
() => expect(Validators.email(new FormControl(''))).toBeNull());
|
||||||
|
|
||||||
|
it('should not error on null',
|
||||||
|
() => expect(Validators.email(new FormControl(null))).toBeNull());
|
||||||
|
|
||||||
|
it('should error on invalid email',
|
||||||
|
() => expect(Validators.email(new FormControl('some text'))).toEqual({'email': true}));
|
||||||
|
|
||||||
|
it('should not error on valid email',
|
||||||
|
() => expect(Validators.email(new FormControl('test@gmail.com'))).toBeNull());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('minLength', () => {
|
||||||
|
it('should not error on an empty string', () => {
|
||||||
|
expect(Validators.minLength(2)(new FormControl(''))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on null', () => {
|
||||||
|
expect(Validators.minLength(2)(new FormControl(null))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on undefined', () => {
|
||||||
|
expect(Validators.minLength(2)(new FormControl(undefined))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on valid strings', () => {
|
||||||
|
expect(Validators.minLength(2)(new FormControl('aa'))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error on short strings', () => {
|
||||||
|
expect(Validators.minLength(2)(new FormControl('a'))).toEqual({
|
||||||
|
'minlength': {'requiredLength': 2, 'actualLength': 1}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error when FormArray has valid length', () => {
|
||||||
|
const fa = new FormArray([new FormControl(''), new FormControl('')]);
|
||||||
|
expect(Validators.minLength(2)(fa)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error when FormArray has invalid length', () => {
|
||||||
|
const fa = new FormArray([new FormControl('')]);
|
||||||
|
expect(Validators.minLength(2)(fa)).toEqual({
|
||||||
|
'minlength': {'requiredLength': 2, 'actualLength': 1}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('maxLength', () => {
|
||||||
|
it('should not error on an empty string', () => {
|
||||||
|
expect(Validators.maxLength(2)(new FormControl(''))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on null', () => {
|
||||||
|
expect(Validators.maxLength(2)(new FormControl(null))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on undefined', () => {
|
||||||
|
expect(Validators.maxLength(2)(new FormControl(undefined))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on valid strings', () => {
|
||||||
|
expect(Validators.maxLength(2)(new FormControl('aa'))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error on long strings', () => {
|
||||||
|
expect(Validators.maxLength(2)(new FormControl('aaa'))).toEqual({
|
||||||
|
'maxlength': {'requiredLength': 2, 'actualLength': 3}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error when FormArray has valid length', () => {
|
||||||
|
const fa = new FormArray([new FormControl(''), new FormControl('')]);
|
||||||
|
expect(Validators.maxLength(2)(fa)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error when FormArray has invalid length', () => {
|
||||||
|
const fa = new FormArray([new FormControl(''), new FormControl('')]);
|
||||||
|
expect(Validators.maxLength(1)(fa)).toEqual({
|
||||||
|
'maxlength': {'requiredLength': 1, 'actualLength': 2}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pattern', () => {
|
||||||
|
it('should not error on an empty string', () => {
|
||||||
|
expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(''))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on null', () => {
|
||||||
|
expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(null))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on undefined', () => {
|
||||||
|
expect(Validators.pattern('[a-zA-Z ]+')(new FormControl(undefined))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on null value and "null" pattern', () => {
|
||||||
|
expect(Validators.pattern('null')(new FormControl(null))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on valid strings',
|
||||||
|
() => expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaAA'))).toBeNull());
|
||||||
|
|
||||||
|
it('should error on failure to match string', () => {
|
||||||
|
expect(Validators.pattern('[a-zA-Z ]*')(new FormControl('aaa0'))).toEqual({
|
||||||
|
'pattern': {'requiredPattern': '^[a-zA-Z ]*$', 'actualValue': 'aaa0'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept RegExp object', () => {
|
||||||
|
const pattern: RegExp = new RegExp('[a-zA-Z ]+');
|
||||||
|
expect(Validators.pattern(pattern)(new FormControl('aaAA'))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should error on failure to match RegExp object', () => {
|
||||||
|
const pattern: RegExp = new RegExp('^[a-zA-Z ]*$');
|
||||||
|
expect(Validators.pattern(pattern)(new FormControl('aaa0'))).toEqual({
|
||||||
|
'pattern': {'requiredPattern': '/^[a-zA-Z ]*$/', 'actualValue': 'aaa0'}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not error on "null" pattern',
|
||||||
|
() => expect(Validators.pattern(null!)(new FormControl('aaAA'))).toBeNull());
|
||||||
|
|
||||||
|
it('should not error on "undefined" pattern',
|
||||||
|
() => expect(Validators.pattern(undefined!)(new FormControl('aaAA'))).toBeNull());
|
||||||
|
|
||||||
|
it('should work with pattern string containing both boundary symbols',
|
||||||
|
() => expect(Validators.pattern('^[aA]*$')(new FormControl('aaAA'))).toBeNull());
|
||||||
|
|
||||||
|
it('should work with pattern string containing only start boundary symbols',
|
||||||
|
() => expect(Validators.pattern('^[aA]*')(new FormControl('aaAA'))).toBeNull());
|
||||||
|
|
||||||
|
it('should work with pattern string containing only end boundary symbols',
|
||||||
|
() => expect(Validators.pattern('[aA]*$')(new FormControl('aaAA'))).toBeNull());
|
||||||
|
|
||||||
|
it('should work with pattern string not containing any boundary symbols',
|
||||||
|
() => expect(Validators.pattern('[aA]*')(new FormControl('aaAA'))).toBeNull());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compose', () => {
|
||||||
|
it('should return null when given null', () => {
|
||||||
|
expect(Validators.compose(null!)).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should collect errors from all the validators', () => {
|
||||||
|
const c = Validators.compose([validator('a', true), validator('b', true)])!;
|
||||||
|
expect(c(new FormControl(''))).toEqual({'a': true, 'b': true});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should run validators left to right', () => {
|
||||||
|
const c = Validators.compose([validator('a', 1), validator('a', 2)])!;
|
||||||
|
expect(c(new FormControl(''))).toEqual({'a': 2});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when no errors', () => {
|
||||||
|
const c = Validators.compose([Validators.nullValidator, Validators.nullValidator])!;
|
||||||
|
expect(c(new FormControl(''))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore nulls', () => {
|
||||||
|
const c = Validators.compose([null!, Validators.required])!;
|
||||||
|
expect(c(new FormControl(''))).toEqual({'required': true});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('composeAsync', () => {
|
||||||
|
describe('promises', () => {
|
||||||
|
function promiseValidator(response: {[key: string]: any}): AsyncValidatorFn {
|
||||||
|
return (c: AbstractControl) => {
|
||||||
|
const res = c.value != 'expected' ? response : null;
|
||||||
|
return Promise.resolve(res);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should return null when given null', () => {
|
||||||
|
expect(Validators.composeAsync(null!)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should collect errors from all the validators', fakeAsync(() => {
|
||||||
|
const v = Validators.composeAsync(
|
||||||
|
[promiseValidator({'one': true}), promiseValidator({'two': true})])!;
|
||||||
|
|
||||||
|
let errorMap: {[key: string]: any}|null = undefined!;
|
||||||
|
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(errorMap!).toEqual({'one': true, 'two': true});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => {
|
||||||
|
const v = Validators.composeAsync(
|
||||||
|
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))])!;
|
||||||
|
|
||||||
|
let errorMap: {[key: string]: any}|null = undefined!;
|
||||||
|
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(errorMap!).toEqual({'one': true});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should return null when no errors', fakeAsync(() => {
|
||||||
|
const v = Validators.composeAsync([promiseValidator({'one': true})])!;
|
||||||
|
|
||||||
|
let errorMap: {[key: string]: any}|null = undefined!;
|
||||||
|
(v(new FormControl('expected')) as Observable<ValidationErrors|null>)
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(errorMap).toBeNull();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should ignore nulls', fakeAsync(() => {
|
||||||
|
const v = Validators.composeAsync([promiseValidator({'one': true}), null!])!;
|
||||||
|
|
||||||
|
let errorMap: {[key: string]: any}|null = undefined!;
|
||||||
|
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(errorMap!).toEqual({'one': true});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('observables', () => {
|
||||||
|
function observableValidator(response: {[key: string]: any}): AsyncValidatorFn {
|
||||||
|
return (c: AbstractControl) => {
|
||||||
|
const res = c.value != 'expected' ? response : null;
|
||||||
|
return of(res);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should return null when given null', () => {
|
||||||
|
expect(Validators.composeAsync(null!)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
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 v = Validators.composeAsync(
|
||||||
expect(c(new FormControl(''))).toEqual({'a': true, 'b': true});
|
[observableValidator({'one': true}), observableValidator({'two': true})])!;
|
||||||
|
|
||||||
|
let errorMap: {[key: string]: any}|null = undefined!;
|
||||||
|
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors);
|
||||||
|
|
||||||
|
expect(errorMap!).toEqual({'one': true, 'two': true});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run validators left to right', () => {
|
it('should normalize and evaluate async validator-directives correctly', () => {
|
||||||
const c = Validators.compose([validator('a', 1), validator('a', 2)]) !;
|
const v = Validators.composeAsync(
|
||||||
expect(c(new FormControl(''))).toEqual({'a': 2});
|
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))])!;
|
||||||
|
|
||||||
|
let errorMap: {[key: string]: any}|null = undefined!;
|
||||||
|
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors)!;
|
||||||
|
|
||||||
|
expect(errorMap!).toEqual({'one': true});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null when no errors', () => {
|
it('should return null when no errors', () => {
|
||||||
const c = Validators.compose([Validators.nullValidator, Validators.nullValidator]) !;
|
const v = Validators.composeAsync([observableValidator({'one': true})])!;
|
||||||
expect(c(new FormControl(''))).toBeNull();
|
|
||||||
|
let errorMap: {[key: string]: any}|null = undefined!;
|
||||||
|
(v(new FormControl('expected')) as Observable<ValidationErrors|null>)
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors);
|
||||||
|
|
||||||
|
expect(errorMap).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore nulls', () => {
|
it('should ignore nulls', () => {
|
||||||
const c = Validators.compose([null !, Validators.required]) !;
|
const v = Validators.composeAsync([observableValidator({'one': true}), null!])!;
|
||||||
expect(c(new FormControl(''))).toEqual({'required': true});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('composeAsync', () => {
|
let errorMap: {[key: string]: any}|null = undefined!;
|
||||||
|
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors);
|
||||||
|
|
||||||
describe('promises', () => {
|
expect(errorMap!).toEqual({'one': true});
|
||||||
function promiseValidator(response: {[key: string]: any}): AsyncValidatorFn {
|
|
||||||
return (c: AbstractControl) => {
|
|
||||||
const res = c.value != 'expected' ? response : null;
|
|
||||||
return Promise.resolve(res);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should return null when given null',
|
|
||||||
() => { expect(Validators.composeAsync(null !)).toBeNull(); });
|
|
||||||
|
|
||||||
it('should collect errors from all the validators', fakeAsync(() => {
|
|
||||||
const v = Validators.composeAsync(
|
|
||||||
[promiseValidator({'one': true}), promiseValidator({'two': true})]) !;
|
|
||||||
|
|
||||||
let errorMap: {[key: string]: any}|null = undefined !;
|
|
||||||
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((errors: {[key: string]: any} | null) => errorMap = errors);
|
|
||||||
tick();
|
|
||||||
|
|
||||||
expect(errorMap !).toEqual({'one': true, 'two': true});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => {
|
|
||||||
const v = Validators.composeAsync([normalizeAsyncValidator(
|
|
||||||
new AsyncValidatorDirective('expected', {'one': true}))]) !;
|
|
||||||
|
|
||||||
let errorMap: {[key: string]: any}|null = undefined !;
|
|
||||||
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((errors: {[key: string]: any} | null) => errorMap = errors);
|
|
||||||
tick();
|
|
||||||
|
|
||||||
expect(errorMap !).toEqual({'one': true});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should return null when no errors', fakeAsync(() => {
|
|
||||||
const v = Validators.composeAsync([promiseValidator({'one': true})]) !;
|
|
||||||
|
|
||||||
let errorMap: {[key: string]: any}|null = undefined !;
|
|
||||||
(v(new FormControl('expected')) as Observable<ValidationErrors|null>)
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((errors: {[key: string]: any} | null) => errorMap = errors);
|
|
||||||
tick();
|
|
||||||
|
|
||||||
expect(errorMap).toBeNull();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should ignore nulls', fakeAsync(() => {
|
|
||||||
const v = Validators.composeAsync([promiseValidator({'one': true}), null !]) !;
|
|
||||||
|
|
||||||
let errorMap: {[key: string]: any}|null = undefined !;
|
|
||||||
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((errors: {[key: string]: any} | null) => errorMap = errors);
|
|
||||||
tick();
|
|
||||||
|
|
||||||
expect(errorMap !).toEqual({'one': true});
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('observables', () => {
|
it('should wait for all validators before setting errors', fakeAsync(() => {
|
||||||
function observableValidator(response: {[key: string]: any}): AsyncValidatorFn {
|
function getTimerObs(time: number, errorMap: {[key: string]: any}): AsyncValidatorFn {
|
||||||
return (c: AbstractControl) => {
|
return (c: AbstractControl) => {
|
||||||
const res = c.value != 'expected' ? response : null;
|
return timer(time).pipe(map(() => errorMap));
|
||||||
return of (res);
|
};
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
|
||||||
it('should return null when given null',
|
const v = Validators.composeAsync(
|
||||||
() => { expect(Validators.composeAsync(null !)).toBeNull(); });
|
[getTimerObs(100, {one: true}), getTimerObs(200, {two: true})])!;
|
||||||
|
|
||||||
it('should collect errors from all the validators', () => {
|
let errorMap: {[key: string]: any}|null = undefined!;
|
||||||
const v = Validators.composeAsync(
|
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
||||||
[observableValidator({'one': true}), observableValidator({'two': true})]) !;
|
.pipe(first())
|
||||||
|
.subscribe((errors: {[key: string]: any}|null) => errorMap = errors);
|
||||||
|
|
||||||
let errorMap: {[key: string]: any}|null = undefined !;
|
tick(100);
|
||||||
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
expect(errorMap).not.toBeDefined(
|
||||||
.pipe(first())
|
`Expected errors not to be set until all validators came back.`);
|
||||||
.subscribe((errors: {[key: string]: any} | null) => errorMap = errors);
|
|
||||||
|
|
||||||
expect(errorMap !).toEqual({'one': true, 'two': true});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should normalize and evaluate async validator-directives correctly', () => {
|
|
||||||
const v = Validators.composeAsync(
|
|
||||||
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]) !;
|
|
||||||
|
|
||||||
let errorMap: {[key: string]: any}|null = undefined !;
|
|
||||||
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((errors: {[key: string]: any} | null) => errorMap = errors) !;
|
|
||||||
|
|
||||||
expect(errorMap !).toEqual({'one': true});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null when no errors', () => {
|
|
||||||
const v = Validators.composeAsync([observableValidator({'one': true})]) !;
|
|
||||||
|
|
||||||
let errorMap: {[key: string]: any}|null = undefined !;
|
|
||||||
(v(new FormControl('expected')) as Observable<ValidationErrors|null>)
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((errors: {[key: string]: any} | null) => errorMap = errors);
|
|
||||||
|
|
||||||
expect(errorMap).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should ignore nulls', () => {
|
|
||||||
const v = Validators.composeAsync([observableValidator({'one': true}), null !]) !;
|
|
||||||
|
|
||||||
let errorMap: {[key: string]: any}|null = undefined !;
|
|
||||||
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((errors: {[key: string]: any} | null) => errorMap = errors);
|
|
||||||
|
|
||||||
expect(errorMap !).toEqual({'one': true});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should wait for all validators before setting errors', fakeAsync(() => {
|
|
||||||
function getTimerObs(time: number, errorMap: {[key: string]: any}): AsyncValidatorFn {
|
|
||||||
return (c: AbstractControl) => { return timer(time).pipe(map(() => errorMap)); };
|
|
||||||
}
|
|
||||||
|
|
||||||
const v = Validators.composeAsync(
|
|
||||||
[getTimerObs(100, {one: true}), getTimerObs(200, {two: true})]) !;
|
|
||||||
|
|
||||||
let errorMap: {[key: string]: any}|null = undefined !;
|
|
||||||
(v(new FormControl('invalid')) as Observable<ValidationErrors|null>)
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((errors: {[key: string]: any} | null) => errorMap = errors);
|
|
||||||
|
|
||||||
tick(100);
|
|
||||||
expect(errorMap).not.toBeDefined(
|
|
||||||
`Expected errors not to be set until all validators came back.`);
|
|
||||||
|
|
||||||
tick(100);
|
|
||||||
expect(errorMap !)
|
|
||||||
.toEqual(
|
|
||||||
{one: true, two: true},
|
|
||||||
`Expected errors to merge once all validators resolved.`);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
tick(100);
|
||||||
|
expect(errorMap!).toEqual(
|
||||||
|
{one: true, two: true}, `Expected errors to merge once all validators resolved.`);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -197,7 +198,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
|
|
||||||
it('should throw an error if compareWith is not a function', () => {
|
it('should throw an error if compareWith is not a function', () => {
|
||||||
const fixture = initTest(FormControlSelectWithCompareFn);
|
const fixture = initTest(FormControlSelectWithCompareFn);
|
||||||
fixture.componentInstance.compareFn = null !;
|
fixture.componentInstance.compareFn = null!;
|
||||||
expect(() => fixture.detectChanges())
|
expect(() => fixture.detectChanges())
|
||||||
.toThrowError(/compareWith must be a function, but received null/);
|
.toThrowError(/compareWith must be a function, but received null/);
|
||||||
});
|
});
|
||||||
|
@ -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', () => {
|
||||||
|
@ -336,7 +336,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
dispatchEvent(select.nativeElement, 'change');
|
dispatchEvent(select.nativeElement, 'change');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick();
|
tick();
|
||||||
expect(comp.selectedCity !['name']).toEqual('NYC');
|
expect(comp.selectedCity!['name']).toEqual('NYC');
|
||||||
|
|
||||||
select.nativeElement.value = '0: null';
|
select.nativeElement.value = '0: null';
|
||||||
dispatchEvent(select.nativeElement, 'change');
|
dispatchEvent(select.nativeElement, 'change');
|
||||||
|
@ -348,7 +348,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
it('should throw an error when compareWith is not a function', () => {
|
it('should throw an error when compareWith is not a function', () => {
|
||||||
const fixture = initTest(NgModelSelectWithCustomCompareFnForm);
|
const fixture = initTest(NgModelSelectWithCustomCompareFnForm);
|
||||||
const comp = fixture.componentInstance;
|
const comp = fixture.componentInstance;
|
||||||
comp.compareFn = null !;
|
comp.compareFn = null!;
|
||||||
expect(() => fixture.detectChanges())
|
expect(() => fixture.detectChanges())
|
||||||
.toThrowError(/compareWith must be a function, but received null/);
|
.toThrowError(/compareWith must be a function, but received null/);
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
@ -432,7 +427,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
|
|
||||||
it('should throw an error when compareWith is not a function', () => {
|
it('should throw an error when compareWith is not a function', () => {
|
||||||
const fixture = initTest(FormControlSelectMultipleWithCompareFn);
|
const fixture = initTest(FormControlSelectMultipleWithCompareFn);
|
||||||
fixture.componentInstance.compareFn = null !;
|
fixture.componentInstance.compareFn = null!;
|
||||||
expect(() => fixture.detectChanges())
|
expect(() => fixture.detectChanges())
|
||||||
.toThrowError(/compareWith must be a function, but received null/);
|
.toThrowError(/compareWith must be a function, but received null/);
|
||||||
});
|
});
|
||||||
|
@ -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', () => {
|
||||||
|
@ -520,7 +514,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
it('should throw an error when compareWith is not a function', () => {
|
it('should throw an error when compareWith is not a function', () => {
|
||||||
const fixture = initTest(NgModelSelectMultipleWithCustomCompareFnForm);
|
const fixture = initTest(NgModelSelectMultipleWithCustomCompareFnForm);
|
||||||
const comp = fixture.componentInstance;
|
const comp = fixture.componentInstance;
|
||||||
comp.compareFn = null !;
|
comp.compareFn = null!;
|
||||||
expect(() => fixture.detectChanges())
|
expect(() => fixture.detectChanges())
|
||||||
.toThrowError(/compareWith must be a function, but received null/);
|
.toThrowError(/compareWith must be a function, but received null/);
|
||||||
});
|
});
|
||||||
|
@ -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 =
|
||||||
|
@ -562,10 +553,10 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
// view -> model
|
// view -> model
|
||||||
expect(form.get('food') !.value).toEqual('chicken');
|
expect(form.get('food')!.value).toEqual('chicken');
|
||||||
expect(inputs[1].nativeElement.checked).toEqual(false);
|
expect(inputs[1].nativeElement.checked).toEqual(false);
|
||||||
|
|
||||||
form.get('food') !.setValue('fish');
|
form.get('food')!.setValue('fish');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
// programmatic change -> view
|
// programmatic change -> view
|
||||||
|
@ -606,16 +597,16 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
fixture.componentInstance.form = form;
|
fixture.componentInstance.form = form;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
form.get('food') !.setValue(null);
|
form.get('food')!.setValue(null);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
||||||
expect(inputs[0].nativeElement.checked).toEqual(false);
|
expect(inputs[0].nativeElement.checked).toEqual(false);
|
||||||
|
|
||||||
form.get('food') !.setValue('chicken');
|
form.get('food')!.setValue('chicken');
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
form.get('food') !.setValue(undefined);
|
form.get('food')!.setValue(undefined);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(inputs[0].nativeElement.checked).toEqual(false);
|
expect(inputs[0].nativeElement.checked).toEqual(false);
|
||||||
});
|
});
|
||||||
|
@ -706,13 +697,12 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
// view -> model
|
// view -> model
|
||||||
expect(form.get('food') !.value).toEqual('chicken');
|
expect(form.get('food')!.value).toEqual('chicken');
|
||||||
expect(form.get('nested.food') !.value).toEqual('fish');
|
expect(form.get('nested.food')!.value).toEqual('fish');
|
||||||
|
|
||||||
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', () => {
|
||||||
|
@ -728,7 +718,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
expect(inputs[2].nativeElement.disabled).toEqual(false);
|
expect(inputs[2].nativeElement.disabled).toEqual(false);
|
||||||
expect(inputs[3].nativeElement.disabled).toEqual(false);
|
expect(inputs[3].nativeElement.disabled).toEqual(false);
|
||||||
|
|
||||||
form.get('food') !.disable();
|
form.get('food')!.disable();
|
||||||
expect(inputs[0].nativeElement.disabled).toEqual(true);
|
expect(inputs[0].nativeElement.disabled).toEqual(true);
|
||||||
expect(inputs[1].nativeElement.disabled).toEqual(true);
|
expect(inputs[1].nativeElement.disabled).toEqual(true);
|
||||||
expect(inputs[2].nativeElement.disabled).toEqual(false);
|
expect(inputs[2].nativeElement.disabled).toEqual(false);
|
||||||
|
@ -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', () => {
|
||||||
|
@ -860,7 +849,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
fixture.componentInstance.food = null !;
|
fixture.componentInstance.food = null!;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
|
@ -872,7 +861,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
fixture.componentInstance.food = undefined !;
|
fixture.componentInstance.food = undefined!;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick();
|
tick();
|
||||||
expect(inputs[0].nativeElement.checked).toEqual(false);
|
expect(inputs[0].nativeElement.checked).toEqual(false);
|
||||||
|
@ -886,7 +875,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
const form = fixture.debugElement.children[0].injector.get(NgForm);
|
||||||
form.control.get('food') !.disable();
|
form.control.get('food')!.disable();
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
||||||
|
@ -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', () => {
|
||||||
|
@ -987,15 +971,12 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
tick();
|
tick();
|
||||||
// view -> model
|
// view -> model
|
||||||
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);
|
||||||
|
@ -1014,9 +995,9 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
expect(form.value).toEqual({'login': 'bb'});
|
expect(form.value).toEqual({'login': 'bb'});
|
||||||
|
|
||||||
// custom validator
|
// custom validator
|
||||||
expect(form.get('login') !.errors).toEqual({'err': true});
|
expect(form.get('login')!.errors).toEqual({'err': true});
|
||||||
form.setValue({login: 'expected'});
|
form.setValue({login: 'expected'});
|
||||||
expect(form.get('login') !.errors).toEqual(null);
|
expect(form.get('login')!.errors).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support non builtin input elements that fire a change event without a \'target\' property',
|
it('should support non builtin input elements that fire a change event without a \'target\' property',
|
||||||
|
@ -1042,7 +1023,7 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
});
|
});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(fixture.componentInstance.form.status).toEqual('DISABLED');
|
expect(fixture.componentInstance.form.status).toEqual('DISABLED');
|
||||||
expect(fixture.componentInstance.form.get('login') !.status).toEqual('DISABLED');
|
expect(fixture.componentInstance.form.get('login')!.status).toEqual('DISABLED');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support custom accessors without setDisabledState - formControlDirective',
|
it('should support custom accessors without setDisabledState - formControlDirective',
|
||||||
|
@ -1061,9 +1042,9 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
fixture.componentInstance.form = new FormGroup({'login': new FormControl('aa')});
|
fixture.componentInstance.form = new FormGroup({'login': new FormControl('aa')});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(fixture.componentInstance.myInput !.control).toBeDefined();
|
expect(fixture.componentInstance.myInput!.control).toBeDefined();
|
||||||
expect(fixture.componentInstance.myInput !.control)
|
expect(fixture.componentInstance.myInput!.control)
|
||||||
.toEqual(fixture.componentInstance.myInput !.controlDir.control);
|
.toEqual(fixture.componentInstance.myInput!.controlDir.control);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1089,16 +1070,14 @@ import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util'
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'form-control-comp', template: `<input type="text" [formControl]="control">`})
|
@Component({selector: 'form-control-comp', template: `<input type="text" [formControl]="control">`})
|
||||||
export class FormControlComp {
|
export class FormControlComp {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
control !: FormControl;
|
control!: FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1110,13 +1089,13 @@ export class FormControlComp {
|
||||||
})
|
})
|
||||||
export class FormGroupComp {
|
export class FormGroupComp {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
control !: FormControl;
|
control!: FormControl;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
myGroup !: FormGroup;
|
myGroup!: FormGroup;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
event !: Event;
|
event!: Event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1125,7 +1104,7 @@ export class FormGroupComp {
|
||||||
})
|
})
|
||||||
class FormControlNumberInput {
|
class FormControlNumberInput {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
control !: FormControl;
|
control!: FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1167,7 +1146,7 @@ class FormControlSelectNgValue {
|
||||||
})
|
})
|
||||||
class FormControlSelectWithCompareFn {
|
class FormControlSelectWithCompareFn {
|
||||||
compareFn:
|
compareFn:
|
||||||
(o1: any, o2: any) => boolean = (o1: any, o2: any) => o1 && o2? o1.id === o2.id: o1 === o2
|
(o1: any, o2: any) => boolean = (o1: any, o2: any) => o1 && o2 ? o1.id === o2.id : o1 === o2
|
||||||
cities = [{id: 1, name: 'SF'}, {id: 2, name: 'NY'}];
|
cities = [{id: 1, name: 'SF'}, {id: 2, name: 'NY'}];
|
||||||
form = new FormGroup({city: new FormControl({id: 1, name: 'SF'})});
|
form = new FormGroup({city: new FormControl({id: 1, name: 'SF'})});
|
||||||
}
|
}
|
||||||
|
@ -1211,7 +1190,7 @@ class FormControlSelectMultipleNgValue {
|
||||||
})
|
})
|
||||||
class FormControlSelectMultipleWithCompareFn {
|
class FormControlSelectMultipleWithCompareFn {
|
||||||
compareFn:
|
compareFn:
|
||||||
(o1: any, o2: any) => boolean = (o1: any, o2: any) => o1 && o2? o1.id === o2.id: o1 === o2
|
(o1: any, o2: any) => boolean = (o1: any, o2: any) => o1 && o2 ? o1.id === o2.id : o1 === o2
|
||||||
cities = [{id: 1, name: 'SF'}, {id: 2, name: 'NY'}];
|
cities = [{id: 1, name: 'SF'}, {id: 2, name: 'NY'}];
|
||||||
form = new FormGroup({city: new FormControl([{id: 1, name: 'SF'}])});
|
form = new FormGroup({city: new FormControl([{id: 1, name: 'SF'}])});
|
||||||
}
|
}
|
||||||
|
@ -1254,7 +1233,7 @@ class NgModelSelectWithNullForm {
|
||||||
})
|
})
|
||||||
class NgModelSelectWithCustomCompareFnForm {
|
class NgModelSelectWithCustomCompareFnForm {
|
||||||
compareFn:
|
compareFn:
|
||||||
(o1: any, o2: any) => boolean = (o1: any, o2: any) => o1 && o2? o1.id === o2.id: o1 === o2
|
(o1: any, o2: any) => boolean = (o1: any, o2: any) => o1 && o2 ? o1.id === o2.id : o1 === o2
|
||||||
selectedCity: any = {};
|
selectedCity: any = {};
|
||||||
cities: any[] = [];
|
cities: any[] = [];
|
||||||
}
|
}
|
||||||
|
@ -1270,7 +1249,7 @@ class NgModelSelectWithCustomCompareFnForm {
|
||||||
})
|
})
|
||||||
class NgModelSelectMultipleWithCustomCompareFnForm {
|
class NgModelSelectMultipleWithCustomCompareFnForm {
|
||||||
compareFn:
|
compareFn:
|
||||||
(o1: any, o2: any) => boolean = (o1: any, o2: any) => o1 && o2? o1.id === o2.id: o1 === o2
|
(o1: any, o2: any) => boolean = (o1: any, o2: any) => o1 && o2 ? o1.id === o2.id : o1 === o2
|
||||||
selectedCities: any[] = [];
|
selectedCities: any[] = [];
|
||||||
cities: any[] = [];
|
cities: any[] = [];
|
||||||
}
|
}
|
||||||
|
@ -1285,7 +1264,7 @@ class NgModelSelectMultipleWithCustomCompareFnForm {
|
||||||
})
|
})
|
||||||
class NgModelSelectMultipleForm {
|
class NgModelSelectMultipleForm {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
selectedCities !: any[];
|
selectedCities!: any[];
|
||||||
cities: any[] = [];
|
cities: any[] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1295,7 +1274,7 @@ class NgModelSelectMultipleForm {
|
||||||
})
|
})
|
||||||
class FormControlRangeInput {
|
class FormControlRangeInput {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
control !: FormControl;
|
control!: FormControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'ng-model-range-form', template: '<input type="range" [(ngModel)]="val">'})
|
@Component({selector: 'ng-model-range-form', template: '<input type="range" [(ngModel)]="val">'})
|
||||||
|
@ -1317,7 +1296,7 @@ class NgModelRangeForm {
|
||||||
})
|
})
|
||||||
export class FormControlRadioButtons {
|
export class FormControlRadioButtons {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
showRadio = new FormControl('yes');
|
showRadio = new FormControl('yes');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1335,9 +1314,9 @@ export class FormControlRadioButtons {
|
||||||
})
|
})
|
||||||
class NgModelRadioForm {
|
class NgModelRadioForm {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
food !: string;
|
food!: string;
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
drink !: string;
|
drink!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
|
@ -1351,37 +1330,55 @@ class NgModelRadioForm {
|
||||||
class WrappedValue implements ControlValueAccessor {
|
class WrappedValue implements ControlValueAccessor {
|
||||||
value: any;
|
value: any;
|
||||||
// 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: ''})
|
||||||
export class MyInput implements ControlValueAccessor {
|
export class MyInput implements ControlValueAccessor {
|
||||||
@Output('input') onInput = new EventEmitter();
|
@Output('input') onInput = new EventEmitter();
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
value !: string;
|
value!: string;
|
||||||
|
|
||||||
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({
|
||||||
|
@ -1393,7 +1390,7 @@ export class MyInput implements ControlValueAccessor {
|
||||||
})
|
})
|
||||||
export class MyInputForm {
|
export class MyInputForm {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
@ViewChild(MyInput) myInput: MyInput|null = null;
|
@ViewChild(MyInput) myInput: MyInput|null = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1406,7 +1403,7 @@ export class MyInputForm {
|
||||||
})
|
})
|
||||||
class WrappedValueForm {
|
class WrappedValueForm {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
form !: FormGroup;
|
form!: FormGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -1418,18 +1415,24 @@ class WrappedValueForm {
|
||||||
})
|
})
|
||||||
export class NgModelCustomComp implements ControlValueAccessor {
|
export class NgModelCustomComp implements ControlValueAccessor {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
model !: string;
|
model!: string;
|
||||||
@Input('disabled') isDisabled: boolean = false;
|
@Input('disabled') isDisabled: boolean = false;
|
||||||
// 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({
|
||||||
|
@ -1442,6 +1445,6 @@ export class NgModelCustomComp implements ControlValueAccessor {
|
||||||
})
|
})
|
||||||
export class NgModelCustomWrapper {
|
export class NgModelCustomWrapper {
|
||||||
// TODO(issue/24571): remove '!'.
|
// TODO(issue/24571): remove '!'.
|
||||||
name !: string;
|
name!: string;
|
||||||
isDisabled = false;
|
isDisabled = false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue