mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(forms): Split up model.ts. (#45217)
model.ts is currently extremely large. This is the first step in an attempt to refactor it to be more easily navigable and reviewable. This commit breaks up `model.ts` into the following new files: * `model/abstract_model.ts`: The remainder of the model, including the `AbstractControl` base class and helper functions which are used throughout. * `model/form_control.ts`: `FormControl`, `FormControlOptions`, and helpers, plus the constructor and untyped friends. * `model/form_array.ts`: `FormArray` and untyped friends. * `model/form_group.ts`: `FormGroup` and untyped friends. This first phase is a purely mechanical code move. There is no new code at all, and no interfaces have been separated. PR Close #45217
This commit is contained in:
parent
c5df474c7c
commit
c8e4d62882
24 changed files with 2712 additions and 2667 deletions
|
|
@ -138,25 +138,11 @@
|
|||
"packages/core/testing/src/test_bed.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives.ts",
|
||||
"packages/forms/src/directives/ng_control_status.ts",
|
||||
"packages/forms/src/directives/abstract_control_directive.ts",
|
||||
"packages/forms/src/model.ts",
|
||||
"packages/forms/src/directives/shared.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_control_directive.ts",
|
||||
"packages/forms/src/model.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
"packages/forms/src/directives/control_container.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_control_directive.ts",
|
||||
"packages/forms/src/model.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
"packages/forms/src/directives/control_container.ts",
|
||||
"packages/forms/src/directives/form_interface.ts",
|
||||
"packages/forms/src/directives/ng_control.ts"
|
||||
"packages/forms/src/model/abstract_model.ts",
|
||||
"packages/forms/src/forms.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
|
|
@ -165,10 +151,7 @@
|
|||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
"packages/forms/src/directives/control_container.ts",
|
||||
"packages/forms/src/directives/form_interface.ts",
|
||||
"packages/forms/src/model.ts",
|
||||
"packages/forms/src/directives/shared.ts"
|
||||
"packages/forms/src/directives/form_interface.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
|
|
@ -176,13 +159,59 @@
|
|||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
"packages/forms/src/model.ts",
|
||||
"packages/forms/src/directives/shared.ts"
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/control_container.ts",
|
||||
"packages/forms/src/directives/form_interface.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/ng_control.ts",
|
||||
"packages/forms/src/directives/control_container.ts",
|
||||
"packages/forms/src/directives/form_interface.ts",
|
||||
"packages/forms/src/directives/ng_control.ts"
|
||||
"packages/forms/src/directives/form_interface.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts",
|
||||
"packages/forms/src/directives/control_container.ts",
|
||||
"packages/forms/src/directives/form_interface.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_directive.ts",
|
||||
"packages/forms/src/directives/control_container.ts",
|
||||
"packages/forms/src/directives/form_interface.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_directive.ts",
|
||||
"packages/forms/src/directives/form_interface.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_directive.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_control_name.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/abstract_form_group_directive.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_directive.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_control_name.ts",
|
||||
"packages/forms/src/directives/control_container.ts",
|
||||
"packages/forms/src/directives/form_interface.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/ng_form.ts",
|
||||
|
|
@ -200,14 +229,6 @@
|
|||
"packages/forms/src/directives/reactive_directives/form_group_directive.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_control_name.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/reactive_directives/form_control_directive.ts",
|
||||
"packages/forms/src/model.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_directive.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_control_name.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/reactive_directives/form_control_name.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_directive.ts"
|
||||
|
|
@ -223,13 +244,6 @@
|
|||
"packages/forms/src/directives/reactive_directives/form_group_name.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_directive.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/reactive_directives/form_control_name.ts",
|
||||
"packages/forms/src/model.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_directive.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/reactive_directives/form_group_directive.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts"
|
||||
|
|
@ -239,40 +253,23 @@
|
|||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/reactive_directives/form_group_directive.ts",
|
||||
"packages/forms/src/model.ts",
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts",
|
||||
"packages/forms/src/directives/shared.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/reactive_directives/form_group_name.ts",
|
||||
"packages/forms/src/model.ts",
|
||||
"packages/forms/src/directives/shared.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/model.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/validators.ts",
|
||||
"packages/forms/src/directives/validators.ts",
|
||||
"packages/forms/src/model.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/shared.ts",
|
||||
"packages/forms/src/validators.ts",
|
||||
"packages/forms/src/model.ts"
|
||||
"packages/forms/src/model/abstract_model.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/validators.ts",
|
||||
"packages/forms/src/validators.ts"
|
||||
],
|
||||
[
|
||||
"packages/forms/src/directives/validators.ts",
|
||||
"packages/forms/src/validators.ts",
|
||||
"packages/forms/src/model/abstract_model.ts"
|
||||
],
|
||||
[
|
||||
"packages/router/src/directives/router_outlet.ts",
|
||||
"packages/router/src/router_outlet_context.ts"
|
||||
|
|
|
|||
|
|
@ -1115,9 +1115,6 @@
|
|||
{
|
||||
"name": "isFormControl"
|
||||
},
|
||||
{
|
||||
"name": "isFormGroup"
|
||||
},
|
||||
{
|
||||
"name": "isForwardRef"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1076,9 +1076,6 @@
|
|||
{
|
||||
"name": "isDirectiveHost"
|
||||
},
|
||||
{
|
||||
"name": "isFormGroup"
|
||||
},
|
||||
{
|
||||
"name": "isForwardRef"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
import {AbstractControl} from '../model';
|
||||
import {AbstractControl} from '../model/abstract_model';
|
||||
import {composeAsyncValidators, composeValidators} from '../validators';
|
||||
|
||||
import {AsyncValidator, AsyncValidatorFn, ValidationErrors, Validator, ValidatorFn} from './validators';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Directive, OnDestroy, OnInit} from '@angular/core';
|
||||
|
||||
import {FormGroup} from '../model';
|
||||
import {FormGroup} from '../model/form_group';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {Form} from './form_interface';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
import {Form} from './form_interface';
|
||||
|
||||
|
||||
/**
|
||||
* @description
|
||||
* A base class for directives that contain multiple registered instances of `NgControl`.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {FormControl, FormGroup} from '../model';
|
||||
import {FormControl} from '../model/form_control';
|
||||
import {FormGroup} from '../model/form_group';
|
||||
|
||||
import {AbstractFormGroupDirective} from './abstract_form_group_directive';
|
||||
import {NgControl} from './ng_control';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@
|
|||
|
||||
import {AfterViewInit, Directive, EventEmitter, forwardRef, Inject, Input, Optional, Self} from '@angular/core';
|
||||
|
||||
import {AbstractControl, FormControl, FormGroup, FormHooks} from '../model';
|
||||
import {AbstractControl, FormHooks} from '../model/abstract_model';
|
||||
import {FormControl} from '../model/form_control';
|
||||
import {FormGroup} from '../model/form_group';
|
||||
import {composeAsyncValidators, composeValidators, NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
|
||||
import {ChangeDetectorRef, Directive, EventEmitter, forwardRef, Host, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges, ɵcoerceToBoolean as coerceToBoolean} from '@angular/core';
|
||||
|
||||
import {FormControl, FormHooks} from '../model';
|
||||
import {FormHooks} from '../model/abstract_model';
|
||||
import {FormControl} from '../model/form_control';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
|
||||
|
||||
import {AbstractFormGroupDirective} from './abstract_form_group_directive';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Directive, EventEmitter, forwardRef, Inject, InjectionToken, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges} from '@angular/core';
|
||||
|
||||
import {FormControl} from '../../model';
|
||||
import {FormControl} from '../../model/form_control';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor';
|
||||
import {NgControl} from '../ng_control';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
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/form_control';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
|
||||
import {AbstractFormGroupDirective} from '../abstract_form_group_directive';
|
||||
import {ControlContainer} from '../control_container';
|
||||
|
|
|
|||
|
|
@ -8,12 +8,15 @@
|
|||
|
||||
import {Directive, EventEmitter, forwardRef, Inject, Input, OnChanges, OnDestroy, Optional, Output, Self, SimpleChanges} from '@angular/core';
|
||||
|
||||
import {FormArray, FormControl, FormGroup, isFormControl} from '../../model';
|
||||
import {FormArray} from '../../model/form_array';
|
||||
import {FormControl, isFormControl} from '../../model/form_control';
|
||||
import {FormGroup} from '../../model/form_group';
|
||||
import {removeListItem} from '../../util';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
|
||||
import {ControlContainer} from '../control_container';
|
||||
import {Form} from '../form_interface';
|
||||
import {missingFormException} from '../reactive_errors';
|
||||
import {cleanUpControl, cleanUpFormContainer, cleanUpValidators, removeListItem, setUpControl, setUpFormContainer, setUpValidators, syncPendingControls} from '../shared';
|
||||
import {cleanUpControl, cleanUpFormContainer, cleanUpValidators, setUpControl, setUpFormContainer, setUpValidators, syncPendingControls} from '../shared';
|
||||
import {AsyncValidator, AsyncValidatorFn, Validator, ValidatorFn} from '../validators';
|
||||
|
||||
import {FormControlName} from './form_control_name';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {Directive, forwardRef, Host, Inject, Input, OnDestroy, OnInit, Optional, Self, SkipSelf} from '@angular/core';
|
||||
|
||||
import {FormArray} from '../../model';
|
||||
import {FormArray} from '../../model/form_array';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
|
||||
import {AbstractFormGroupDirective} from '../abstract_form_group_directive';
|
||||
import {ControlContainer} from '../control_container';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AbstractControl, FormArray, FormControl, FormGroup} from '../model';
|
||||
import {AbstractControl} from '../model/abstract_model';
|
||||
import {FormArray} from '../model/form_array';
|
||||
import {FormControl} from '../model/form_control';
|
||||
import {FormGroup} from '../model/form_group';
|
||||
import {getControlAsyncValidators, getControlValidators, mergeValidators} from '../validators';
|
||||
|
||||
import {AbstractControlDirective} from './abstract_control_directive';
|
||||
|
|
@ -347,11 +350,6 @@ export function selectValueAccessor(
|
|||
return null;
|
||||
}
|
||||
|
||||
export function removeListItem<T>(list: T[], el: T): void {
|
||||
const index = list.indexOf(el);
|
||||
if (index > -1) list.splice(index, 1);
|
||||
}
|
||||
|
||||
// TODO(kara): remove after deprecation period
|
||||
export function _ngModelWarning(
|
||||
name: string, type: {_ngModelWarningSentOnce: boolean},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import {Directive, forwardRef, Input, OnChanges, SimpleChanges, StaticProvider, ɵcoerceToBoolean as coerceToBoolean} from '@angular/core';
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
import {AbstractControl} from '../model';
|
||||
import {AbstractControl} from '../model/abstract_model';
|
||||
import {emailValidator, maxLengthValidator, maxValidator, minLengthValidator, minValidator, NG_VALIDATORS, nullValidator, patternValidator, requiredTrueValidator, requiredValidator} from '../validators';
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ import {Injectable} from '@angular/core';
|
|||
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
import {ReactiveFormsModule} from './form_providers';
|
||||
import {AbstractControl, AbstractControlOptions, FormArray, FormControl, FormControlOptions, FormGroup, FormHooks, isFormArray, isFormControl, isFormGroup} from './model';
|
||||
import {AbstractControl, AbstractControlOptions, FormHooks} from './model/abstract_model';
|
||||
import {FormArray, isFormArray} from './model/form_array';
|
||||
import {FormControl, FormControlOptions, isFormControl} from './model/form_control';
|
||||
import {FormGroup, isFormGroup} from './model/form_group';
|
||||
|
||||
function isAbstractControlOptions(options: AbstractControlOptions|
|
||||
{[key: string]: any}): options is AbstractControlOptions {
|
||||
|
|
|
|||
|
|
@ -42,7 +42,10 @@ export {NgSelectOption, SelectControlValueAccessor} from './directives/select_co
|
|||
export {SelectMultipleControlValueAccessor, ɵNgSelectMultipleOption} from './directives/select_multiple_control_value_accessor';
|
||||
export {AsyncValidator, AsyncValidatorFn, CheckboxRequiredValidator, EmailValidator, MaxLengthValidator, MaxValidator, MinLengthValidator, MinValidator, PatternValidator, RequiredValidator, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
|
||||
export {FormBuilder} from './form_builder';
|
||||
export {AbstractControl, AbstractControlOptions, FormArray, FormControl, FormControlOptions, FormControlStatus, FormGroup, UntypedFormArray, UntypedFormControl, UntypedFormGroup, ɵFormControlCtor} from './model';
|
||||
export {AbstractControl, AbstractControlOptions, FormControlStatus} from './model/abstract_model';
|
||||
export {FormArray, UntypedFormArray} from './model/form_array';
|
||||
export {FormControl, FormControlOptions, UntypedFormControl, ɵFormControlCtor} from './model/form_control';
|
||||
export {FormGroup, UntypedFormGroup} from './model/form_group';
|
||||
export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './validators';
|
||||
export {VERSION} from './version';
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
1149
packages/forms/src/model/abstract_model.ts
Normal file
1149
packages/forms/src/model/abstract_model.ts
Normal file
File diff suppressed because it is too large
Load diff
499
packages/forms/src/model/form_array.ts
Normal file
499
packages/forms/src/model/form_array.ts
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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
|
||||
*/
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from '../directives/validators';
|
||||
|
||||
import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertControlPresent, pickAsyncValidators, pickValidators} from './abstract_model';
|
||||
|
||||
/**
|
||||
* Tracks the value and validity state of an array of `FormControl`,
|
||||
* `FormGroup` or `FormArray` instances.
|
||||
*
|
||||
* A `FormArray` aggregates the values of each child `FormControl` into an array.
|
||||
* It calculates its status by reducing the status values of its children. For example, if one of
|
||||
* the controls in a `FormArray` is invalid, the entire array becomes invalid.
|
||||
*
|
||||
* `FormArray` is one of the three fundamental building blocks used to define forms in Angular,
|
||||
* along with `FormControl` and `FormGroup`.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Create an array of form controls
|
||||
*
|
||||
* ```
|
||||
* const arr = new FormArray([
|
||||
* new FormControl('Nancy', Validators.minLength(2)),
|
||||
* new FormControl('Drew'),
|
||||
* ]);
|
||||
*
|
||||
* console.log(arr.value); // ['Nancy', 'Drew']
|
||||
* console.log(arr.status); // 'VALID'
|
||||
* ```
|
||||
*
|
||||
* ### Create a form array with array-level validators
|
||||
*
|
||||
* You include array-level validators and async validators. These come in handy
|
||||
* when you want to perform validation that considers the value of more than one child
|
||||
* control.
|
||||
*
|
||||
* The two types of validators are passed in separately as the second and third arg
|
||||
* respectively, or together as part of an options object.
|
||||
*
|
||||
* ```
|
||||
* const arr = new FormArray([
|
||||
* new FormControl('Nancy'),
|
||||
* new FormControl('Drew')
|
||||
* ], {validators: myValidator, asyncValidators: myAsyncValidator});
|
||||
* ```
|
||||
*
|
||||
* ### Set the updateOn property for all controls in a form array
|
||||
*
|
||||
* 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
|
||||
* array level, all child controls default to 'blur', unless the child
|
||||
* has explicitly specified a different `updateOn` value.
|
||||
*
|
||||
* ```ts
|
||||
* const arr = new FormArray([
|
||||
* new FormControl()
|
||||
* ], {updateOn: 'blur'});
|
||||
* ```
|
||||
*
|
||||
* ### Adding or removing controls from a form array
|
||||
*
|
||||
* To change the controls in the array, use the `push`, `insert`, `removeAt` or `clear` methods
|
||||
* in `FormArray` itself. These methods ensure the controls are properly tracked in the
|
||||
* form's hierarchy. Do not modify the array of `AbstractControl`s used to instantiate
|
||||
* the `FormArray` directly, as that result in strange and unexpected behavior such
|
||||
* as broken change detection.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export class FormArray extends AbstractControl {
|
||||
/**
|
||||
* Creates a new `FormArray` instance.
|
||||
*
|
||||
* @param controls An array of child controls. Each child control is given an index
|
||||
* where it is registered.
|
||||
*
|
||||
* @param validatorOrOpts A synchronous validator function, or an array of
|
||||
* such functions, or an `AbstractControlOptions` object that contains validation functions
|
||||
* and a validation trigger.
|
||||
*
|
||||
* @param asyncValidator A single async validator or array of async validator functions
|
||||
*
|
||||
*/
|
||||
constructor(
|
||||
public controls: AbstractControl[],
|
||||
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
|
||||
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null) {
|
||||
super(pickValidators(validatorOrOpts), pickAsyncValidators(asyncValidator, validatorOrOpts));
|
||||
this._initObservables();
|
||||
this._setUpdateStrategy(validatorOrOpts);
|
||||
this._setUpControls();
|
||||
this.updateValueAndValidity({
|
||||
onlySelf: true,
|
||||
// If `asyncValidator` is present, it will trigger control status change from `PENDING` to
|
||||
// `VALID` or `INVALID`.
|
||||
// The status should be broadcasted via the `statusChanges` observable, so we set `emitEvent`
|
||||
// to `true` to allow that during the control creation process.
|
||||
emitEvent: !!this.asyncValidator
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `AbstractControl` at the given `index` in the array.
|
||||
*
|
||||
* @param index Index in the array to retrieve the control. If `index` is negative, it will wrap
|
||||
* around from the back, and if index is greatly negative (less than `-length`), the result is
|
||||
* undefined. This behavior is the same as `Array.at(index)`.
|
||||
*/
|
||||
at(index: number): AbstractControl {
|
||||
return this.controls[this._adjustIndex(index)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new `AbstractControl` at the end of the array.
|
||||
*
|
||||
* @param control Form control to be inserted
|
||||
* @param options Specifies whether this FormArray instance should emit events after a new
|
||||
* control is added.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges` observables emit events with the latest status and value when the control is
|
||||
* inserted. When false, no events are emitted.
|
||||
*/
|
||||
push(control: AbstractControl, options: {emitEvent?: boolean} = {}): void {
|
||||
this.controls.push(control);
|
||||
this._registerControl(control);
|
||||
this.updateValueAndValidity({emitEvent: options.emitEvent});
|
||||
this._onCollectionChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new `AbstractControl` at the given `index` in the array.
|
||||
*
|
||||
* @param index Index in the array to insert the control. If `index` is negative, wraps around
|
||||
* from the back. If `index` is greatly negative (less than `-length`), prepends to the array.
|
||||
* This behavior is the same as `Array.splice(index, 0, control)`.
|
||||
* @param control Form control to be inserted
|
||||
* @param options Specifies whether this FormArray instance should emit events after a new
|
||||
* control is inserted.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges` observables emit events with the latest status and value when the control is
|
||||
* inserted. When false, no events are emitted.
|
||||
*/
|
||||
insert(index: number, control: AbstractControl, options: {emitEvent?: boolean} = {}): void {
|
||||
this.controls.splice(index, 0, control);
|
||||
|
||||
this._registerControl(control);
|
||||
this.updateValueAndValidity({emitEvent: options.emitEvent});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the control at the given `index` in the array.
|
||||
*
|
||||
* @param index Index in the array to remove the control. If `index` is negative, wraps around
|
||||
* from the back. If `index` is greatly negative (less than `-length`), removes the first
|
||||
* element. This behavior is the same as `Array.splice(index, 1)`.
|
||||
* @param options Specifies whether this FormArray instance should emit events after a
|
||||
* control is removed.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges` observables emit events with the latest status and value when the control is
|
||||
* removed. When false, no events are emitted.
|
||||
*/
|
||||
removeAt(index: number, options: {emitEvent?: boolean} = {}): void {
|
||||
// Adjust the index, then clamp it at no less than 0 to prevent undesired underflows.
|
||||
let adjustedIndex = this._adjustIndex(index);
|
||||
if (adjustedIndex < 0) adjustedIndex = 0;
|
||||
|
||||
if (this.controls[adjustedIndex])
|
||||
this.controls[adjustedIndex]._registerOnCollectionChange(() => {});
|
||||
this.controls.splice(adjustedIndex, 1);
|
||||
this.updateValueAndValidity({emitEvent: options.emitEvent});
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an existing control.
|
||||
*
|
||||
* @param index Index in the array to replace the control. If `index` is negative, wraps around
|
||||
* from the back. If `index` is greatly negative (less than `-length`), replaces the first
|
||||
* element. This behavior is the same as `Array.splice(index, 1, control)`.
|
||||
* @param control The `AbstractControl` control to replace the existing control
|
||||
* @param options Specifies whether this FormArray instance should emit events after an
|
||||
* existing control is replaced with a new one.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges` observables emit events with the latest status and value when the control is
|
||||
* replaced with a new one. When false, no events are emitted.
|
||||
*/
|
||||
setControl(index: number, control: AbstractControl, options: {emitEvent?: boolean} = {}): void {
|
||||
// Adjust the index, then clamp it at no less than 0 to prevent undesired underflows.
|
||||
let adjustedIndex = this._adjustIndex(index);
|
||||
if (adjustedIndex < 0) adjustedIndex = 0;
|
||||
|
||||
if (this.controls[adjustedIndex])
|
||||
this.controls[adjustedIndex]._registerOnCollectionChange(() => {});
|
||||
this.controls.splice(adjustedIndex, 1);
|
||||
|
||||
if (control) {
|
||||
this.controls.splice(adjustedIndex, 0, control);
|
||||
this._registerControl(control);
|
||||
}
|
||||
|
||||
this.updateValueAndValidity({emitEvent: options.emitEvent});
|
||||
this._onCollectionChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the control array.
|
||||
*/
|
||||
get length(): number {
|
||||
return this.controls.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the `FormArray`. It accepts an array that matches
|
||||
* the structure of the control.
|
||||
*
|
||||
* This method performs strict checks, and throws 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.
|
||||
*
|
||||
* @usageNotes
|
||||
* ### Set the values for the controls in the form array
|
||||
*
|
||||
* ```
|
||||
* const arr = new FormArray([
|
||||
* new FormControl(),
|
||||
* new FormControl()
|
||||
* ]);
|
||||
* console.log(arr.value); // [null, null]
|
||||
*
|
||||
* arr.setValue(['Nancy', 'Drew']);
|
||||
* console.log(arr.value); // ['Nancy', 'Drew']
|
||||
* ```
|
||||
*
|
||||
* @param value Array of values for the controls
|
||||
* @param options Configure options that determine how the control propagates changes and
|
||||
* emits events after the value changes
|
||||
*
|
||||
* * `onlySelf`: When true, each change only affects this control, and not its parent. Default
|
||||
* is false.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges`
|
||||
* observables emit events with the latest status and value when the control value is updated.
|
||||
* When false, no events are emitted.
|
||||
* The configuration options are passed to the {@link AbstractControl#updateValueAndValidity
|
||||
* updateValueAndValidity} method.
|
||||
*/
|
||||
override setValue(value: any[], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
assertAllValuesPresent(this, false, value);
|
||||
value.forEach((newValue: any, index: number) => {
|
||||
assertControlPresent(this, false, index);
|
||||
this.at(index).setValue(newValue, {onlySelf: true, emitEvent: options.emitEvent});
|
||||
});
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches the value of the `FormArray`. It accepts an array that matches the
|
||||
* structure of the control, and does 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.
|
||||
*
|
||||
* @usageNotes
|
||||
* ### Patch the values for controls in a form array
|
||||
*
|
||||
* ```
|
||||
* const arr = new FormArray([
|
||||
* new FormControl(),
|
||||
* new FormControl()
|
||||
* ]);
|
||||
* console.log(arr.value); // [null, null]
|
||||
*
|
||||
* arr.patchValue(['Nancy']);
|
||||
* console.log(arr.value); // ['Nancy', null]
|
||||
* ```
|
||||
*
|
||||
* @param value Array of latest values for the controls
|
||||
* @param options Configure options that determine how the control propagates changes and
|
||||
* emits events after the value changes
|
||||
*
|
||||
* * `onlySelf`: When true, each change only affects this control, and not its parent. Default
|
||||
* is false.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges` observables emit events with the latest status and value when the control value
|
||||
* is updated. When false, no events are emitted. The configuration options are passed to
|
||||
* the {@link AbstractControl#updateValueAndValidity updateValueAndValidity} method.
|
||||
*/
|
||||
override patchValue(value: any[], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
// Even though the `value` argument type doesn't allow `null` and `undefined` values, the
|
||||
// `patchValue` can be called recursively and inner data structures might have these values, so
|
||||
// we just ignore such cases when a field containing FormArray instance receives `null` or
|
||||
// `undefined` as a value.
|
||||
if (value == null /* both `null` and `undefined` */) return;
|
||||
|
||||
value.forEach((newValue: any, index: number) => {
|
||||
if (this.at(index)) {
|
||||
this.at(index).patchValue(newValue, {onlySelf: true, emitEvent: options.emitEvent});
|
||||
}
|
||||
});
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the `FormArray` and all descendants are marked `pristine` and `untouched`, and the
|
||||
* value of all descendants to null or null maps.
|
||||
*
|
||||
* You reset to a specific form state by passing in an array of states
|
||||
* that matches the structure of the control. The state is a standalone value
|
||||
* or a form state object with both a value and a disabled status.
|
||||
*
|
||||
* @usageNotes
|
||||
* ### Reset the values in a form array
|
||||
*
|
||||
* ```ts
|
||||
* const arr = new FormArray([
|
||||
* new FormControl(),
|
||||
* new FormControl()
|
||||
* ]);
|
||||
* arr.reset(['name', 'last name']);
|
||||
*
|
||||
* console.log(arr.value); // ['name', 'last name']
|
||||
* ```
|
||||
*
|
||||
* ### Reset the values in a form array and the disabled status for the first control
|
||||
*
|
||||
* ```
|
||||
* arr.reset([
|
||||
* {value: 'name', disabled: true},
|
||||
* 'last'
|
||||
* ]);
|
||||
*
|
||||
* console.log(arr.value); // ['last']
|
||||
* console.log(arr.at(0).status); // 'DISABLED'
|
||||
* ```
|
||||
*
|
||||
* @param value Array of values for the controls
|
||||
* @param options Configure options that determine how the control propagates changes and
|
||||
* emits events after the value changes
|
||||
*
|
||||
* * `onlySelf`: When true, each change only affects this control, and not its parent. Default
|
||||
* is false.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges`
|
||||
* observables emit events with the latest status and value when the control is reset.
|
||||
* When false, no events are emitted.
|
||||
* The configuration options are passed to the {@link AbstractControl#updateValueAndValidity
|
||||
* updateValueAndValidity} method.
|
||||
*/
|
||||
override reset(value: any = [], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
this._forEachChild((control: AbstractControl, index: number) => {
|
||||
control.reset(value[index], {onlySelf: true, emitEvent: options.emitEvent});
|
||||
});
|
||||
this._updatePristine(options);
|
||||
this._updateTouched(options);
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* The aggregate value of the array, including any disabled controls.
|
||||
*
|
||||
* Reports all values regardless of disabled status.
|
||||
* For enabled controls only, the `value` property is the best way to get the value of the array.
|
||||
*/
|
||||
override getRawValue(): any[] {
|
||||
return this.controls.map((control: AbstractControl) => control.getRawValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all controls in the `FormArray`.
|
||||
*
|
||||
* @param options Specifies whether this FormArray instance should emit events after all
|
||||
* controls are removed.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges` observables emit events with the latest status and value when all controls
|
||||
* in this FormArray instance are removed. When false, no events are emitted.
|
||||
*
|
||||
* @usageNotes
|
||||
* ### Remove all elements from a FormArray
|
||||
*
|
||||
* ```ts
|
||||
* const arr = new FormArray([
|
||||
* new FormControl(),
|
||||
* new FormControl()
|
||||
* ]);
|
||||
* console.log(arr.length); // 2
|
||||
*
|
||||
* arr.clear();
|
||||
* console.log(arr.length); // 0
|
||||
* ```
|
||||
*
|
||||
* It's a simpler and more efficient alternative to removing all elements one by one:
|
||||
*
|
||||
* ```ts
|
||||
* const arr = new FormArray([
|
||||
* new FormControl(),
|
||||
* new FormControl()
|
||||
* ]);
|
||||
*
|
||||
* while (arr.length) {
|
||||
* arr.removeAt(0);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
clear(options: {emitEvent?: boolean} = {}): void {
|
||||
if (this.controls.length < 1) return;
|
||||
this._forEachChild((control: AbstractControl) => control._registerOnCollectionChange(() => {}));
|
||||
this.controls.splice(0);
|
||||
this.updateValueAndValidity({emitEvent: options.emitEvent});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts a negative index by summing it with the length of the array. For very negative indices,
|
||||
* the result may remain negative.
|
||||
* @internal
|
||||
*/
|
||||
private _adjustIndex(index: number): number {
|
||||
return index < 0 ? index + this.length : index;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _syncPendingControls(): boolean {
|
||||
let subtreeUpdated = this.controls.reduce((updated: boolean, child: AbstractControl) => {
|
||||
return child._syncPendingControls() ? true : updated;
|
||||
}, false);
|
||||
if (subtreeUpdated) this.updateValueAndValidity({onlySelf: true});
|
||||
return subtreeUpdated;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _forEachChild(cb: (c: AbstractControl, index: number) => void): void {
|
||||
this.controls.forEach((control: AbstractControl, index: number) => {
|
||||
cb(control, index);
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _updateValue(): void {
|
||||
(this as {value: any}).value =
|
||||
this.controls.filter((control) => control.enabled || this.disabled)
|
||||
.map((control) => control.value);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _anyControls(condition: (c: AbstractControl) => boolean): boolean {
|
||||
return this.controls.some((control: AbstractControl) => control.enabled && condition(control));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setUpControls(): void {
|
||||
this._forEachChild((control: AbstractControl) => this._registerControl(control));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _allControlsDisabled(): boolean {
|
||||
for (const control of this.controls) {
|
||||
if (control.enabled) return false;
|
||||
}
|
||||
return this.controls.length > 0 || this.disabled;
|
||||
}
|
||||
|
||||
private _registerControl(control: AbstractControl) {
|
||||
control.setParent(this);
|
||||
control._registerOnCollectionChange(this._onCollectionChange);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _find(name: string|number): AbstractControl|null {
|
||||
return this.at(name as number) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
interface UntypedFormArrayCtor {
|
||||
new(controls: AbstractControl[],
|
||||
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
|
||||
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): UntypedFormArray;
|
||||
|
||||
/**
|
||||
* The presence of an explicit `prototype` property provides backwards-compatibility for apps that
|
||||
* manually inspect the prototype chain.
|
||||
*/
|
||||
prototype: FormArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* UntypedFormArray is a non-strongly-typed version of @see FormArray.
|
||||
* Note: this is used for migration purposes only. Please avoid using it directly in your code and
|
||||
* prefer `FormControl` instead, unless you have been migrated to it automatically.
|
||||
*/
|
||||
export type UntypedFormArray = FormArray;
|
||||
|
||||
export const UntypedFormArray: UntypedFormArrayCtor = FormArray;
|
||||
|
||||
export const isFormArray = (control: unknown): control is FormArray => control instanceof FormArray;
|
||||
478
packages/forms/src/model/form_control.ts
Normal file
478
packages/forms/src/model/form_control.ts
Normal file
|
|
@ -0,0 +1,478 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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
|
||||
*/
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from '../directives/validators';
|
||||
import {removeListItem} from '../util';
|
||||
|
||||
import {AbstractControl, AbstractControlOptions, isOptionsObj, pickAsyncValidators, pickValidators} from './abstract_model';
|
||||
|
||||
/**
|
||||
* Interface for options provided to a {@link FormControl}.
|
||||
*
|
||||
* This interface extends all options from {@link AbstractControlOptions}, plus some options
|
||||
* unique to `FormControl`.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface FormControlOptions extends AbstractControlOptions {
|
||||
/**
|
||||
* @description
|
||||
* Whether to use the initial value used to construct the {@link FormControl} as its default value
|
||||
* as well. If this option is false or not provided, the default value of a FormControl is `null`.
|
||||
* When a FormControl is reset without an explicit value, its value reverts to
|
||||
* its default value.
|
||||
*/
|
||||
initialValueIsDefault?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the value and validation status of an individual form control.
|
||||
*
|
||||
* This is one of the three fundamental building blocks of Angular forms, along with
|
||||
* `FormGroup` and `FormArray`. It extends the `AbstractControl` class that
|
||||
* implements most of the base functionality for accessing the value, validation status,
|
||||
* user interactions and events. See [usage examples below](#usage-notes).
|
||||
*
|
||||
* @see `AbstractControl`
|
||||
* @see [Reactive Forms Guide](guide/reactive-forms)
|
||||
* @see [Usage Notes](#usage-notes)
|
||||
*
|
||||
* @publicApi
|
||||
*
|
||||
* @overriddenImplementation ɵFormControlCtor
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Initializing Form Controls
|
||||
*
|
||||
* Instantiate a `FormControl`, with an initial value.
|
||||
*
|
||||
* ```ts
|
||||
* const control = new FormControl('some value');
|
||||
* console.log(control.value); // 'some value'
|
||||
* ```
|
||||
*
|
||||
* The following example initializes the control with a form state object. The `value`
|
||||
* and `disabled` keys are required in this case.
|
||||
*
|
||||
* ```ts
|
||||
* const control = new FormControl({ value: 'n/a', disabled: true });
|
||||
* console.log(control.value); // 'n/a'
|
||||
* console.log(control.status); // 'DISABLED'
|
||||
* ```
|
||||
*
|
||||
* The following example initializes the control with a synchronous validator.
|
||||
*
|
||||
* ```ts
|
||||
* const control = new FormControl('', Validators.required);
|
||||
* console.log(control.value); // ''
|
||||
* console.log(control.status); // 'INVALID'
|
||||
* ```
|
||||
*
|
||||
* The following example initializes the control using an options object.
|
||||
*
|
||||
* ```ts
|
||||
* const control = new FormControl('', {
|
||||
* validators: Validators.required,
|
||||
* asyncValidators: myAsyncValidator
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ### Configure the control to update on a blur event
|
||||
*
|
||||
* Set the `updateOn` option to `'blur'` to update on the blur `event`.
|
||||
*
|
||||
* ```ts
|
||||
* const control = new FormControl('', { updateOn: 'blur' });
|
||||
* ```
|
||||
*
|
||||
* ### Configure the control to update on a submit event
|
||||
*
|
||||
* Set the `updateOn` option to `'submit'` to update on a submit `event`.
|
||||
*
|
||||
* ```ts
|
||||
* const control = new FormControl('', { updateOn: 'submit' });
|
||||
* ```
|
||||
*
|
||||
* ### Reset the control back to an initial value
|
||||
*
|
||||
* You 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).
|
||||
*
|
||||
* ```ts
|
||||
* const control = new FormControl('Nancy');
|
||||
*
|
||||
* console.log(control.value); // 'Nancy'
|
||||
*
|
||||
* control.reset('Drew');
|
||||
*
|
||||
* console.log(control.value); // 'Drew'
|
||||
* ```
|
||||
*
|
||||
* ### Reset the control back to an initial value and disabled
|
||||
*
|
||||
* ```
|
||||
* const control = new FormControl('Nancy');
|
||||
*
|
||||
* console.log(control.value); // 'Nancy'
|
||||
* console.log(control.status); // 'VALID'
|
||||
*
|
||||
* control.reset({ value: 'Drew', disabled: true });
|
||||
*
|
||||
* console.log(control.value); // 'Drew'
|
||||
* console.log(control.status); // 'DISABLED'
|
||||
* ```
|
||||
*/
|
||||
export interface FormControl extends AbstractControl {
|
||||
/**
|
||||
* The default value of this FormControl, used whenever the control is reset without an explicit
|
||||
* value. See {@link FormControlOptions#initialValueIsDefault} for more information on configuring
|
||||
* a default value.
|
||||
*/
|
||||
readonly defaultValue: any;
|
||||
|
||||
/** @internal */
|
||||
_onChange: Function[];
|
||||
|
||||
/**
|
||||
* This field holds a pending value that has not yet been applied to the form's value.
|
||||
* It is `any` because the value is untyped.
|
||||
* @internal
|
||||
*/
|
||||
_pendingValue: any;
|
||||
|
||||
/** @internal */
|
||||
_pendingChange: boolean;
|
||||
|
||||
/**
|
||||
* Sets a new value for the form control.
|
||||
*
|
||||
* @param value The new value for the control.
|
||||
* @param options Configuration options that determine how the control propagates changes
|
||||
* and emits events when the value changes.
|
||||
* The configuration options are passed to the {@link AbstractControl#updateValueAndValidity
|
||||
* updateValueAndValidity} method.
|
||||
*
|
||||
* * `onlySelf`: When true, each change only affects this control, and not its parent. Default is
|
||||
* false.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges`
|
||||
* observables emit events with the latest status and value when the control value is updated.
|
||||
* When false, no events are emitted.
|
||||
* * `emitModelToViewChange`: When true or not supplied (the default), each change triggers an
|
||||
* `onChange` event to
|
||||
* update the view.
|
||||
* * `emitViewToModelChange`: When true or not supplied (the default), each change triggers an
|
||||
* `ngModelChange`
|
||||
* event to update the model.
|
||||
*
|
||||
*/
|
||||
setValue(value: any, options?: {
|
||||
onlySelf?: boolean,
|
||||
emitEvent?: boolean,
|
||||
emitModelToViewChange?: boolean,
|
||||
emitViewToModelChange?: boolean
|
||||
}): void;
|
||||
|
||||
/**
|
||||
* Patches the value of a control.
|
||||
*
|
||||
* This function is functionally the same as {@link FormControl#setValue setValue} at this level.
|
||||
* It exists for symmetry with {@link FormGroup#patchValue patchValue} on `FormGroups` and
|
||||
* `FormArrays`, where it does behave differently.
|
||||
*
|
||||
* @see `setValue` for options
|
||||
*/
|
||||
patchValue(value: any, options?: {
|
||||
onlySelf?: boolean,
|
||||
emitEvent?: boolean,
|
||||
emitModelToViewChange?: boolean,
|
||||
emitViewToModelChange?: boolean
|
||||
}): void;
|
||||
|
||||
/**
|
||||
* Resets the form control, marking it `pristine` and `untouched`, and resetting
|
||||
* the value. The new value will be the provided value (if passed), `null`, or the initial value
|
||||
* if `initialValueIsDefault` was set in the constructor via {@link FormControlOptions}.
|
||||
*
|
||||
* ```ts
|
||||
* // By default, the control will reset to null.
|
||||
* const dog = new FormControl('spot');
|
||||
* dog.reset(); // dog.value is null
|
||||
*
|
||||
* // If this flag is set, the control will instead reset to the initial value.
|
||||
* const cat = new FormControl('tabby', {initialValueIsDefault: true});
|
||||
* cat.reset(); // cat.value is "tabby"
|
||||
*
|
||||
* // A value passed to reset always takes precedence.
|
||||
* const fish = new FormControl('finn', {initialValueIsDefault: true});
|
||||
* fish.reset('bubble'); // fish.value is "bubble"
|
||||
* ```
|
||||
*
|
||||
* @param formState Resets the control with an initial value,
|
||||
* or an object that defines the initial value and disabled state.
|
||||
*
|
||||
* @param options Configuration options that determine how the control propagates changes
|
||||
* and emits events after the value changes.
|
||||
*
|
||||
* * `onlySelf`: When true, each change only affects this control, and not its parent. Default is
|
||||
* false.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges`
|
||||
* observables emit events with the latest status and value when the control is reset.
|
||||
* When false, no events are emitted.
|
||||
*
|
||||
*/
|
||||
reset(formState?: any, options?: {onlySelf?: boolean, emitEvent?: boolean}): void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_updateValue(): void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_anyControls(condition: (c: AbstractControl) => boolean): boolean;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_allControlsDisabled(): boolean;
|
||||
|
||||
|
||||
/**
|
||||
* Register a listener for change events.
|
||||
*
|
||||
* @param fn The method that is called when the value changes
|
||||
*/
|
||||
registerOnChange(fn: Function): void;
|
||||
|
||||
|
||||
/**
|
||||
* Internal function to unregister a change events listener.
|
||||
* @internal
|
||||
*/
|
||||
_unregisterOnChange(fn: (value?: any, emitModelEvent?: boolean) => void): void;
|
||||
|
||||
/**
|
||||
* Register a listener for disabled events.
|
||||
*
|
||||
* @param fn The method that is called when the disabled status changes.
|
||||
*/
|
||||
registerOnDisabledChange(fn: (isDisabled: boolean) => void): void;
|
||||
|
||||
/**
|
||||
* Internal function to unregister a disabled event listener.
|
||||
* @internal
|
||||
*/
|
||||
_unregisterOnDisabledChange(fn: (isDisabled: boolean) => void): void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_forEachChild(cb: (c: AbstractControl) => void): void;
|
||||
|
||||
/** @internal */
|
||||
_syncPendingControls(): boolean;
|
||||
}
|
||||
|
||||
type FormControlInterface = FormControl;
|
||||
|
||||
/**
|
||||
* Various available constructors for `FormControl`.
|
||||
* Do not use this interface directly. Instead, use `FormControl`:
|
||||
* ```
|
||||
* const fc = new FormControl('foo');
|
||||
* ```
|
||||
* This symbol is prefixed with ɵ to make plain that it is an internal symbol.
|
||||
*/
|
||||
export interface ɵFormControlCtor {
|
||||
/**
|
||||
* Construct a FormControl with no initial value or validators.
|
||||
*/
|
||||
new(): FormControl;
|
||||
|
||||
/**
|
||||
* Creates a new `FormControl` instance.
|
||||
*
|
||||
* @param formState Initializes the control with an initial value,
|
||||
* or an object that defines the initial value and disabled state.
|
||||
*
|
||||
* @param validatorOrOpts A synchronous validator function, or an array of
|
||||
* such functions, or a `FormControlOptions` object that contains validation functions
|
||||
* and a validation trigger.
|
||||
*
|
||||
* @param asyncValidator A single async validator or array of async validator functions.
|
||||
*/
|
||||
new(formState: any, validatorOrOpts?: ValidatorFn|ValidatorFn[]|FormControlOptions|null,
|
||||
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): FormControl;
|
||||
|
||||
/**
|
||||
* The presence of an explicit `prototype` property provides backwards-compatibility for apps that
|
||||
* manually inspect the prototype chain.
|
||||
*/
|
||||
prototype: FormControl;
|
||||
}
|
||||
|
||||
export const FormControl: ɵFormControlCtor =
|
||||
(class FormControl extends AbstractControl implements FormControlInterface {
|
||||
/** @publicApi */
|
||||
public readonly defaultValue: any = null;
|
||||
|
||||
/** @internal */
|
||||
_onChange: Function[] = [];
|
||||
|
||||
/** @internal */
|
||||
_pendingValue: any;
|
||||
|
||||
/** @internal */
|
||||
_pendingChange: boolean = false;
|
||||
|
||||
constructor(
|
||||
formState: any = null,
|
||||
validatorOrOpts?: ValidatorFn|ValidatorFn[]|FormControlOptions|null,
|
||||
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null) {
|
||||
super(
|
||||
pickValidators(validatorOrOpts), pickAsyncValidators(asyncValidator, validatorOrOpts));
|
||||
this._applyFormState(formState);
|
||||
this._setUpdateStrategy(validatorOrOpts);
|
||||
this._initObservables();
|
||||
this.updateValueAndValidity({
|
||||
onlySelf: true,
|
||||
// If `asyncValidator` is present, it will trigger control status change from `PENDING` to
|
||||
// `VALID` or `INVALID`.
|
||||
// The status should be broadcasted via the `statusChanges` observable, so we set
|
||||
// `emitEvent` to `true` to allow that during the control creation process.
|
||||
emitEvent: !!this.asyncValidator
|
||||
});
|
||||
if (isOptionsObj(validatorOrOpts) && validatorOrOpts.initialValueIsDefault) {
|
||||
if (this._isBoxedValue(formState)) {
|
||||
(this.defaultValue as any) = formState.value;
|
||||
} else {
|
||||
(this.defaultValue as any) = formState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override setValue(value: any, options: {
|
||||
onlySelf?: boolean,
|
||||
emitEvent?: boolean,
|
||||
emitModelToViewChange?: boolean,
|
||||
emitViewToModelChange?: boolean
|
||||
} = {}): void {
|
||||
(this as {value: any}).value = this._pendingValue = value;
|
||||
if (this._onChange.length && options.emitModelToViewChange !== false) {
|
||||
this._onChange.forEach(
|
||||
(changeFn) => changeFn(this.value, options.emitViewToModelChange !== false));
|
||||
}
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
override patchValue(value: any, options: {
|
||||
onlySelf?: boolean,
|
||||
emitEvent?: boolean,
|
||||
emitModelToViewChange?: boolean,
|
||||
emitViewToModelChange?: boolean
|
||||
} = {}): void {
|
||||
this.setValue(value, options);
|
||||
}
|
||||
|
||||
override reset(
|
||||
formState: any = this.defaultValue,
|
||||
options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
this._applyFormState(formState);
|
||||
this.markAsPristine(options);
|
||||
this.markAsUntouched(options);
|
||||
this.setValue(this.value, options);
|
||||
this._pendingChange = false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _updateValue(): void {}
|
||||
|
||||
/** @internal */
|
||||
override _anyControls(condition: (c: AbstractControl) => boolean): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _allControlsDisabled(): boolean {
|
||||
return this.disabled;
|
||||
}
|
||||
|
||||
registerOnChange(fn: Function): void {
|
||||
this._onChange.push(fn);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_unregisterOnChange(fn: (value?: any, emitModelEvent?: boolean) => void): void {
|
||||
removeListItem(this._onChange, fn);
|
||||
}
|
||||
|
||||
registerOnDisabledChange(fn: (isDisabled: boolean) => void): void {
|
||||
this._onDisabledChange.push(fn);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_unregisterOnDisabledChange(fn: (isDisabled: boolean) => void): void {
|
||||
removeListItem(this._onDisabledChange, fn);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _forEachChild(cb: (c: AbstractControl) => void): void {}
|
||||
|
||||
/** @internal */
|
||||
override _syncPendingControls(): boolean {
|
||||
if (this.updateOn === 'submit') {
|
||||
if (this._pendingDirty) this.markAsDirty();
|
||||
if (this._pendingTouched) this.markAsTouched();
|
||||
if (this._pendingChange) {
|
||||
this.setValue(this._pendingValue, {onlySelf: true, emitModelToViewChange: false});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _applyFormState(formState: any) {
|
||||
if (this._isBoxedValue(formState)) {
|
||||
(this as {value: any}).value = this._pendingValue = formState.value;
|
||||
formState.disabled ? this.disable({onlySelf: true, emitEvent: false}) :
|
||||
this.enable({onlySelf: true, emitEvent: false});
|
||||
} else {
|
||||
(this as {value: any}).value = this._pendingValue = formState;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
interface UntypedFormControlCtor {
|
||||
new(): UntypedFormControl;
|
||||
|
||||
new(formState?: any, validatorOrOpts?: ValidatorFn|ValidatorFn[]|FormControlOptions|null,
|
||||
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): UntypedFormControl;
|
||||
|
||||
/**
|
||||
* The presence of an explicit `prototype` property provides backwards-compatibility for apps that
|
||||
* manually inspect the prototype chain.
|
||||
*/
|
||||
prototype: FormControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* UntypedFormControl is a non-strongly-typed version of @see FormControl.
|
||||
* Note: this is used for migration purposes only. Please avoid using it directly in your code and
|
||||
* prefer `FormControl` instead, unless you have been migrated to it automatically.
|
||||
*/
|
||||
export type UntypedFormControl = FormControl;
|
||||
|
||||
export const UntypedFormControl: UntypedFormControlCtor = FormControl;
|
||||
|
||||
export const isFormControl = (control: unknown): control is FormControl =>
|
||||
control instanceof FormControl;
|
||||
482
packages/forms/src/model/form_group.ts
Normal file
482
packages/forms/src/model/form_group.ts
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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
|
||||
*/
|
||||
|
||||
import {AsyncValidatorFn, ValidatorFn} from '../directives/validators';
|
||||
|
||||
import {AbstractControl, AbstractControlOptions, assertAllValuesPresent, assertControlPresent, pickAsyncValidators, pickValidators} from './abstract_model';
|
||||
|
||||
/**
|
||||
* Tracks the value and validity state of a group of `FormControl` instances.
|
||||
*
|
||||
* A `FormGroup` aggregates the values of each child `FormControl` into one object,
|
||||
* with each control name as the key. It calculates its status by reducing the status values
|
||||
* of its children. For example, if one of the controls in a group is invalid, the entire
|
||||
* group becomes invalid.
|
||||
*
|
||||
* `FormGroup` is one of the three fundamental building blocks used to define forms in Angular,
|
||||
* along with `FormControl` and `FormArray`.
|
||||
*
|
||||
* When instantiating a `FormGroup`, pass in a collection of child controls as the first
|
||||
* argument. The key for each child registers the name for the control.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Create a form group with 2 controls
|
||||
*
|
||||
* ```
|
||||
* 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'
|
||||
* ```
|
||||
*
|
||||
* ### Create a form group with a group-level validator
|
||||
*
|
||||
* You 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.
|
||||
*
|
||||
* ```
|
||||
* const form = new FormGroup({
|
||||
* password: new FormControl('', Validators.minLength(2)),
|
||||
* passwordConfirm: new FormControl('', Validators.minLength(2)),
|
||||
* }, passwordMatchValidator);
|
||||
*
|
||||
*
|
||||
* function passwordMatchValidator(g: FormGroup) {
|
||||
* return g.get('password').value === g.get('passwordConfirm').value
|
||||
* ? null : {'mismatch': true};
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Like `FormControl` instances, you choose to pass in
|
||||
* validators and async validators as part of an options object.
|
||||
*
|
||||
* ```
|
||||
* const form = new FormGroup({
|
||||
* password: new FormControl('')
|
||||
* passwordConfirm: new FormControl('')
|
||||
* }, { validators: passwordMatchValidator, asyncValidators: otherValidator });
|
||||
* ```
|
||||
*
|
||||
* ### Set the updateOn property for all controls in a form group
|
||||
*
|
||||
* 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
|
||||
* group level, all child controls default to 'blur', unless the child
|
||||
* has explicitly specified a different `updateOn` value.
|
||||
*
|
||||
* ```ts
|
||||
* const c = new FormGroup({
|
||||
* one: new FormControl()
|
||||
* }, { updateOn: 'blur' });
|
||||
* ```
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export class FormGroup extends AbstractControl {
|
||||
/**
|
||||
* Creates a new `FormGroup` instance.
|
||||
*
|
||||
* @param controls A collection of child controls. The key for each child is the name
|
||||
* under which it is registered.
|
||||
*
|
||||
* @param validatorOrOpts A synchronous validator function, or an array of
|
||||
* such functions, or an `AbstractControlOptions` object that contains validation functions
|
||||
* and a validation trigger.
|
||||
*
|
||||
* @param asyncValidator A single async validator or array of async validator functions
|
||||
*
|
||||
*/
|
||||
constructor(
|
||||
public controls: {[key: string]: AbstractControl},
|
||||
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
|
||||
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null) {
|
||||
super(pickValidators(validatorOrOpts), pickAsyncValidators(asyncValidator, validatorOrOpts));
|
||||
this._initObservables();
|
||||
this._setUpdateStrategy(validatorOrOpts);
|
||||
this._setUpControls();
|
||||
this.updateValueAndValidity({
|
||||
onlySelf: true,
|
||||
// If `asyncValidator` is present, it will trigger control status change from `PENDING` to
|
||||
// `VALID` or `INVALID`. The status should be broadcasted via the `statusChanges` observable,
|
||||
// so we set `emitEvent` to `true` to allow that during the control creation process.
|
||||
emitEvent: !!this.asyncValidator
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a control with the group's list of controls.
|
||||
*
|
||||
* This method does not update the value or validity of the control.
|
||||
* Use {@link FormGroup#addControl addControl} instead.
|
||||
*
|
||||
* @param name The control name to register in the collection
|
||||
* @param control Provides the control for the given name
|
||||
*/
|
||||
registerControl(name: string, control: AbstractControl): AbstractControl {
|
||||
if (this.controls[name]) return this.controls[name];
|
||||
this.controls[name] = control;
|
||||
control.setParent(this);
|
||||
control._registerOnCollectionChange(this._onCollectionChange);
|
||||
return control;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a control to this group.
|
||||
*
|
||||
* If a control with a given name already exists, it would *not* be replaced with a new one.
|
||||
* If you want to replace an existing control, use the {@link FormGroup#setControl setControl}
|
||||
* method instead. This method also updates the value and validity of the control.
|
||||
*
|
||||
* @param name The control name to add to the collection
|
||||
* @param control Provides the control for the given name
|
||||
* @param options Specifies whether this FormGroup instance should emit events after a new
|
||||
* control is added.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges` observables emit events with the latest status and value when the control is
|
||||
* added. When false, no events are emitted.
|
||||
*/
|
||||
addControl(name: string, control: AbstractControl, options: {emitEvent?: boolean} = {}): void {
|
||||
this.registerControl(name, control);
|
||||
this.updateValueAndValidity({emitEvent: options.emitEvent});
|
||||
this._onCollectionChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a control from this group.
|
||||
*
|
||||
* This method also updates the value and validity of the control.
|
||||
*
|
||||
* @param name The control name to remove from the collection
|
||||
* @param options Specifies whether this FormGroup instance should emit events after a
|
||||
* control is removed.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges` observables emit events with the latest status and value when the control is
|
||||
* removed. When false, no events are emitted.
|
||||
*/
|
||||
removeControl(name: string, options: {emitEvent?: boolean} = {}): void {
|
||||
if (this.controls[name]) this.controls[name]._registerOnCollectionChange(() => {});
|
||||
delete (this.controls[name]);
|
||||
this.updateValueAndValidity({emitEvent: options.emitEvent});
|
||||
this._onCollectionChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace an existing control.
|
||||
*
|
||||
* If a control with a given name does not exist in this `FormGroup`, it will be added.
|
||||
*
|
||||
* @param name The control name to replace in the collection
|
||||
* @param control Provides the control for the given name
|
||||
* @param options Specifies whether this FormGroup instance should emit events after an
|
||||
* existing control is replaced.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges` observables emit events with the latest status and value when the control is
|
||||
* replaced with a new one. When false, no events are emitted.
|
||||
*/
|
||||
setControl(name: string, control: AbstractControl, options: {emitEvent?: boolean} = {}): void {
|
||||
if (this.controls[name]) this.controls[name]._registerOnCollectionChange(() => {});
|
||||
delete (this.controls[name]);
|
||||
if (control) this.registerControl(name, control);
|
||||
this.updateValueAndValidity({emitEvent: options.emitEvent});
|
||||
this._onCollectionChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether there is an enabled control with the given name in the group.
|
||||
*
|
||||
* Reports false for disabled controls. If you'd like to check for existence in the group
|
||||
* only, use {@link AbstractControl#get get} instead.
|
||||
*
|
||||
* @param controlName The control name to check for existence in the collection
|
||||
*
|
||||
* @returns false for disabled controls, true otherwise.
|
||||
*/
|
||||
contains(controlName: string): boolean {
|
||||
return this.controls.hasOwnProperty(controlName) && this.controls[controlName].enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the `FormGroup`. It accepts an object that matches
|
||||
* the structure of the group, with control names as keys.
|
||||
*
|
||||
* @usageNotes
|
||||
* ### Set the complete value for the form group
|
||||
*
|
||||
* ```
|
||||
* 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'}
|
||||
* ```
|
||||
*
|
||||
* @throws When strict checks fail, such as setting the value of a control
|
||||
* that doesn't exist or if you exclude a value of a control that does exist.
|
||||
*
|
||||
* @param value The new value for the control that matches the structure of the group.
|
||||
* @param options Configuration options that determine how the control propagates changes
|
||||
* and emits events after the value changes.
|
||||
* The configuration options are passed to the {@link AbstractControl#updateValueAndValidity
|
||||
* updateValueAndValidity} method.
|
||||
*
|
||||
* * `onlySelf`: When true, each change only affects this control, and not its parent. Default is
|
||||
* false.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges`
|
||||
* observables emit events with the latest status and value when the control value is updated.
|
||||
* When false, no events are emitted.
|
||||
*/
|
||||
override setValue(
|
||||
value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
assertAllValuesPresent(this, true, value);
|
||||
Object.keys(value).forEach(name => {
|
||||
assertControlPresent(this, true, name);
|
||||
this.controls[name].setValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});
|
||||
});
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches the value of the `FormGroup`. It accepts an object with control
|
||||
* names as keys, and does 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.
|
||||
*
|
||||
* @usageNotes
|
||||
* ### Patch the value for a form group
|
||||
*
|
||||
* ```
|
||||
* 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}
|
||||
* ```
|
||||
*
|
||||
* @param value The object that matches the structure of the group.
|
||||
* @param options Configuration options that determine how the control propagates changes and
|
||||
* emits events after the value is patched.
|
||||
* * `onlySelf`: When true, each change only affects this control and not its parent. Default is
|
||||
* true.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges` observables emit events with the latest status and value when the control value
|
||||
* is updated. When false, no events are emitted. The configuration options are passed to
|
||||
* the {@link AbstractControl#updateValueAndValidity updateValueAndValidity} method.
|
||||
*/
|
||||
override patchValue(
|
||||
value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
// Even though the `value` argument type doesn't allow `null` and `undefined` values, the
|
||||
// `patchValue` can be called recursively and inner data structures might have these values, so
|
||||
// we just ignore such cases when a field containing FormGroup instance receives `null` or
|
||||
// `undefined` as a value.
|
||||
if (value == null /* both `null` and `undefined` */) return;
|
||||
|
||||
Object.keys(value).forEach(name => {
|
||||
if (this.controls[name]) {
|
||||
this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});
|
||||
}
|
||||
});
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the `FormGroup`, marks all descendants `pristine` and `untouched` and sets
|
||||
* the value of all descendants to null.
|
||||
*
|
||||
* You 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
|
||||
* is a standalone value or a form state object with both a value and a disabled
|
||||
* status.
|
||||
*
|
||||
* @param value Resets the control with an initial value,
|
||||
* or an object that defines the initial value and disabled state.
|
||||
*
|
||||
* @param options Configuration options that determine how the control propagates changes
|
||||
* and emits events when the group is reset.
|
||||
* * `onlySelf`: When true, each change only affects this control, and not its parent. Default is
|
||||
* false.
|
||||
* * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and
|
||||
* `valueChanges`
|
||||
* observables emit events with the latest status and value when the control is reset.
|
||||
* When false, no events are emitted.
|
||||
* The configuration options are passed to the {@link AbstractControl#updateValueAndValidity
|
||||
* updateValueAndValidity} method.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Reset the form group values
|
||||
*
|
||||
* ```ts
|
||||
* const form = new FormGroup({
|
||||
* first: new FormControl('first name'),
|
||||
* last: new FormControl('last name')
|
||||
* });
|
||||
*
|
||||
* console.log(form.value); // {first: 'first name', last: 'last name'}
|
||||
*
|
||||
* form.reset({ first: 'name', last: 'last name' });
|
||||
*
|
||||
* console.log(form.value); // {first: 'name', last: 'last name'}
|
||||
* ```
|
||||
*
|
||||
* ### Reset the form group values and disabled status
|
||||
*
|
||||
* ```
|
||||
* const form = new FormGroup({
|
||||
* first: new FormControl('first name'),
|
||||
* last: new FormControl('last name')
|
||||
* });
|
||||
*
|
||||
* form.reset({
|
||||
* first: {value: 'name', disabled: true},
|
||||
* last: 'last'
|
||||
* });
|
||||
*
|
||||
* console.log(form.value); // {last: 'last'}
|
||||
* console.log(form.get('first').status); // 'DISABLED'
|
||||
* ```
|
||||
*/
|
||||
override reset(value: any = {}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
this._forEachChild((control: AbstractControl, name: string) => {
|
||||
control.reset(value[name], {onlySelf: true, emitEvent: options.emitEvent});
|
||||
});
|
||||
this._updatePristine(options);
|
||||
this._updateTouched(options);
|
||||
this.updateValueAndValidity(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* The aggregate value of the `FormGroup`, including any disabled controls.
|
||||
*
|
||||
* Retrieves all values regardless of disabled status.
|
||||
* The `value` property is the best way to get the value of the group, because
|
||||
* it excludes disabled controls in the `FormGroup`.
|
||||
*/
|
||||
override getRawValue(): any {
|
||||
return this._reduceChildren(
|
||||
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
|
||||
acc[name] = control.getRawValue();
|
||||
return acc;
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _syncPendingControls(): boolean {
|
||||
let subtreeUpdated = this._reduceChildren(false, (updated: boolean, child: AbstractControl) => {
|
||||
return child._syncPendingControls() ? true : updated;
|
||||
});
|
||||
if (subtreeUpdated) this.updateValueAndValidity({onlySelf: true});
|
||||
return subtreeUpdated;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _forEachChild(cb: (v: any, k: string) => void): void {
|
||||
Object.keys(this.controls).forEach(key => {
|
||||
// The list of controls can change (for ex. controls might be removed) while the loop
|
||||
// is running (as a result of invoking Forms API in `valueChanges` subscription), so we
|
||||
// have to null check before invoking the callback.
|
||||
const control = this.controls[key];
|
||||
control && cb(control, key);
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_setUpControls(): void {
|
||||
this._forEachChild((control: AbstractControl) => {
|
||||
control.setParent(this);
|
||||
control._registerOnCollectionChange(this._onCollectionChange);
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _updateValue(): void {
|
||||
(this as {value: any}).value = this._reduceValue();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _anyControls(condition: (c: AbstractControl) => boolean): boolean {
|
||||
for (const controlName of Object.keys(this.controls)) {
|
||||
const control = this.controls[controlName];
|
||||
if (this.contains(controlName) && condition(control)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceValue() {
|
||||
return this._reduceChildren(
|
||||
{}, (acc: {[k: string]: any}, control: AbstractControl, name: string): any => {
|
||||
if (control.enabled || this.disabled) {
|
||||
acc[name] = control.value;
|
||||
}
|
||||
return acc;
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reduceChildren<T>(initValue: T, fn: (acc: T, control: AbstractControl, name: string) => T): T {
|
||||
let res = initValue;
|
||||
this._forEachChild((control: AbstractControl, name: string) => {
|
||||
res = fn(res, control, name);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _allControlsDisabled(): boolean {
|
||||
for (const controlName of Object.keys(this.controls)) {
|
||||
if (this.controls[controlName].enabled) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return Object.keys(this.controls).length > 0 || this.disabled;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override _find(name: string|number): AbstractControl|null {
|
||||
return this.controls.hasOwnProperty(name as string) ? this.controls[name as string] : null;
|
||||
}
|
||||
}
|
||||
|
||||
interface UntypedFormGroupCtor {
|
||||
new(controls: {[key: string]: AbstractControl},
|
||||
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
|
||||
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null): UntypedFormGroup;
|
||||
|
||||
/**
|
||||
* The presence of an explicit `prototype` property provides backwards-compatibility for apps that
|
||||
* manually inspect the prototype chain.
|
||||
*/
|
||||
prototype: FormGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* UntypedFormGroup is a non-strongly-typed version of @see FormGroup.
|
||||
* Note: this is used for migration purposes only. Please avoid using it directly in your code and
|
||||
* prefer `FormControl` instead, unless you have been migrated to it automatically.
|
||||
*/
|
||||
export type UntypedFormGroup = FormGroup;
|
||||
|
||||
export const UntypedFormGroup: UntypedFormGroupCtor = FormGroup;
|
||||
|
||||
export const isFormGroup = (control: unknown): control is FormGroup => control instanceof FormGroup;
|
||||
12
packages/forms/src/util.ts
Normal file
12
packages/forms/src/util.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC 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
|
||||
*/
|
||||
|
||||
export function removeListItem<T>(list: T[], el: T): void {
|
||||
const index = list.indexOf(el);
|
||||
if (index > -1) list.splice(index, 1);
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import {forkJoin, from, Observable} from 'rxjs';
|
|||
import {map} from 'rxjs/operators';
|
||||
|
||||
import {AsyncValidator, AsyncValidatorFn, ValidationErrors, Validator, ValidatorFn} from './directives/validators';
|
||||
import {AbstractControl} from './model';
|
||||
import {AbstractControl} from './model/abstract_model';
|
||||
|
||||
function isEmptyInputValue(value: any): boolean {
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue