2016-06-23 12:47:54 -04:00
/ * *
* @license
* Copyright Google Inc . All Rights Reserved .
*
* Use of this source code is governed by an MIT - style license that can be
* found in the LICENSE file at https : //angular.io/license
* /
2017-02-17 15:55:55 -05:00
import { ɵ isObservable as isObservable , ɵ isPromise as isPromise } from '@angular/core' ;
2016-08-30 15:13:38 -04:00
import { fromPromise } from 'rxjs/observable/fromPromise' ;
2016-08-02 18:53:34 -04:00
2016-06-13 14:27:04 -04:00
import { composeAsyncValidators , composeValidators } from './directives/shared' ;
2016-06-08 19:38:52 -04:00
import { AsyncValidatorFn , ValidatorFn } from './directives/validators' ;
2016-08-02 18:53:34 -04:00
import { EventEmitter , Observable } from './facade/async' ;
2017-02-01 18:41:14 -05:00
2016-06-13 14:27:04 -04:00
2016-07-08 16:04:25 -04:00
2016-06-08 18:36:24 -04:00
/ * *
2016-06-10 14:15:59 -04:00
* Indicates that a FormControl is valid , i . e . that no errors exist in the input value .
2016-06-08 18:36:24 -04:00
* /
2016-06-08 19:38:52 -04:00
export const VALID = 'VALID' ;
2016-06-08 18:36:24 -04:00
/ * *
2016-06-10 14:15:59 -04:00
* Indicates that a FormControl is invalid , i . e . that an error exists in the input value .
2016-06-08 18:36:24 -04:00
* /
2016-06-08 19:38:52 -04:00
export const INVALID = 'INVALID' ;
2016-06-08 18:36:24 -04:00
/ * *
2016-06-10 14:15:59 -04:00
* Indicates that a FormControl is pending , i . e . that async validation is occurring and
2016-06-08 18:36:24 -04:00
* errors are not yet available for the input value .
* /
2016-06-08 19:38:52 -04:00
export const PENDING = 'PENDING' ;
2016-06-08 18:36:24 -04:00
2016-08-24 19:58:43 -04:00
/ * *
* Indicates that a FormControl is disabled , i . e . that the control is exempt from ancestor
* calculations of validity or value .
* /
export const DISABLED = 'DISABLED' ;
2016-08-01 17:22:50 -04:00
function _find ( control : AbstractControl , path : Array < string | number > | string , delimiter : string ) {
2016-10-21 18:14:44 -04:00
if ( path == null ) return null ;
2016-06-08 18:36:24 -04:00
if ( ! ( path instanceof Array ) ) {
2016-08-01 17:22:50 -04:00
path = ( < string > path ) . split ( delimiter ) ;
2016-06-08 18:36:24 -04:00
}
2016-10-19 12:55:50 -04:00
if ( path instanceof Array && ( path . length === 0 ) ) return null ;
2016-06-08 18:36:24 -04:00
2016-06-08 19:38:52 -04:00
return ( < Array < string | number > > path ) . reduce ( ( v , name ) = > {
2016-06-10 14:15:59 -04:00
if ( v instanceof FormGroup ) {
2016-10-07 21:11:37 -04:00
return v . controls [ name ] || null ;
2016-06-08 19:38:52 -04:00
}
2016-10-07 21:11:37 -04:00
if ( v instanceof FormArray ) {
return v . at ( < number > name ) || null ;
}
return null ;
2016-06-08 19:38:52 -04:00
} , control ) ;
2016-06-08 18:36:24 -04:00
}
function toObservable ( r : any ) : Observable < any > {
2016-08-30 15:13:38 -04:00
return isPromise ( r ) ? fromPromise ( r ) : r ;
2016-06-08 18:36:24 -04:00
}
2016-06-13 14:27:04 -04:00
function coerceToValidator ( validator : ValidatorFn | ValidatorFn [ ] ) : ValidatorFn {
return Array . isArray ( validator ) ? composeValidators ( validator ) : validator ;
}
function coerceToAsyncValidator ( asyncValidator : AsyncValidatorFn | AsyncValidatorFn [ ] ) :
AsyncValidatorFn {
return Array . isArray ( asyncValidator ) ? composeAsyncValidators ( asyncValidator ) : asyncValidator ;
}
2016-06-08 18:36:24 -04:00
/ * *
2016-09-12 23:31:13 -04:00
* @whatItDoes This is the base class for { @link FormControl } , { @link FormGroup } , and
* { @link FormArray } .
*
* It provides some of the shared behavior that all controls and groups of controls have , like
* running validators , calculating status , and resetting state . It also defines the properties
* that are shared between all sub - classes , like ` value ` , ` valid ` , and ` dirty ` . It shouldn ' t be
* instantiated directly .
*
2016-08-17 10:44:39 -04:00
* @stable
2016-06-08 18:36:24 -04:00
* /
export abstract class AbstractControl {
/** @internal */
_value : any ;
2016-09-02 18:57:35 -04:00
/** @internal */
_onCollectionChange = ( ) = > { } ;
2016-06-08 18:36:24 -04:00
private _valueChanges : EventEmitter < any > ;
private _statusChanges : EventEmitter < any > ;
private _status : string ;
private _errors : { [ key : string ] : any } ;
private _pristine : boolean = true ;
private _touched : boolean = false ;
2016-06-10 14:15:59 -04:00
private _parent : FormGroup | FormArray ;
2016-06-08 18:36:24 -04:00
private _asyncValidationSubscription : any ;
constructor ( public validator : ValidatorFn , public asyncValidator : AsyncValidatorFn ) { }
2016-09-12 23:31:13 -04:00
/ * *
* The value of the control .
* /
2016-06-08 18:36:24 -04:00
get value ( ) : any { return this . _value ; }
2016-10-19 12:55:50 -04:00
/ * *
* The parent control .
* /
get parent ( ) : FormGroup | FormArray { return this . _parent ; }
2016-09-12 23:31:13 -04:00
/ * *
* The validation status of the control . There are four possible
* validation statuses :
*
* * * * VALID * * : control has passed all validation checks
* * * * INVALID * * : control has failed at least one validation check
* * * * PENDING * * : control is in the midst of conducting a validation check
* * * * DISABLED * * : control is exempt from validation checks
*
* These statuses are mutually exclusive , so a control cannot be
* both valid AND invalid or invalid AND disabled .
* /
2016-06-08 18:36:24 -04:00
get status ( ) : string { return this . _status ; }
2016-09-12 23:31:13 -04:00
/ * *
* A control is ` valid ` when its ` status === VALID ` .
*
* In order to have this status , the control must have passed all its
* validation checks .
* /
2016-06-08 18:36:24 -04:00
get valid ( ) : boolean { return this . _status === VALID ; }
2016-09-12 23:31:13 -04:00
/ * *
* A control is ` invalid ` when its ` status === INVALID ` .
*
* In order to have this status , the control must have failed
* at least one of its validation checks .
* /
2016-08-01 21:41:25 -04:00
get invalid ( ) : boolean { return this . _status === INVALID ; }
2016-06-08 18:36:24 -04:00
/ * *
2016-09-12 23:31:13 -04:00
* A control is ` pending ` when its ` status === PENDING ` .
*
* In order to have this status , the control must be in the
* middle of conducting a validation check .
* /
get pending ( ) : boolean { return this . _status == PENDING ; }
/ * *
* A control is ` disabled ` when its ` status === DISABLED ` .
*
* Disabled controls are exempt from validation checks and
* are not included in the aggregate value of their ancestor
* controls .
* /
get disabled ( ) : boolean { return this . _status === DISABLED ; }
/ * *
* A control is ` enabled ` as long as its ` status !== DISABLED ` .
*
* In other words , it has a status of ` VALID ` , ` INVALID ` , or
* ` PENDING ` .
* /
get enabled ( ) : boolean { return this . _status !== DISABLED ; }
/ * *
* Returns any errors generated by failing validation . If there
* are no errors , it will return null .
2016-06-08 18:36:24 -04:00
* /
get errors ( ) : { [ key : string ] : any } { return this . _errors ; }
2016-09-12 23:31:13 -04:00
/ * *
* A control is ` pristine ` if the user has not yet changed
* the value in the UI .
*
* Note that programmatic changes to a control ' s value will
* * not * mark it dirty .
* /
2016-06-08 18:36:24 -04:00
get pristine ( ) : boolean { return this . _pristine ; }
2016-09-12 23:31:13 -04:00
/ * *
* A control is ` dirty ` if the user has changed the value
* in the UI .
*
* Note that programmatic changes to a control ' s value will
* * not * mark it dirty .
* /
2016-06-08 18:36:24 -04:00
get dirty ( ) : boolean { return ! this . pristine ; }
2016-09-12 23:31:13 -04:00
/ * *
* A control is marked ` touched ` once the user has triggered
* a ` blur ` event on it .
* /
2016-06-08 18:36:24 -04:00
get touched ( ) : boolean { return this . _touched ; }
2016-09-12 23:31:13 -04:00
/ * *
* A control is ` untouched ` if the user has not yet triggered
* a ` blur ` event on it .
* /
2016-06-08 18:36:24 -04:00
get untouched ( ) : boolean { return ! this . _touched ; }
2016-09-12 23:31:13 -04:00
/ * *
* Emits an event every time the value of the control changes , in
* the UI or programmatically .
* /
2016-06-08 18:36:24 -04:00
get valueChanges ( ) : Observable < any > { return this . _valueChanges ; }
2016-09-12 23:31:13 -04:00
/ * *
* Emits an event every time the validation status of the control
* is re - calculated .
* /
2016-06-08 18:36:24 -04:00
get statusChanges ( ) : Observable < any > { return this . _statusChanges ; }
2016-09-12 23:31:13 -04:00
/ * *
* Sets the synchronous validators that are active on this control . Calling
* this will overwrite any existing sync validators .
* /
setValidators ( newValidator : ValidatorFn | ValidatorFn [ ] ) : void {
this . validator = coerceToValidator ( newValidator ) ;
}
2016-08-24 19:58:43 -04:00
2016-09-12 23:31:13 -04:00
/ * *
* Sets the async validators that are active on this control . Calling this
* will overwrite any existing async validators .
* /
2016-06-23 11:18:07 -04:00
setAsyncValidators ( newValidator : AsyncValidatorFn | AsyncValidatorFn [ ] ) : void {
this . asyncValidator = coerceToAsyncValidator ( newValidator ) ;
}
2016-09-12 23:31:13 -04:00
/ * *
* Empties out the sync validator list .
* /
2016-06-23 11:18:07 -04:00
clearValidators ( ) : void { this . validator = null ; }
2016-09-12 23:31:13 -04:00
/ * *
* Empties out the async validator list .
* /
clearAsyncValidators ( ) : void { this . asyncValidator = null ; }
/ * *
* Marks the control as ` touched ` .
*
* This will also mark all direct ancestors as ` touched ` to maintain
* the model .
* /
2016-07-01 18:36:04 -04:00
markAsTouched ( { onlySelf } : { onlySelf? : boolean } = { } ) : void {
this . _touched = true ;
2016-10-21 18:14:44 -04:00
if ( this . _parent && ! onlySelf ) {
2016-10-19 12:54:54 -04:00
this . _parent . markAsTouched ( { onlySelf } ) ;
2016-07-01 18:36:04 -04:00
}
}
2016-06-08 18:36:24 -04:00
2016-09-12 23:31:13 -04:00
/ * *
* Marks the control as ` untouched ` .
*
* If the control has any children , it will also mark all children as ` untouched `
* to maintain the model , and re - calculate the ` touched ` status of all parent
* controls .
* /
markAsUntouched ( { onlySelf } : { onlySelf? : boolean } = { } ) : void {
this . _touched = false ;
this . _forEachChild (
( control : AbstractControl ) = > { control . markAsUntouched ( { onlySelf : true } ) ; } ) ;
2016-10-21 18:14:44 -04:00
if ( this . _parent && ! onlySelf ) {
2016-10-19 12:54:54 -04:00
this . _parent . _updateTouched ( { onlySelf } ) ;
2016-09-12 23:31:13 -04:00
}
}
/ * *
* Marks the control as ` dirty ` .
*
* This will also mark all direct ancestors as ` dirty ` to maintain
* the model .
* /
2016-06-08 18:36:24 -04:00
markAsDirty ( { onlySelf } : { onlySelf? : boolean } = { } ) : void {
this . _pristine = false ;
2016-10-21 18:14:44 -04:00
if ( this . _parent && ! onlySelf ) {
2016-10-19 12:54:54 -04:00
this . _parent . markAsDirty ( { onlySelf } ) ;
2016-06-08 18:36:24 -04:00
}
}
2016-09-12 23:31:13 -04:00
/ * *
* Marks the control as ` pristine ` .
*
* If the control has any children , it will also mark all children as ` pristine `
* to maintain the model , and re - calculate the ` pristine ` status of all parent
* controls .
* /
2016-07-12 18:02:25 -04:00
markAsPristine ( { onlySelf } : { onlySelf? : boolean } = { } ) : void {
this . _pristine = true ;
this . _forEachChild ( ( control : AbstractControl ) = > { control . markAsPristine ( { onlySelf : true } ) ; } ) ;
2016-10-21 18:14:44 -04:00
if ( this . _parent && ! onlySelf ) {
2016-10-19 12:54:54 -04:00
this . _parent . _updatePristine ( { onlySelf } ) ;
2016-07-12 18:02:25 -04:00
}
}
2016-09-12 23:31:13 -04:00
/ * *
* Marks the control as ` pending ` .
* /
2016-06-08 18:36:24 -04:00
markAsPending ( { onlySelf } : { onlySelf? : boolean } = { } ) : void {
this . _status = PENDING ;
2016-10-21 18:14:44 -04:00
if ( this . _parent && ! onlySelf ) {
2016-10-19 12:54:54 -04:00
this . _parent . markAsPending ( { onlySelf } ) ;
2016-06-08 18:36:24 -04:00
}
}
2016-09-12 23:31:13 -04:00
/ * *
* Disables the control . This means the control will be exempt from validation checks and
* excluded from the aggregate value of any parent . Its status is ` DISABLED ` .
*
* If the control has children , all children will be disabled to maintain the model .
* /
2016-08-24 19:58:43 -04:00
disable ( { onlySelf , emitEvent } : { onlySelf? : boolean , emitEvent? : boolean } = { } ) : void {
this . _status = DISABLED ;
2016-09-09 15:00:38 -04:00
this . _errors = null ;
2016-08-24 19:58:43 -04:00
this . _forEachChild ( ( control : AbstractControl ) = > { control . disable ( { onlySelf : true } ) ; } ) ;
this . _updateValue ( ) ;
2016-10-21 18:14:44 -04:00
if ( emitEvent !== false ) {
2016-08-24 19:58:43 -04:00
this . _valueChanges . emit ( this . _value ) ;
this . _statusChanges . emit ( this . _status ) ;
}
this . _updateAncestors ( onlySelf ) ;
2016-09-20 12:08:12 -04:00
this . _onDisabledChange . forEach ( ( changeFn ) = > changeFn ( true ) ) ;
2016-08-24 19:58:43 -04:00
}
2016-09-12 23:31:13 -04:00
/ * *
* Enables the control . This means the control will be included in validation checks and
* the aggregate value of its parent . Its status is re - calculated based on its value and
* its validators .
*
* If the control has children , all children will be enabled .
* /
2016-08-24 19:58:43 -04:00
enable ( { onlySelf , emitEvent } : { onlySelf? : boolean , emitEvent? : boolean } = { } ) : void {
this . _status = VALID ;
this . _forEachChild ( ( control : AbstractControl ) = > { control . enable ( { onlySelf : true } ) ; } ) ;
2016-10-19 12:54:54 -04:00
this . updateValueAndValidity ( { onlySelf : true , emitEvent } ) ;
2016-08-24 19:58:43 -04:00
this . _updateAncestors ( onlySelf ) ;
2016-09-20 12:08:12 -04:00
this . _onDisabledChange . forEach ( ( changeFn ) = > changeFn ( false ) ) ;
2016-08-24 19:58:43 -04:00
}
private _updateAncestors ( onlySelf : boolean ) {
2016-10-21 18:14:44 -04:00
if ( this . _parent && ! onlySelf ) {
2016-08-24 19:58:43 -04:00
this . _parent . updateValueAndValidity ( ) ;
this . _parent . _updatePristine ( ) ;
this . _parent . _updateTouched ( ) ;
}
}
2016-06-10 14:15:59 -04:00
setParent ( parent : FormGroup | FormArray ) : void { this . _parent = parent ; }
2016-06-08 18:36:24 -04:00
2016-09-12 23:31:13 -04:00
/ * *
* Sets the value of the control . Abstract method ( implemented in sub - classes ) .
* /
2016-08-05 16:35:17 -04:00
abstract setValue ( value : any , options? : Object ) : void ;
2016-09-12 23:31:13 -04:00
/ * *
* Patches the value of the control . Abstract method ( implemented in sub - classes ) .
* /
2016-08-05 16:35:17 -04:00
abstract patchValue ( value : any , options? : Object ) : void ;
2016-07-08 16:04:25 -04:00
2016-09-12 23:31:13 -04:00
/ * *
* Resets the control . Abstract method ( implemented in sub - classes ) .
* /
2016-07-12 18:02:25 -04:00
abstract reset ( value? : any , options? : Object ) : void ;
2016-09-12 23:31:13 -04:00
/ * *
* Re - calculates the value and validation status of the control .
*
* By default , it will also update the value and validity of its ancestors .
* /
2016-06-08 19:38:52 -04:00
updateValueAndValidity ( { onlySelf , emitEvent } : { onlySelf? : boolean , emitEvent? : boolean } = { } ) :
void {
2016-09-09 15:00:38 -04:00
this . _setInitialStatus ( ) ;
2016-06-08 18:36:24 -04:00
this . _updateValue ( ) ;
2016-09-09 15:00:38 -04:00
if ( this . enabled ) {
2016-12-03 17:17:09 -05:00
this . _cancelExistingSubscription ( ) ;
2016-09-09 15:00:38 -04:00
this . _errors = this . _runValidator ( ) ;
this . _status = this . _calculateStatus ( ) ;
2016-06-08 18:36:24 -04:00
2016-09-09 15:00:38 -04:00
if ( this . _status === VALID || this . _status === PENDING ) {
this . _runAsyncValidator ( emitEvent ) ;
}
2016-08-24 19:58:43 -04:00
}
2016-10-21 18:14:44 -04:00
if ( emitEvent !== false ) {
2016-08-02 18:53:34 -04:00
this . _valueChanges . emit ( this . _value ) ;
this . _statusChanges . emit ( this . _status ) ;
2016-06-08 18:36:24 -04:00
}
2016-10-21 18:14:44 -04:00
if ( this . _parent && ! onlySelf ) {
2016-10-19 12:54:54 -04:00
this . _parent . updateValueAndValidity ( { onlySelf , emitEvent } ) ;
2016-06-08 18:36:24 -04:00
}
}
2016-08-29 14:33:49 -04:00
/** @internal */
_updateTreeValidity ( { emitEvent } : { emitEvent? : boolean } = { emitEvent : true } ) {
this . _forEachChild ( ( ctrl : AbstractControl ) = > ctrl . _updateTreeValidity ( { emitEvent } ) ) ;
this . updateValueAndValidity ( { onlySelf : true , emitEvent } ) ;
}
2016-09-09 15:00:38 -04:00
private _setInitialStatus() { this . _status = this . _allControlsDisabled ( ) ? DISABLED : VALID ; }
2016-06-08 18:36:24 -04:00
private _runValidator ( ) : { [ key : string ] : any } {
2016-10-21 18:14:44 -04:00
return this . validator ? this . validator ( this ) : null ;
2016-06-08 18:36:24 -04:00
}
private _runAsyncValidator ( emitEvent : boolean ) : void {
2016-10-21 18:14:44 -04:00
if ( this . asyncValidator ) {
2016-06-08 18:36:24 -04:00
this . _status = PENDING ;
2016-10-21 18:14:44 -04:00
const obs = toObservable ( this . asyncValidator ( this ) ) ;
2017-02-01 18:41:14 -05:00
if ( ! ( isObservable ( obs ) ) ) {
2017-01-22 03:37:22 -05:00
throw new Error (
` expected the following validator to return Promise or Observable: ${ this . asyncValidator } . If you are using FormBuilder; did you forget to brace your validators in an array? ` ) ;
}
2016-10-19 12:54:54 -04:00
this . _asyncValidationSubscription =
obs . subscribe ( { next : ( res : { [ key : string ] : any } ) = > this . setErrors ( res , { emitEvent } ) } ) ;
2016-06-08 18:36:24 -04:00
}
}
private _cancelExistingSubscription ( ) : void {
2016-10-21 18:14:44 -04:00
if ( this . _asyncValidationSubscription ) {
2016-08-02 18:53:34 -04:00
this . _asyncValidationSubscription . unsubscribe ( ) ;
2016-06-08 18:36:24 -04:00
}
}
/ * *
2016-06-10 14:15:59 -04:00
* Sets errors on a form control .
2016-06-08 18:36:24 -04:00
*
2016-09-12 23:31:13 -04:00
* This is used when validations are run manually by the user , rather than automatically .
2016-06-08 18:36:24 -04:00
*
* Calling ` setErrors ` will also update the validity of the parent control .
*
2016-09-12 23:31:13 -04:00
* # # # Example
2016-06-08 18:36:24 -04:00
*
* ` ` `
2016-09-12 23:31:13 -04:00
* const login = new FormControl ( "someLogin" ) ;
2016-06-08 18:36:24 -04:00
* login . setErrors ( {
* "notUnique" : true
* } ) ;
*
* expect ( login . valid ) . toEqual ( false ) ;
* expect ( login . errors ) . toEqual ( { "notUnique" : true } ) ;
*
2016-09-12 23:31:13 -04:00
* login . setValue ( "someOtherLogin" ) ;
2016-06-08 18:36:24 -04:00
*
* expect ( login . valid ) . toEqual ( true ) ;
* ` ` `
* /
setErrors ( errors : { [ key : string ] : any } , { emitEvent } : { emitEvent? : boolean } = { } ) : void {
this . _errors = errors ;
2016-10-21 18:14:44 -04:00
this . _updateControlsErrors ( emitEvent !== false ) ;
2016-06-08 18:36:24 -04:00
}
2016-09-12 23:31:13 -04:00
/ * *
* Retrieves a child control given the control ' s name or path .
*
* Paths can be passed in as an array or a string delimited by a dot .
*
* To get a control nested within a ` person ` sub - group :
*
* * ` this.form.get('person.name'); `
*
* - OR -
*
* * ` this.form.get(['person', 'name']); `
* /
2016-08-01 17:22:50 -04:00
get ( path : Array < string | number > | string ) : AbstractControl { return _find ( this , path , '.' ) ; }
2016-06-08 18:36:24 -04:00
2016-09-12 23:31:13 -04:00
/ * *
* Returns true if the control with the given path has the error specified . Otherwise
* returns null or undefined .
*
* If no path is given , it checks for the error on the present control .
* /
2016-06-08 18:36:24 -04:00
getError ( errorCode : string , path : string [ ] = null ) : any {
2016-10-21 18:14:44 -04:00
const control = path ? this . get ( path ) : this ;
return control && control . _errors ? control . _errors [ errorCode ] : null ;
2016-06-08 18:36:24 -04:00
}
2016-09-12 23:31:13 -04:00
/ * *
* Returns true if the control with the given path has the error specified . Otherwise
* returns false .
*
* If no path is given , it checks for the error on the present control .
* /
2016-06-08 18:36:24 -04:00
hasError ( errorCode : string , path : string [ ] = null ) : boolean {
2016-10-21 18:14:44 -04:00
return ! ! this . getError ( errorCode , path ) ;
2016-06-08 18:36:24 -04:00
}
2016-09-12 23:31:13 -04:00
/ * *
* Retrieves the top - level ancestor of this control .
* /
2016-06-08 18:36:24 -04:00
get root ( ) : AbstractControl {
let x : AbstractControl = this ;
2016-10-21 18:14:44 -04:00
while ( x . _parent ) {
2016-06-08 18:36:24 -04:00
x = x . _parent ;
}
return x ;
}
/** @internal */
2016-06-27 23:01:24 -04:00
_updateControlsErrors ( emitEvent : boolean ) : void {
2016-06-08 18:36:24 -04:00
this . _status = this . _calculateStatus ( ) ;
2016-06-27 23:01:24 -04:00
if ( emitEvent ) {
2016-08-02 18:53:34 -04:00
this . _statusChanges . emit ( this . _status ) ;
2016-06-27 23:01:24 -04:00
}
2016-10-21 18:14:44 -04:00
if ( this . _parent ) {
2016-06-27 23:01:24 -04:00
this . _parent . _updateControlsErrors ( emitEvent ) ;
2016-06-08 18:36:24 -04:00
}
}
/** @internal */
_initObservables() {
this . _valueChanges = new EventEmitter ( ) ;
this . _statusChanges = new EventEmitter ( ) ;
}
private _calculateStatus ( ) : string {
2016-09-01 19:51:42 -04:00
if ( this . _allControlsDisabled ( ) ) return DISABLED ;
2016-10-21 18:14:44 -04:00
if ( this . _errors ) return INVALID ;
2016-06-08 18:36:24 -04:00
if ( this . _anyControlsHaveStatus ( PENDING ) ) return PENDING ;
if ( this . _anyControlsHaveStatus ( INVALID ) ) return INVALID ;
return VALID ;
}
/** @internal */
abstract _updateValue ( ) : void ;
/** @internal */
2016-07-12 18:02:25 -04:00
abstract _forEachChild ( cb : Function ) : void ;
/** @internal */
abstract _anyControls ( condition : Function ) : boolean ;
2016-08-24 19:58:43 -04:00
/** @internal */
abstract _allControlsDisabled ( ) : boolean ;
2016-07-12 18:02:25 -04:00
/** @internal */
_anyControlsHaveStatus ( status : string ) : boolean {
2016-10-19 12:55:50 -04:00
return this . _anyControls ( ( control : AbstractControl ) = > control . status === status ) ;
2016-07-12 18:02:25 -04:00
}
/** @internal */
_anyControlsDirty ( ) : boolean {
return this . _anyControls ( ( control : AbstractControl ) = > control . dirty ) ;
}
/** @internal */
_anyControlsTouched ( ) : boolean {
return this . _anyControls ( ( control : AbstractControl ) = > control . touched ) ;
}
/** @internal */
_updatePristine ( { onlySelf } : { onlySelf? : boolean } = { } ) : void {
this . _pristine = ! this . _anyControlsDirty ( ) ;
2016-10-21 18:14:44 -04:00
if ( this . _parent && ! onlySelf ) {
2016-10-19 12:54:54 -04:00
this . _parent . _updatePristine ( { onlySelf } ) ;
2016-07-12 18:02:25 -04:00
}
}
/** @internal */
_updateTouched ( { onlySelf } : { onlySelf? : boolean } = { } ) : void {
this . _touched = this . _anyControlsTouched ( ) ;
2016-10-21 18:14:44 -04:00
if ( this . _parent && ! onlySelf ) {
2016-10-19 12:54:54 -04:00
this . _parent . _updateTouched ( { onlySelf } ) ;
2016-07-12 18:02:25 -04:00
}
}
2016-08-24 19:58:43 -04:00
/** @internal */
2016-09-20 12:08:12 -04:00
_onDisabledChange : Function [ ] = [ ] ;
2016-08-24 19:58:43 -04:00
/** @internal */
_isBoxedValue ( formState : any ) : boolean {
2016-10-19 16:42:39 -04:00
return typeof formState === 'object' && formState !== null &&
Object . keys ( formState ) . length === 2 && 'value' in formState && 'disabled' in formState ;
2016-08-24 19:58:43 -04:00
}
2016-09-02 18:57:35 -04:00
/** @internal */
_registerOnCollectionChange ( fn : ( ) = > void ) : void { this . _onCollectionChange = fn ; }
2016-06-08 18:36:24 -04:00
}
/ * *
2016-09-13 03:14:07 -04:00
* @whatItDoes Tracks the value and validation status of an individual form control .
2016-06-08 18:36:24 -04:00
*
2016-09-13 03:14:07 -04:00
* It is one of the three fundamental building blocks of Angular forms , along with
* { @link FormGroup } and { @link FormArray } .
2016-06-08 18:36:24 -04:00
*
2016-09-13 03:14:07 -04:00
* @howToUse
2016-06-08 18:36:24 -04:00
*
2016-09-13 03:14:07 -04:00
* When instantiating a { @link FormControl } , you can pass in an initial value as the
* first argument . Example :
*
* ` ` ` ts
* const ctrl = new FormControl ( 'some value' ) ;
* console . log ( ctrl . value ) ; // 'some value'
* ` ` `
*
* You can also initialize the control with a form state object on instantiation ,
* which includes both the value and whether or not the control is disabled .
2016-09-28 16:59:08 -04:00
* You can ' t use the value key without the disabled key ; both are required
* to use this way of initialization .
2016-09-13 03:14:07 -04:00
*
* ` ` ` ts
* const ctrl = new FormControl ( { value : 'n/a' , disabled : true } ) ;
* console . log ( ctrl . value ) ; // 'n/a'
* console . log ( ctrl . status ) ; // 'DISABLED'
* ` ` `
*
* To include a sync validator ( or an array of sync validators ) with the control ,
* pass it in as the second argument . Async validators are also supported , but
* have to be passed in separately as the third arg .
*
* ` ` ` ts
* const ctrl = new FormControl ( '' , Validators . required ) ;
* console . log ( ctrl . value ) ; // ''
* console . log ( ctrl . status ) ; // 'INVALID'
* ` ` `
*
* See its superclass , { @link AbstractControl } , for more properties and methods .
*
* * * * npm package * * : ` @angular/forms `
2016-06-08 18:36:24 -04:00
*
2016-08-17 10:44:39 -04:00
* @stable
2016-06-08 18:36:24 -04:00
* /
2016-06-10 14:15:59 -04:00
export class FormControl extends AbstractControl {
2016-06-08 18:36:24 -04:00
/** @internal */
2016-06-15 18:15:41 -04:00
_onChange : Function [ ] = [ ] ;
2016-06-08 18:36:24 -04:00
2016-06-08 19:38:52 -04:00
constructor (
2016-08-24 19:58:43 -04:00
formState : any = null , validator : ValidatorFn | ValidatorFn [ ] = null ,
2016-06-13 14:27:04 -04:00
asyncValidator : AsyncValidatorFn | AsyncValidatorFn [ ] = null ) {
super ( coerceToValidator ( validator ) , coerceToAsyncValidator ( asyncValidator ) ) ;
2016-08-24 19:58:43 -04:00
this . _applyFormState ( formState ) ;
2016-06-08 18:36:24 -04:00
this . updateValueAndValidity ( { onlySelf : true , emitEvent : false } ) ;
this . _initObservables ( ) ;
}
/ * *
2016-06-10 14:15:59 -04:00
* Set the value of the form control to ` value ` .
2016-06-08 18:36:24 -04:00
*
2016-06-10 14:15:59 -04:00
* If ` onlySelf ` is ` true ` , this change will only affect the validation of this ` FormControl `
2016-09-13 14:43:08 -04:00
* and not its parent component . This defaults to false .
*
* If ` emitEvent ` is ` true ` , this
2016-09-13 03:14:07 -04:00
* change will cause a ` valueChanges ` event on the ` FormControl ` to be emitted . This defaults
* to true ( as it falls through to ` updateValueAndValidity ` ) .
2016-06-08 18:36:24 -04:00
*
* If ` emitModelToViewChange ` is ` true ` , the view will be notified about the new value
* via an ` onChange ` event . This is the default behavior if ` emitModelToViewChange ` is not
* specified .
2016-07-12 18:02:25 -04:00
*
* If ` emitViewToModelChange ` is ` true ` , an ngModelChange event will be fired to update the
* model . This is the default behavior if ` emitViewToModelChange ` is not specified .
2016-06-08 18:36:24 -04:00
* /
2016-08-05 16:35:17 -04:00
setValue ( value : any , { onlySelf , emitEvent , emitModelToViewChange , emitViewToModelChange } : {
2016-06-08 18:36:24 -04:00
onlySelf? : boolean ,
emitEvent? : boolean ,
2016-07-12 18:02:25 -04:00
emitModelToViewChange? : boolean ,
emitViewToModelChange? : boolean
2016-06-08 18:36:24 -04:00
} = { } ) : void {
this . _value = value ;
2016-10-21 18:14:44 -04:00
if ( this . _onChange . length && emitModelToViewChange !== false ) {
this . _onChange . forEach ( ( changeFn ) = > changeFn ( this . _value , emitViewToModelChange !== false ) ) ;
2016-06-15 18:15:41 -04:00
}
2016-10-19 12:54:54 -04:00
this . updateValueAndValidity ( { onlySelf , emitEvent } ) ;
2016-06-08 18:36:24 -04:00
}
2016-08-05 16:35:17 -04:00
/ * *
2016-09-13 03:14:07 -04:00
* Patches the value of a control .
*
* This function is functionally the same as { @link FormControl . setValue } at this level .
* It exists for symmetry with { @link FormGroup . patchValue } on ` FormGroups ` and ` FormArrays ` ,
* where it does behave differently .
2016-08-05 16:35:17 -04:00
* /
patchValue ( value : any , options : {
onlySelf? : boolean ,
emitEvent? : boolean ,
emitModelToViewChange? : boolean ,
emitViewToModelChange? : boolean
} = { } ) : void {
this . setValue ( value , options ) ;
}
2016-09-13 03:14:07 -04:00
/ * *
* Resets the form control . This means by default :
*
* * it is marked as ` pristine `
* * it is marked as ` untouched `
* * value is set to null
*
* You can also reset to a specific form state by passing through a standalone
* value or a form state object that contains both a value and a disabled state
* ( these are the only two properties that cannot be calculated ) .
*
* Ex :
*
* ` ` ` ts
* this . control . reset ( 'Nancy' ) ;
*
* console . log ( this . control . value ) ; // 'Nancy'
* ` ` `
*
* OR
*
* ` ` `
* this . control . reset ( { value : 'Nancy' , disabled : true } ) ;
2016-09-13 14:43:08 -04:00
*
2016-09-13 03:14:07 -04:00
* console . log ( this . control . value ) ; // 'Nancy'
* console . log ( this . control . status ) ; // 'DISABLED'
* ` ` `
* /
2016-10-19 12:54:54 -04:00
reset ( formState : any = null , { onlySelf , emitEvent } : { onlySelf? : boolean ,
emitEvent? : boolean } = { } ) : void {
2016-08-24 19:58:43 -04:00
this . _applyFormState ( formState ) ;
this . markAsPristine ( { onlySelf } ) ;
this . markAsUntouched ( { onlySelf } ) ;
2016-10-19 12:54:54 -04:00
this . setValue ( this . _value , { onlySelf , emitEvent } ) ;
2016-07-12 18:02:25 -04:00
}
2016-06-08 18:36:24 -04:00
/ * *
* @internal
* /
_updateValue() { }
/ * *
* @internal
* /
2016-07-12 18:02:25 -04:00
_anyControls ( condition : Function ) : boolean { return false ; }
2016-06-08 18:36:24 -04:00
2016-08-24 19:58:43 -04:00
/ * *
* @internal
* /
_allControlsDisabled ( ) : boolean { return this . disabled ; }
2016-06-08 18:36:24 -04:00
/ * *
* Register a listener for change events .
* /
2016-06-15 18:15:41 -04:00
registerOnChange ( fn : Function ) : void { this . _onChange . push ( fn ) ; }
2016-07-12 18:02:25 -04:00
2016-08-25 17:37:57 -04:00
/ * *
* @internal
* /
_clearChangeFns ( ) : void {
this . _onChange = [ ] ;
2016-09-20 12:08:12 -04:00
this . _onDisabledChange = [ ] ;
2016-09-02 18:57:35 -04:00
this . _onCollectionChange = ( ) = > { } ;
2016-08-25 17:37:57 -04:00
}
2016-08-24 19:58:43 -04:00
/ * *
* Register a listener for disabled events .
* /
2016-09-20 12:08:12 -04:00
registerOnDisabledChange ( fn : ( isDisabled : boolean ) = > void ) : void {
this . _onDisabledChange . push ( fn ) ;
}
2016-08-24 19:58:43 -04:00
2016-07-12 18:02:25 -04:00
/ * *
* @internal
* /
_forEachChild ( cb : Function ) : void { }
2016-08-24 19:58:43 -04:00
private _applyFormState ( formState : any ) {
if ( this . _isBoxedValue ( formState ) ) {
this . _value = formState . value ;
formState . disabled ? this . disable ( { onlySelf : true , emitEvent : false } ) :
this . enable ( { onlySelf : true , emitEvent : false } ) ;
} else {
this . _value = formState ;
}
}
2016-06-08 18:36:24 -04:00
}
/ * *
2016-09-13 14:43:08 -04:00
* @whatItDoes Tracks the value and validity state of a group of { @link FormControl }
* instances .
2016-06-08 18:36:24 -04:00
*
2016-09-13 14:43:08 -04:00
* A ` FormGroup ` aggregates the values of each child { @link FormControl } into one object ,
* with each control name as the key . It calculates its status by reducing the statuses
* of its children . For example , if one of the controls in a group is invalid , the entire
* group becomes invalid .
2016-06-08 18:36:24 -04:00
*
2016-06-10 14:15:59 -04:00
* ` FormGroup ` is one of the three fundamental building blocks used to define forms in Angular ,
2016-09-13 14:43:08 -04:00
* along with { @link FormControl } and { @link FormArray } .
*
* @howToUse
*
* When instantiating a { @link FormGroup } , pass in a collection of child controls as the first
* argument . The key for each child will be the name under which it is registered .
*
* # # # Example
*
* ` ` `
* const form = new FormGroup ( {
* first : new FormControl ( 'Nancy' , Validators . minLength ( 2 ) ) ,
* last : new FormControl ( 'Drew' ) ,
* } ) ;
*
* console . log ( form . value ) ; // {first: 'Nancy', last; 'Drew'}
* console . log ( form . status ) ; // 'VALID'
* ` ` `
*
* You can also include group - level validators as the second arg , or group - level async
* validators as the third arg . These come in handy when you want to perform validation
* that considers the value of more than one child control .
*
* # # # Example
*
* ` ` `
* const form = new FormGroup ( {
* password : new FormControl ( '' , Validators . minLength ( 2 ) ) ,
* passwordConfirm : new FormControl ( '' , Validators . minLength ( 2 ) ) ,
* } , passwordMatchValidator ) ;
2016-06-08 18:36:24 -04:00
*
*
2016-09-13 14:43:08 -04:00
* function passwordMatchValidator ( g : FormGroup ) {
* return g . get ( 'password' ) . value === g . get ( 'passwordConfirm' ) . value
* ? null : { 'mismatch' : true } ;
* }
* ` ` `
*
* * * * npm package * * : ` @angular/forms `
*
2016-08-17 10:44:39 -04:00
* @stable
2016-06-08 18:36:24 -04:00
* /
2016-06-10 14:15:59 -04:00
export class FormGroup extends AbstractControl {
2016-06-08 19:38:52 -04:00
constructor (
2016-08-24 19:58:43 -04:00
public controls : { [ key : string ] : AbstractControl } , validator : ValidatorFn = null ,
2016-08-17 10:44:39 -04:00
asyncValidator : AsyncValidatorFn = null ) {
2016-06-08 18:36:24 -04:00
super ( validator , asyncValidator ) ;
this . _initObservables ( ) ;
2016-09-02 18:57:35 -04:00
this . _setUpControls ( ) ;
2016-06-08 18:36:24 -04:00
this . updateValueAndValidity ( { onlySelf : true , emitEvent : false } ) ;
}
/ * *
2016-09-13 14:43:08 -04:00
* Registers a control with the group ' s list of controls .
*
* This method does not update value or validity of the control , so for
* most cases you ' ll want to use { @link FormGroup . addControl } instead .
2016-06-08 18:36:24 -04:00
* /
2016-06-15 18:15:41 -04:00
registerControl ( name : string , control : AbstractControl ) : AbstractControl {
if ( this . controls [ name ] ) return this . controls [ name ] ;
2016-06-08 18:36:24 -04:00
this . controls [ name ] = control ;
control . setParent ( this ) ;
2016-09-02 18:57:35 -04:00
control . _registerOnCollectionChange ( this . _onCollectionChange ) ;
2016-06-15 18:15:41 -04:00
return control ;
2016-06-08 18:36:24 -04:00
}
/ * *
* Add a control to this group .
* /
addControl ( name : string , control : AbstractControl ) : void {
this . registerControl ( name , control ) ;
this . updateValueAndValidity ( ) ;
2016-09-02 18:57:35 -04:00
this . _onCollectionChange ( ) ;
2016-06-08 18:36:24 -04:00
}
/ * *
* Remove a control from this group .
* /
removeControl ( name : string ) : void {
2016-09-02 18:57:35 -04:00
if ( this . controls [ name ] ) this . controls [ name ] . _registerOnCollectionChange ( ( ) = > { } ) ;
2016-09-19 20:15:57 -04:00
delete ( this . controls [ name ] ) ;
2016-09-02 18:57:35 -04:00
this . updateValueAndValidity ( ) ;
this . _onCollectionChange ( ) ;
}
/ * *
* Replace an existing control .
* /
setControl ( name : string , control : AbstractControl ) : void {
if ( this . controls [ name ] ) this . controls [ name ] . _registerOnCollectionChange ( ( ) = > { } ) ;
2016-09-19 20:15:57 -04:00
delete ( this . controls [ name ] ) ;
2016-09-02 18:57:35 -04:00
if ( control ) this . registerControl ( name , control ) ;
2016-06-08 18:36:24 -04:00
this . updateValueAndValidity ( ) ;
2016-09-02 18:57:35 -04:00
this . _onCollectionChange ( ) ;
2016-06-08 18:36:24 -04:00
}
/ * *
2016-09-13 14:43:08 -04:00
* Check whether there is an enabled control with the given name in the group .
*
* It will return false for disabled controls . If you ' d like to check for
* existence in the group only , use { @link AbstractControl . get } instead .
2016-06-08 18:36:24 -04:00
* /
contains ( controlName : string ) : boolean {
2016-09-12 18:15:50 -04:00
return this . controls . hasOwnProperty ( controlName ) && this . controls [ controlName ] . enabled ;
2016-06-08 18:36:24 -04:00
}
2016-09-13 14:43:08 -04:00
/ * *
* Sets the value of the { @link FormGroup } . It accepts an object that matches
* the structure of the group , with control names as keys .
*
* This method performs strict checks , so it will throw an error if you try
* to set the value of a control that doesn ' t exist or if you exclude the
* value of a control .
*
* # # # Example
*
* ` ` `
* const form = new FormGroup ( {
* first : new FormControl ( ) ,
* last : new FormControl ( )
* } ) ;
* console . log ( form . value ) ; // {first: null, last: null}
*
* form . setValue ( { first : 'Nancy' , last : 'Drew' } ) ;
* console . log ( form . value ) ; // {first: 'Nancy', last: 'Drew'}
*
* ` ` `
* /
2016-10-19 12:54:54 -04:00
setValue (
value : { [ key : string ] : any } ,
{ onlySelf , emitEvent } : { onlySelf? : boolean , emitEvent? : boolean } = { } ) : void {
2016-08-05 16:35:17 -04:00
this . _checkAllValuesPresent ( value ) ;
2016-10-03 19:46:05 -04:00
Object . keys ( value ) . forEach ( name = > {
2016-07-08 16:04:25 -04:00
this . _throwIfControlMissing ( name ) ;
2016-10-19 12:54:54 -04:00
this . controls [ name ] . setValue ( value [ name ] , { onlySelf : true , emitEvent } ) ;
2016-08-05 16:35:17 -04:00
} ) ;
2016-10-19 12:54:54 -04:00
this . updateValueAndValidity ( { onlySelf , emitEvent } ) ;
2016-08-05 16:35:17 -04:00
}
2016-09-13 14:43:08 -04:00
/ * *
* Patches the value of the { @link FormGroup } . It accepts an object with control
* names as keys , and will do its best to match the values to the correct controls
* in the group .
*
* It accepts both super - sets and sub - sets of the group without throwing an error .
*
* # # # Example
*
* ` ` `
* const form = new FormGroup ( {
* first : new FormControl ( ) ,
* last : new FormControl ( )
* } ) ;
* console . log ( form . value ) ; // {first: null, last: null}
*
* form . patchValue ( { first : 'Nancy' } ) ;
* console . log ( form . value ) ; // {first: 'Nancy', last: null}
*
* ` ` `
* /
2016-10-19 12:54:54 -04:00
patchValue (
value : { [ key : string ] : any } ,
{ onlySelf , emitEvent } : { onlySelf? : boolean , emitEvent? : boolean } = { } ) : void {
2016-10-03 19:46:05 -04:00
Object . keys ( value ) . forEach ( name = > {
2016-08-05 16:35:17 -04:00
if ( this . controls [ name ] ) {
2016-10-19 12:54:54 -04:00
this . controls [ name ] . patchValue ( value [ name ] , { onlySelf : true , emitEvent } ) ;
2016-08-05 16:35:17 -04:00
}
2016-07-08 16:04:25 -04:00
} ) ;
2016-10-19 12:54:54 -04:00
this . updateValueAndValidity ( { onlySelf , emitEvent } ) ;
2016-07-08 16:04:25 -04:00
}
2016-09-13 14:43:08 -04:00
/ * *
* Resets the { @link FormGroup } . This means by default :
*
* * The group and all descendants are marked ` pristine `
* * The group and all descendants are marked ` untouched `
* * The value of all descendants will be null or null maps
*
* You can also reset to a specific form state by passing in a map of states
* that matches the structure of your form , with control names as keys . The state
* can be a standalone value or a form state object with both a value and a disabled
* status .
*
* # # # Example
*
* ` ` ` ts
2016-10-25 18:44:07 -04:00
* this . form . reset ( { first : 'name' , last : 'last name' } ) ;
2016-09-13 14:43:08 -04:00
*
* console . log ( this . form . value ) ; // {first: 'name', last: 'last name'}
* ` ` `
*
* - OR -
*
* ` ` `
* this . form . reset ( {
* first : { value : 'name' , disabled : true } ,
* last : 'last'
* } ) ;
*
* console . log ( this . form . value ) ; // {first: 'name', last: 'last name'}
* console . log ( this . form . get ( 'first' ) . status ) ; // 'DISABLED'
* ` ` `
* /
2016-10-19 12:54:54 -04:00
reset ( value : any = { } , { onlySelf , emitEvent } : { onlySelf? : boolean , emitEvent? : boolean } = { } ) :
void {
2016-07-12 18:02:25 -04:00
this . _forEachChild ( ( control : AbstractControl , name : string ) = > {
2016-10-19 12:54:54 -04:00
control . reset ( value [ name ] , { onlySelf : true , emitEvent } ) ;
2016-07-12 18:02:25 -04:00
} ) ;
2016-10-19 12:54:54 -04:00
this . updateValueAndValidity ( { onlySelf , emitEvent } ) ;
this . _updatePristine ( { onlySelf } ) ;
this . _updateTouched ( { onlySelf } ) ;
2016-07-12 18:02:25 -04:00
}
2016-09-13 14:43:08 -04:00
/ * *
2016-09-13 16:07:40 -04:00
* The aggregate value of the { @link FormGroup } , including any disabled controls .
2016-09-13 14:43:08 -04:00
*
* If you ' d like to include all values regardless of disabled status , use this method .
2016-09-13 16:07:40 -04:00
* Otherwise , the ` value ` property is the best way to get the value of the group .
2016-09-13 14:43:08 -04:00
* /
2016-11-08 18:44:36 -05:00
getRawValue ( ) : any {
2016-08-24 19:58:43 -04:00
return this . _reduceChildren (
{ } , ( acc : { [ k : string ] : AbstractControl } , control : AbstractControl , name : string ) = > {
2016-11-18 06:44:05 -05:00
acc [ name ] = control instanceof FormControl ? control . value : ( < any > control ) . getRawValue ( ) ;
2016-08-24 19:58:43 -04:00
return acc ;
} ) ;
}
2016-07-08 16:04:25 -04:00
/** @internal */
_throwIfControlMissing ( name : string ) : void {
2016-08-05 16:35:17 -04:00
if ( ! Object . keys ( this . controls ) . length ) {
2016-08-25 03:50:16 -04:00
throw new Error ( `
2016-08-05 16:35:17 -04:00
There are no form controls registered with this group yet . If you ' re using ngModel ,
you may want to check next tick ( e . g . use setTimeout ) .
` );
}
2016-07-08 16:04:25 -04:00
if ( ! this . controls [ name ] ) {
2016-08-25 03:50:16 -04:00
throw new Error ( ` Cannot find form control with name: ${ name } . ` ) ;
2016-07-08 16:04:25 -04:00
}
}
2016-07-12 18:02:25 -04:00
/** @internal */
2016-07-15 19:26:19 -04:00
_forEachChild ( cb : ( v : any , k : string ) = > void ) : void {
2016-10-03 19:46:05 -04:00
Object . keys ( this . controls ) . forEach ( k = > cb ( this . controls [ k ] , k ) ) ;
2016-07-15 19:26:19 -04:00
}
2016-07-12 18:02:25 -04:00
2016-06-08 18:36:24 -04:00
/** @internal */
2016-10-19 12:55:50 -04:00
_setUpControls ( ) : void {
2016-09-02 18:57:35 -04:00
this . _forEachChild ( ( control : AbstractControl ) = > {
control . setParent ( this ) ;
control . _registerOnCollectionChange ( this . _onCollectionChange ) ;
} ) ;
2016-06-08 18:36:24 -04:00
}
/** @internal */
2016-10-19 12:55:50 -04:00
_updateValue ( ) : void { this . _value = this . _reduceValue ( ) ; }
2016-06-08 18:36:24 -04:00
/** @internal */
2016-07-12 18:02:25 -04:00
_anyControls ( condition : Function ) : boolean {
2016-10-19 12:55:50 -04:00
let res = false ;
2016-07-12 18:02:25 -04:00
this . _forEachChild ( ( control : AbstractControl , name : string ) = > {
res = res || ( this . contains ( name ) && condition ( control ) ) ;
2016-06-08 18:36:24 -04:00
} ) ;
return res ;
}
/** @internal */
_reduceValue() {
return this . _reduceChildren (
{ } , ( acc : { [ k : string ] : AbstractControl } , control : AbstractControl , name : string ) = > {
2016-08-24 19:58:43 -04:00
if ( control . enabled || this . disabled ) {
acc [ name ] = control . value ;
}
2016-06-08 18:36:24 -04:00
return acc ;
} ) ;
}
/** @internal */
_reduceChildren ( initValue : any , fn : Function ) {
2016-10-19 12:55:50 -04:00
let res = initValue ;
2016-08-24 19:58:43 -04:00
this . _forEachChild (
( control : AbstractControl , name : string ) = > { res = fn ( res , control , name ) ; } ) ;
2016-06-08 18:36:24 -04:00
return res ;
}
/** @internal */
2016-08-24 19:58:43 -04:00
_allControlsDisabled ( ) : boolean {
2016-11-12 08:08:58 -05:00
for ( const controlName of Object . keys ( this . controls ) ) {
2016-08-24 19:58:43 -04:00
if ( this . controls [ controlName ] . enabled ) {
return false ;
}
}
2016-09-08 15:21:48 -04:00
return Object . keys ( this . controls ) . length > 0 || this . disabled ;
2016-06-08 18:36:24 -04:00
}
2016-08-05 16:35:17 -04:00
/** @internal */
_checkAllValuesPresent ( value : any ) : void {
this . _forEachChild ( ( control : AbstractControl , name : string ) = > {
if ( value [ name ] === undefined ) {
2016-08-25 03:50:16 -04:00
throw new Error ( ` Must supply a value for form control with name: ' ${ name } '. ` ) ;
2016-08-05 16:35:17 -04:00
}
} ) ;
}
2016-06-08 18:36:24 -04:00
}
/ * *
2017-01-25 02:52:41 -05:00
* @whatItDoes Tracks the value and validity state of an array of { @link FormControl } ,
* { @link FormGroup } or { @link FormArray } instances .
2016-06-08 18:36:24 -04:00
*
2016-09-13 16:07:40 -04:00
* A ` FormArray ` aggregates the values of each child { @link FormControl } into an array .
* It calculates its status by reducing the statuses of its children . For example , if one of
* the controls in a ` FormArray ` is invalid , the entire array becomes invalid .
2016-06-08 18:36:24 -04:00
*
2016-06-10 14:15:59 -04:00
* ` FormArray ` is one of the three fundamental building blocks used to define forms in Angular ,
2016-09-13 16:07:40 -04:00
* along with { @link FormControl } and { @link FormGroup } .
*
* @howToUse
*
* When instantiating a { @link FormArray } , pass in an array of child controls as the first
* argument .
*
* # # # Example
*
* ` ` `
* const arr = new FormArray ( [
* new FormControl ( 'Nancy' , Validators . minLength ( 2 ) ) ,
* new FormControl ( 'Drew' ) ,
* ] ) ;
*
* console . log ( arr . value ) ; // ['Nancy', 'Drew']
* console . log ( arr . status ) ; // 'VALID'
* ` ` `
*
* You can also include array - level validators as the second arg , or array - level async
* validators as the third arg . These come in handy when you want to perform validation
* that considers the value of more than one child control .
2016-06-08 18:36:24 -04:00
*
2016-09-13 16:07:40 -04:00
* # # # Adding or removing controls
2016-06-08 18:36:24 -04:00
*
* To change the controls in the array , use the ` push ` , ` insert ` , or ` removeAt ` methods
2016-06-10 14:15:59 -04:00
* in ` FormArray ` itself . These methods ensure the controls are properly tracked in the
2016-06-08 18:36:24 -04:00
* form ' s hierarchy . Do not modify the array of ` AbstractControl ` s used to instantiate
2016-06-10 14:15:59 -04:00
* the ` FormArray ` directly , as that will result in strange and unexpected behavior such
2016-06-08 18:36:24 -04:00
* as broken change detection .
*
2016-09-13 16:07:40 -04:00
* * * * npm package * * : ` @angular/forms `
2016-06-08 18:36:24 -04:00
*
2016-08-17 10:44:39 -04:00
* @stable
2016-06-08 18:36:24 -04:00
* /
2016-06-10 14:15:59 -04:00
export class FormArray extends AbstractControl {
2016-06-08 19:38:52 -04:00
constructor (
public controls : AbstractControl [ ] , validator : ValidatorFn = null ,
asyncValidator : AsyncValidatorFn = null ) {
2016-06-08 18:36:24 -04:00
super ( validator , asyncValidator ) ;
this . _initObservables ( ) ;
2016-09-02 18:57:35 -04:00
this . _setUpControls ( ) ;
2016-06-08 18:36:24 -04:00
this . updateValueAndValidity ( { onlySelf : true , emitEvent : false } ) ;
}
/ * *
* Get the { @link AbstractControl } at the given ` index ` in the array .
* /
at ( index : number ) : AbstractControl { return this . controls [ index ] ; }
/ * *
* Insert a new { @link AbstractControl } at the end of the array .
* /
push ( control : AbstractControl ) : void {
this . controls . push ( control ) ;
2016-09-02 18:57:35 -04:00
this . _registerControl ( control ) ;
2016-06-08 18:36:24 -04:00
this . updateValueAndValidity ( ) ;
2016-09-02 18:57:35 -04:00
this . _onCollectionChange ( ) ;
2016-06-08 18:36:24 -04:00
}
/ * *
* Insert a new { @link AbstractControl } at the given ` index ` in the array .
* /
insert ( index : number , control : AbstractControl ) : void {
2016-10-19 12:55:50 -04:00
this . controls . splice ( index , 0 , control ) ;
2016-09-02 18:57:35 -04:00
this . _registerControl ( control ) ;
2016-06-08 18:36:24 -04:00
this . updateValueAndValidity ( ) ;
2016-09-02 18:57:35 -04:00
this . _onCollectionChange ( ) ;
2016-06-08 18:36:24 -04:00
}
/ * *
* Remove the control at the given ` index ` in the array .
* /
removeAt ( index : number ) : void {
2016-09-02 18:57:35 -04:00
if ( this . controls [ index ] ) this . controls [ index ] . _registerOnCollectionChange ( ( ) = > { } ) ;
2016-10-19 12:55:50 -04:00
this . controls . splice ( index , 1 ) ;
2016-06-08 18:36:24 -04:00
this . updateValueAndValidity ( ) ;
2016-09-02 18:57:35 -04:00
this . _onCollectionChange ( ) ;
}
/ * *
* Replace an existing control .
* /
setControl ( index : number , control : AbstractControl ) : void {
if ( this . controls [ index ] ) this . controls [ index ] . _registerOnCollectionChange ( ( ) = > { } ) ;
2016-10-19 12:55:50 -04:00
this . controls . splice ( index , 1 ) ;
2016-09-02 18:57:35 -04:00
if ( control ) {
2016-10-19 12:55:50 -04:00
this . controls . splice ( index , 0 , control ) ;
2016-09-02 18:57:35 -04:00
this . _registerControl ( control ) ;
}
this . updateValueAndValidity ( ) ;
this . _onCollectionChange ( ) ;
2016-06-08 18:36:24 -04:00
}
/ * *
* Length of the control array .
* /
get length ( ) : number { return this . controls . length ; }
2016-09-13 16:07:40 -04:00
/ * *
* Sets the value of the { @link FormArray } . It accepts an array that matches
* the structure of the control .
*
* This method performs strict checks , so it will throw an error if you try
* to set the value of a control that doesn ' t exist or if you exclude the
* value of a control .
*
* # # # Example
*
* ` ` `
* const arr = new FormArray ( [
* new FormControl ( ) ,
* new FormControl ( )
* ] ) ;
* console . log ( arr . value ) ; // [null, null]
*
* arr . setValue ( [ 'Nancy' , 'Drew' ] ) ;
* console . log ( arr . value ) ; // ['Nancy', 'Drew']
* ` ` `
* /
2016-10-19 12:54:54 -04:00
setValue ( value : any [ ] , { onlySelf , emitEvent } : { onlySelf? : boolean , emitEvent? : boolean } = { } ) :
void {
2016-08-05 16:35:17 -04:00
this . _checkAllValuesPresent ( value ) ;
2016-07-08 16:04:25 -04:00
value . forEach ( ( newValue : any , index : number ) = > {
this . _throwIfControlMissing ( index ) ;
2016-10-19 12:54:54 -04:00
this . at ( index ) . setValue ( newValue , { onlySelf : true , emitEvent } ) ;
2016-08-05 16:35:17 -04:00
} ) ;
2016-10-19 12:54:54 -04:00
this . updateValueAndValidity ( { onlySelf , emitEvent } ) ;
2016-08-05 16:35:17 -04:00
}
2016-09-13 16:07:40 -04:00
/ * *
* Patches the value of the { @link FormArray } . It accepts an array that matches the
* structure of the control , and will do its best to match the values to the correct
* controls in the group .
*
* It accepts both super - sets and sub - sets of the array without throwing an error .
*
* # # # Example
*
* ` ` `
* const arr = new FormArray ( [
* new FormControl ( ) ,
* new FormControl ( )
* ] ) ;
* console . log ( arr . value ) ; // [null, null]
*
* arr . patchValue ( [ 'Nancy' ] ) ;
* console . log ( arr . value ) ; // ['Nancy', null]
* ` ` `
* /
2016-10-19 12:54:54 -04:00
patchValue ( value : any [ ] , { onlySelf , emitEvent } : { onlySelf? : boolean , emitEvent? : boolean } = { } ) :
void {
2016-08-05 16:35:17 -04:00
value . forEach ( ( newValue : any , index : number ) = > {
if ( this . at ( index ) ) {
2016-10-19 12:54:54 -04:00
this . at ( index ) . patchValue ( newValue , { onlySelf : true , emitEvent } ) ;
2016-08-05 16:35:17 -04:00
}
2016-07-08 16:04:25 -04:00
} ) ;
2016-10-19 12:54:54 -04:00
this . updateValueAndValidity ( { onlySelf , emitEvent } ) ;
2016-07-08 16:04:25 -04:00
}
2016-09-13 16:07:40 -04:00
/ * *
* Resets the { @link FormArray } . This means by default :
*
* * The array and all descendants are marked ` pristine `
* * The array and all descendants are marked ` untouched `
* * The value of all descendants will be null or null maps
*
* You can also reset to a specific form state by passing in an array of states
* that matches the structure of the control . The state can be a standalone value
* or a form state object with both a value and a disabled status .
*
* # # # Example
*
* ` ` ` ts
* this . arr . reset ( [ 'name' , 'last name' ] ) ;
*
* console . log ( this . arr . value ) ; // ['name', 'last name']
* ` ` `
*
* - OR -
*
* ` ` `
* this . arr . reset ( [
* { value : 'name' , disabled : true } ,
* 'last'
* ] ) ;
*
* console . log ( this . arr . value ) ; // ['name', 'last name']
* console . log ( this . arr . get ( 0 ) . status ) ; // 'DISABLED'
* ` ` `
* /
2016-10-19 12:54:54 -04:00
reset ( value : any = [ ] , { onlySelf , emitEvent } : { onlySelf? : boolean , emitEvent? : boolean } = { } ) :
void {
2016-07-12 18:02:25 -04:00
this . _forEachChild ( ( control : AbstractControl , index : number ) = > {
2016-10-19 12:54:54 -04:00
control . reset ( value [ index ] , { onlySelf : true , emitEvent } ) ;
2016-07-12 18:02:25 -04:00
} ) ;
2016-10-19 12:54:54 -04:00
this . updateValueAndValidity ( { onlySelf , emitEvent } ) ;
this . _updatePristine ( { onlySelf } ) ;
this . _updateTouched ( { onlySelf } ) ;
2016-07-12 18:02:25 -04:00
}
2016-09-13 16:07:40 -04:00
/ * *
* The aggregate value of the array , including any disabled controls .
*
* If you ' d like to include all values regardless of disabled status , use this method .
* Otherwise , the ` value ` property is the best way to get the value of the array .
* /
2016-11-18 06:44:05 -05:00
getRawValue ( ) : any [ ] {
return this . controls . map ( ( control : AbstractControl ) = > {
return control instanceof FormControl ? control . value : ( < any > control ) . getRawValue ( ) ;
} ) ;
}
2016-08-24 19:58:43 -04:00
2016-07-08 16:04:25 -04:00
/** @internal */
_throwIfControlMissing ( index : number ) : void {
2016-08-05 16:35:17 -04:00
if ( ! this . controls . length ) {
2016-08-25 03:50:16 -04:00
throw new Error ( `
2016-08-05 16:35:17 -04:00
There are no form controls registered with this array yet . If you ' re using ngModel ,
you may want to check next tick ( e . g . use setTimeout ) .
` );
}
2016-07-08 16:04:25 -04:00
if ( ! this . at ( index ) ) {
2016-08-25 03:50:16 -04:00
throw new Error ( ` Cannot find form control at index ${ index } ` ) ;
2016-07-08 16:04:25 -04:00
}
}
2016-07-12 18:02:25 -04:00
/** @internal */
_forEachChild ( cb : Function ) : void {
this . controls . forEach ( ( control : AbstractControl , index : number ) = > { cb ( control , index ) ; } ) ;
}
2016-06-08 18:36:24 -04:00
/** @internal */
2016-08-24 19:58:43 -04:00
_updateValue ( ) : void {
this . _value = this . controls . filter ( ( control ) = > control . enabled || this . disabled )
. map ( ( control ) = > control . value ) ;
}
2016-06-08 18:36:24 -04:00
/** @internal */
2016-07-12 18:02:25 -04:00
_anyControls ( condition : Function ) : boolean {
2016-08-24 19:58:43 -04:00
return this . controls . some ( ( control : AbstractControl ) = > control . enabled && condition ( control ) ) ;
2016-06-08 18:36:24 -04:00
}
/** @internal */
2016-09-02 18:57:35 -04:00
_setUpControls ( ) : void {
this . _forEachChild ( ( control : AbstractControl ) = > this . _registerControl ( control ) ) ;
2016-06-08 18:36:24 -04:00
}
2016-08-05 16:35:17 -04:00
/** @internal */
_checkAllValuesPresent ( value : any ) : void {
this . _forEachChild ( ( control : AbstractControl , i : number ) = > {
if ( value [ i ] === undefined ) {
2016-08-25 03:50:16 -04:00
throw new Error ( ` Must supply a value for form control at index: ${ i } . ` ) ;
2016-08-05 16:35:17 -04:00
}
} ) ;
}
2016-08-24 19:58:43 -04:00
/** @internal */
_allControlsDisabled ( ) : boolean {
2016-11-12 08:08:58 -05:00
for ( const control of this . controls ) {
2016-08-24 19:58:43 -04:00
if ( control . enabled ) return false ;
}
2016-09-08 15:21:48 -04:00
return this . controls . length > 0 || this . disabled ;
2016-08-24 19:58:43 -04:00
}
2016-09-02 18:57:35 -04:00
private _registerControl ( control : AbstractControl ) {
control . setParent ( this ) ;
control . _registerOnCollectionChange ( this . _onCollectionChange ) ;
}
2016-06-08 18:36:24 -04:00
}