@angular/forms
Version:
Angular - directives and services for creating forms
1,672 lines (1,632 loc) • 149 kB
JavaScript
/**
* @license Angular v21.0.6
* (c) 2010-2025 Google LLC. https://angular.dev/
* License: MIT
*/
import * as i0 from '@angular/core';
import { Directive, InjectionToken, forwardRef, Optional, Inject, ɵisPromise as _isPromise, ɵisSubscribable as _isSubscribable, ɵRuntimeError as _RuntimeError, Self, untracked, computed, signal, EventEmitter, Input, Host, SkipSelf, booleanAttribute, ChangeDetectorRef, Output, Injectable, inject, ApplicationRef, DestroyRef, afterNextRender, NgModule, Version } from '@angular/core';
import { ɵgetDOM as _getDOM } from '@angular/common';
import { forkJoin, from, Subject } from 'rxjs';
import { map } from 'rxjs/operators';
class BaseControlValueAccessor {
_renderer;
_elementRef;
onChange = _ => {};
onTouched = () => {};
constructor(_renderer, _elementRef) {
this._renderer = _renderer;
this._elementRef = _elementRef;
}
setProperty(key, value) {
this._renderer.setProperty(this._elementRef.nativeElement, key, value);
}
registerOnTouched(fn) {
this.onTouched = fn;
}
registerOnChange(fn) {
this.onChange = fn;
}
setDisabledState(isDisabled) {
this.setProperty('disabled', isDisabled);
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: BaseControlValueAccessor,
deps: [{
token: i0.Renderer2
}, {
token: i0.ElementRef
}],
target: i0.ɵɵFactoryTarget.Directive
});
static ɵdir = i0.ɵɵngDeclareDirective({
minVersion: "14.0.0",
version: "21.0.6",
type: BaseControlValueAccessor,
isStandalone: true,
ngImport: i0
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: BaseControlValueAccessor,
decorators: [{
type: Directive
}],
ctorParameters: () => [{
type: i0.Renderer2
}, {
type: i0.ElementRef
}]
});
class BuiltInControlValueAccessor extends BaseControlValueAccessor {
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: BuiltInControlValueAccessor,
deps: null,
target: i0.ɵɵFactoryTarget.Directive
});
static ɵdir = i0.ɵɵngDeclareDirective({
minVersion: "14.0.0",
version: "21.0.6",
type: BuiltInControlValueAccessor,
isStandalone: true,
usesInheritance: true,
ngImport: i0
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: BuiltInControlValueAccessor,
decorators: [{
type: Directive
}]
});
const NG_VALUE_ACCESSOR = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'NgValueAccessor' : '');
const CHECKBOX_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckboxControlValueAccessor),
multi: true
};
class CheckboxControlValueAccessor extends BuiltInControlValueAccessor {
writeValue(value) {
this.setProperty('checked', value);
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: CheckboxControlValueAccessor,
deps: null,
target: i0.ɵɵFactoryTarget.Directive
});
static ɵdir = i0.ɵɵngDeclareDirective({
minVersion: "14.0.0",
version: "21.0.6",
type: CheckboxControlValueAccessor,
isStandalone: false,
selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]",
host: {
listeners: {
"change": "onChange($any($event.target).checked)",
"blur": "onTouched()"
}
},
providers: [CHECKBOX_VALUE_ACCESSOR],
usesInheritance: true,
ngImport: i0
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: CheckboxControlValueAccessor,
decorators: [{
type: Directive,
args: [{
selector: 'input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]',
host: {
'(change)': 'onChange($any($event.target).checked)',
'(blur)': 'onTouched()'
},
providers: [CHECKBOX_VALUE_ACCESSOR],
standalone: false
}]
}]
});
const DEFAULT_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true
};
function _isAndroid() {
const userAgent = _getDOM() ? _getDOM().getUserAgent() : '';
return /android (\d+)/.test(userAgent.toLowerCase());
}
const COMPOSITION_BUFFER_MODE = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'CompositionEventMode' : '');
class DefaultValueAccessor extends BaseControlValueAccessor {
_compositionMode;
_composing = false;
constructor(renderer, elementRef, _compositionMode) {
super(renderer, elementRef);
this._compositionMode = _compositionMode;
if (this._compositionMode == null) {
this._compositionMode = !_isAndroid();
}
}
writeValue(value) {
const normalizedValue = value == null ? '' : value;
this.setProperty('value', normalizedValue);
}
_handleInput(value) {
if (!this._compositionMode || this._compositionMode && !this._composing) {
this.onChange(value);
}
}
_compositionStart() {
this._composing = true;
}
_compositionEnd(value) {
this._composing = false;
this._compositionMode && this.onChange(value);
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: DefaultValueAccessor,
deps: [{
token: i0.Renderer2
}, {
token: i0.ElementRef
}, {
token: COMPOSITION_BUFFER_MODE,
optional: true
}],
target: i0.ɵɵFactoryTarget.Directive
});
static ɵdir = i0.ɵɵngDeclareDirective({
minVersion: "14.0.0",
version: "21.0.6",
type: DefaultValueAccessor,
isStandalone: false,
selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]",
host: {
listeners: {
"input": "_handleInput($any($event.target).value)",
"blur": "onTouched()",
"compositionstart": "_compositionStart()",
"compositionend": "_compositionEnd($any($event.target).value)"
}
},
providers: [DEFAULT_VALUE_ACCESSOR],
usesInheritance: true,
ngImport: i0
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: DefaultValueAccessor,
decorators: [{
type: Directive,
args: [{
selector: 'input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]',
host: {
'(input)': '_handleInput($any($event.target).value)',
'(blur)': 'onTouched()',
'(compositionstart)': '_compositionStart()',
'(compositionend)': '_compositionEnd($any($event.target).value)'
},
providers: [DEFAULT_VALUE_ACCESSOR],
standalone: false
}]
}],
ctorParameters: () => [{
type: i0.Renderer2
}, {
type: i0.ElementRef
}, {
type: undefined,
decorators: [{
type: Optional
}, {
type: Inject,
args: [COMPOSITION_BUFFER_MODE]
}]
}]
});
function isEmptyInputValue(value) {
return value == null || lengthOrSize(value) === 0;
}
function lengthOrSize(value) {
if (value == null) {
return null;
} else if (Array.isArray(value) || typeof value === 'string') {
return value.length;
} else if (value instanceof Set) {
return value.size;
}
return null;
}
const NG_VALIDATORS = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'NgValidators' : '');
const NG_ASYNC_VALIDATORS = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'NgAsyncValidators' : '');
const EMAIL_REGEXP = /^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
class Validators {
static min(min) {
return minValidator(min);
}
static max(max) {
return maxValidator(max);
}
static required(control) {
return requiredValidator(control);
}
static requiredTrue(control) {
return requiredTrueValidator(control);
}
static email(control) {
return emailValidator(control);
}
static minLength(minLength) {
return minLengthValidator(minLength);
}
static maxLength(maxLength) {
return maxLengthValidator(maxLength);
}
static pattern(pattern) {
return patternValidator(pattern);
}
static nullValidator(control) {
return nullValidator();
}
static compose(validators) {
return compose(validators);
}
static composeAsync(validators) {
return composeAsync(validators);
}
}
function minValidator(min) {
return control => {
if (control.value == null || min == null) {
return null;
}
const value = parseFloat(control.value);
return !isNaN(value) && value < min ? {
'min': {
'min': min,
'actual': control.value
}
} : null;
};
}
function maxValidator(max) {
return control => {
if (control.value == null || max == null) {
return null;
}
const value = parseFloat(control.value);
return !isNaN(value) && value > max ? {
'max': {
'max': max,
'actual': control.value
}
} : null;
};
}
function requiredValidator(control) {
return isEmptyInputValue(control.value) ? {
'required': true
} : null;
}
function requiredTrueValidator(control) {
return control.value === true ? null : {
'required': true
};
}
function emailValidator(control) {
if (isEmptyInputValue(control.value)) {
return null;
}
return EMAIL_REGEXP.test(control.value) ? null : {
'email': true
};
}
function minLengthValidator(minLength) {
return control => {
const length = control.value?.length ?? lengthOrSize(control.value);
if (length === null || length === 0) {
return null;
}
return length < minLength ? {
'minlength': {
'requiredLength': minLength,
'actualLength': length
}
} : null;
};
}
function maxLengthValidator(maxLength) {
return control => {
const length = control.value?.length ?? lengthOrSize(control.value);
if (length !== null && length > maxLength) {
return {
'maxlength': {
'requiredLength': maxLength,
'actualLength': length
}
};
}
return null;
};
}
function patternValidator(pattern) {
if (!pattern) return nullValidator;
let regex;
let regexStr;
if (typeof pattern === 'string') {
regexStr = '';
if (pattern.charAt(0) !== '^') regexStr += '^';
regexStr += pattern;
if (pattern.charAt(pattern.length - 1) !== '$') regexStr += '$';
regex = new RegExp(regexStr);
} else {
regexStr = pattern.toString();
regex = pattern;
}
return control => {
if (isEmptyInputValue(control.value)) {
return null;
}
const value = control.value;
return regex.test(value) ? null : {
'pattern': {
'requiredPattern': regexStr,
'actualValue': value
}
};
};
}
function nullValidator(control) {
return null;
}
function isPresent(o) {
return o != null;
}
function toObservable(value) {
const obs = _isPromise(value) ? from(value) : value;
if ((typeof ngDevMode === 'undefined' || ngDevMode) && !_isSubscribable(obs)) {
let errorMessage = `Expected async validator to return Promise or Observable.`;
if (typeof value === 'object') {
errorMessage += ' Are you using a synchronous validator where an async validator is expected?';
}
throw new _RuntimeError(-1101, errorMessage);
}
return obs;
}
function mergeErrors(arrayOfErrors) {
let res = {};
arrayOfErrors.forEach(errors => {
res = errors != null ? {
...res,
...errors
} : res;
});
return Object.keys(res).length === 0 ? null : res;
}
function executeValidators(control, validators) {
return validators.map(validator => validator(control));
}
function isValidatorFn(validator) {
return !validator.validate;
}
function normalizeValidators(validators) {
return validators.map(validator => {
return isValidatorFn(validator) ? validator : c => validator.validate(c);
});
}
function compose(validators) {
if (!validators) return null;
const presentValidators = validators.filter(isPresent);
if (presentValidators.length == 0) return null;
return function (control) {
return mergeErrors(executeValidators(control, presentValidators));
};
}
function composeValidators(validators) {
return validators != null ? compose(normalizeValidators(validators)) : null;
}
function composeAsync(validators) {
if (!validators) return null;
const presentValidators = validators.filter(isPresent);
if (presentValidators.length == 0) return null;
return function (control) {
const observables = executeValidators(control, presentValidators).map(toObservable);
return forkJoin(observables).pipe(map(mergeErrors));
};
}
function composeAsyncValidators(validators) {
return validators != null ? composeAsync(normalizeValidators(validators)) : null;
}
function mergeValidators(controlValidators, dirValidator) {
if (controlValidators === null) return [dirValidator];
return Array.isArray(controlValidators) ? [...controlValidators, dirValidator] : [controlValidators, dirValidator];
}
function getControlValidators(control) {
return control._rawValidators;
}
function getControlAsyncValidators(control) {
return control._rawAsyncValidators;
}
function makeValidatorsArray(validators) {
if (!validators) return [];
return Array.isArray(validators) ? validators : [validators];
}
function hasValidator(validators, validator) {
return Array.isArray(validators) ? validators.includes(validator) : validators === validator;
}
function addValidators(validators, currentValidators) {
const current = makeValidatorsArray(currentValidators);
const validatorsToAdd = makeValidatorsArray(validators);
validatorsToAdd.forEach(v => {
if (!hasValidator(current, v)) {
current.push(v);
}
});
return current;
}
function removeValidators(validators, currentValidators) {
return makeValidatorsArray(currentValidators).filter(v => !hasValidator(validators, v));
}
class AbstractControlDirective {
get value() {
return this.control ? this.control.value : null;
}
get valid() {
return this.control ? this.control.valid : null;
}
get invalid() {
return this.control ? this.control.invalid : null;
}
get pending() {
return this.control ? this.control.pending : null;
}
get disabled() {
return this.control ? this.control.disabled : null;
}
get enabled() {
return this.control ? this.control.enabled : null;
}
get errors() {
return this.control ? this.control.errors : null;
}
get pristine() {
return this.control ? this.control.pristine : null;
}
get dirty() {
return this.control ? this.control.dirty : null;
}
get touched() {
return this.control ? this.control.touched : null;
}
get status() {
return this.control ? this.control.status : null;
}
get untouched() {
return this.control ? this.control.untouched : null;
}
get statusChanges() {
return this.control ? this.control.statusChanges : null;
}
get valueChanges() {
return this.control ? this.control.valueChanges : null;
}
get path() {
return null;
}
_composedValidatorFn;
_composedAsyncValidatorFn;
_rawValidators = [];
_rawAsyncValidators = [];
_setValidators(validators) {
this._rawValidators = validators || [];
this._composedValidatorFn = composeValidators(this._rawValidators);
}
_setAsyncValidators(validators) {
this._rawAsyncValidators = validators || [];
this._composedAsyncValidatorFn = composeAsyncValidators(this._rawAsyncValidators);
}
get validator() {
return this._composedValidatorFn || null;
}
get asyncValidator() {
return this._composedAsyncValidatorFn || null;
}
_onDestroyCallbacks = [];
_registerOnDestroy(fn) {
this._onDestroyCallbacks.push(fn);
}
_invokeOnDestroyCallbacks() {
this._onDestroyCallbacks.forEach(fn => fn());
this._onDestroyCallbacks = [];
}
reset(value = undefined) {
if (this.control) this.control.reset(value);
}
hasError(errorCode, path) {
return this.control ? this.control.hasError(errorCode, path) : false;
}
getError(errorCode, path) {
return this.control ? this.control.getError(errorCode, path) : null;
}
}
class ControlContainer extends AbstractControlDirective {
name;
get formDirective() {
return null;
}
get path() {
return null;
}
}
class NgControl extends AbstractControlDirective {
_parent = null;
name = null;
valueAccessor = null;
}
class AbstractControlStatus {
_cd;
constructor(cd) {
this._cd = cd;
}
get isTouched() {
this._cd?.control?._touched?.();
return !!this._cd?.control?.touched;
}
get isUntouched() {
return !!this._cd?.control?.untouched;
}
get isPristine() {
this._cd?.control?._pristine?.();
return !!this._cd?.control?.pristine;
}
get isDirty() {
return !!this._cd?.control?.dirty;
}
get isValid() {
this._cd?.control?._status?.();
return !!this._cd?.control?.valid;
}
get isInvalid() {
return !!this._cd?.control?.invalid;
}
get isPending() {
return !!this._cd?.control?.pending;
}
get isSubmitted() {
this._cd?._submitted?.();
return !!this._cd?.submitted;
}
}
const ngControlStatusHost = {
'[class.ng-untouched]': 'isUntouched',
'[class.ng-touched]': 'isTouched',
'[class.ng-pristine]': 'isPristine',
'[class.ng-dirty]': 'isDirty',
'[class.ng-valid]': 'isValid',
'[class.ng-invalid]': 'isInvalid',
'[class.ng-pending]': 'isPending'
};
class NgControlStatus extends AbstractControlStatus {
constructor(cd) {
super(cd);
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: NgControlStatus,
deps: [{
token: NgControl,
self: true
}],
target: i0.ɵɵFactoryTarget.Directive
});
static ɵdir = i0.ɵɵngDeclareDirective({
minVersion: "14.0.0",
version: "21.0.6",
type: NgControlStatus,
isStandalone: false,
selector: "[formControlName],[ngModel],[formControl]",
host: {
properties: {
"class.ng-untouched": "isUntouched",
"class.ng-touched": "isTouched",
"class.ng-pristine": "isPristine",
"class.ng-dirty": "isDirty",
"class.ng-valid": "isValid",
"class.ng-invalid": "isInvalid",
"class.ng-pending": "isPending"
}
},
usesInheritance: true,
ngImport: i0
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: NgControlStatus,
decorators: [{
type: Directive,
args: [{
selector: '[formControlName],[ngModel],[formControl]',
host: ngControlStatusHost,
standalone: false
}]
}],
ctorParameters: () => [{
type: NgControl,
decorators: [{
type: Self
}]
}]
});
class NgControlStatusGroup extends AbstractControlStatus {
constructor(cd) {
super(cd);
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: NgControlStatusGroup,
deps: [{
token: ControlContainer,
optional: true,
self: true
}],
target: i0.ɵɵFactoryTarget.Directive
});
static ɵdir = i0.ɵɵngDeclareDirective({
minVersion: "14.0.0",
version: "21.0.6",
type: NgControlStatusGroup,
isStandalone: false,
selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]",
host: {
properties: {
"class.ng-untouched": "isUntouched",
"class.ng-touched": "isTouched",
"class.ng-pristine": "isPristine",
"class.ng-dirty": "isDirty",
"class.ng-valid": "isValid",
"class.ng-invalid": "isInvalid",
"class.ng-pending": "isPending",
"class.ng-submitted": "isSubmitted"
}
},
usesInheritance: true,
ngImport: i0
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: NgControlStatusGroup,
decorators: [{
type: Directive,
args: [{
selector: '[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]',
host: {
...ngControlStatusHost,
'[class.ng-submitted]': 'isSubmitted'
},
standalone: false
}]
}],
ctorParameters: () => [{
type: ControlContainer,
decorators: [{
type: Optional
}, {
type: Self
}]
}]
});
const formControlNameExample = `
<div [formGroup]="myGroup">
<input formControlName="firstName">
</div>
In your class:
this.myGroup = new FormGroup({
firstName: new FormControl()
});`;
const formGroupNameExample = `
<div [formGroup]="myGroup">
<div formGroupName="person">
<input formControlName="firstName">
</div>
</div>
In your class:
this.myGroup = new FormGroup({
person: new FormGroup({ firstName: new FormControl() })
});`;
const formArrayNameExample = `
<div [formGroup]="myGroup">
<div formArrayName="cities">
<div *ngFor="let city of cityArray.controls; index as i">
<input [formControlName]="i">
</div>
</div>
</div>
In your class:
this.cityArray = new FormArray([new FormControl('SF')]);
this.myGroup = new FormGroup({
cities: this.cityArray
});`;
const ngModelGroupExample = `
<form>
<div ngModelGroup="person">
<input [(ngModel)]="person.name" name="firstName">
</div>
</form>`;
const ngModelWithFormGroupExample = `
<div [formGroup]="myGroup">
<input formControlName="firstName">
<input [(ngModel)]="showMoreControls" [ngModelOptions]="{standalone: true}">
</div>
`;
function controlParentException(nameOrIndex) {
return new _RuntimeError(1050, `formControlName must be used with a parent formGroup or formArray directive. You'll want to add a formGroup/formArray
directive and pass it an existing FormGroup/FormArray instance (you can create one in your class).
${describeFormControl(nameOrIndex)}
Example:
${formControlNameExample}`);
}
function describeFormControl(nameOrIndex) {
if (nameOrIndex == null || nameOrIndex === '') {
return '';
}
const valueType = typeof nameOrIndex === 'string' ? 'name' : 'index';
return `Affected Form Control ${valueType}: "${nameOrIndex}"`;
}
function ngModelGroupException() {
return new _RuntimeError(1051, `formControlName cannot be used with an ngModelGroup parent. It is only compatible with parents
that also have a "form" prefix: formGroupName, formArrayName, or formGroup.
Option 1: Update the parent to be formGroupName (reactive form strategy)
${formGroupNameExample}
Option 2: Use ngModel instead of formControlName (template-driven strategy)
${ngModelGroupExample}`);
}
function missingFormException() {
return new _RuntimeError(1052, `formGroup expects a FormGroup instance. Please pass one in.
Example:
${formControlNameExample}`);
}
function groupParentException() {
return new _RuntimeError(1053, `formGroupName must be used with a parent formGroup directive. You'll want to add a formGroup
directive and pass it an existing FormGroup instance (you can create one in your class).
Example:
${formGroupNameExample}`);
}
function arrayParentException() {
return new _RuntimeError(1054, `formArrayName must be used with a parent formGroup directive. You'll want to add a formGroup
directive and pass it an existing FormGroup instance (you can create one in your class).
Example:
${formArrayNameExample}`);
}
const disabledAttrWarning = `
It looks like you're using the disabled attribute with a reactive form directive. If you set disabled to true
when you set up this control in your component class, the disabled attribute will actually be set in the DOM for
you. We recommend using this approach to avoid 'changed after checked' errors.
Example:
// Specify the \`disabled\` property at control creation time:
form = new FormGroup({
first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
last: new FormControl('Drew', Validators.required)
});
// Controls can also be enabled/disabled after creation:
form.get('first')?.enable();
form.get('last')?.disable();
`;
const asyncValidatorsDroppedWithOptsWarning = `
It looks like you're constructing using a FormControl with both an options argument and an
async validators argument. Mixing these arguments will cause your async validators to be dropped.
You should either put all your validators in the options object, or in separate validators
arguments. For example:
// Using validators arguments
fc = new FormControl(42, Validators.required, myAsyncValidator);
// Using AbstractControlOptions
fc = new FormControl(42, {validators: Validators.required, asyncValidators: myAV});
// Do NOT mix them: async validators will be dropped!
fc = new FormControl(42, {validators: Validators.required}, /* Oops! */ myAsyncValidator);
`;
function ngModelWarning(directiveName) {
return `
It looks like you're using ngModel on the same form field as ${directiveName}.
Support for using the ngModel input property and ngModelChange event with
reactive form directives has been deprecated in Angular v6 and will be removed
in a future version of Angular.
For more information on this, see our API docs here:
https://angular.io/api/forms/${directiveName === 'formControl' ? 'FormControlDirective' : 'FormControlName'}#use-with-ngmodel
`;
}
function describeKey(isFormGroup, key) {
return isFormGroup ? `with name: '${key}'` : `at index: ${key}`;
}
function noControlsError(isFormGroup) {
return `
There are no form controls registered with this ${isFormGroup ? 'group' : 'array'} yet. If you're using ngModel,
you may want to check next tick (e.g. use setTimeout).
`;
}
function missingControlError(isFormGroup, key) {
return `Cannot find form control ${describeKey(isFormGroup, key)}`;
}
function missingControlValueError(isFormGroup, key) {
return `Must supply a value for form control ${describeKey(isFormGroup, key)}`;
}
const VALID = 'VALID';
const INVALID = 'INVALID';
const PENDING = 'PENDING';
const DISABLED = 'DISABLED';
class ControlEvent {}
class ValueChangeEvent extends ControlEvent {
value;
source;
constructor(value, source) {
super();
this.value = value;
this.source = source;
}
}
class PristineChangeEvent extends ControlEvent {
pristine;
source;
constructor(pristine, source) {
super();
this.pristine = pristine;
this.source = source;
}
}
class TouchedChangeEvent extends ControlEvent {
touched;
source;
constructor(touched, source) {
super();
this.touched = touched;
this.source = source;
}
}
class StatusChangeEvent extends ControlEvent {
status;
source;
constructor(status, source) {
super();
this.status = status;
this.source = source;
}
}
class FormSubmittedEvent extends ControlEvent {
source;
constructor(source) {
super();
this.source = source;
}
}
class FormResetEvent extends ControlEvent {
source;
constructor(source) {
super();
this.source = source;
}
}
function pickValidators(validatorOrOpts) {
return (isOptionsObj(validatorOrOpts) ? validatorOrOpts.validators : validatorOrOpts) || null;
}
function coerceToValidator(validator) {
return Array.isArray(validator) ? composeValidators(validator) : validator || null;
}
function pickAsyncValidators(asyncValidator, validatorOrOpts) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (isOptionsObj(validatorOrOpts) && asyncValidator) {
console.warn(asyncValidatorsDroppedWithOptsWarning);
}
}
return (isOptionsObj(validatorOrOpts) ? validatorOrOpts.asyncValidators : asyncValidator) || null;
}
function coerceToAsyncValidator(asyncValidator) {
return Array.isArray(asyncValidator) ? composeAsyncValidators(asyncValidator) : asyncValidator || null;
}
function isOptionsObj(validatorOrOpts) {
return validatorOrOpts != null && !Array.isArray(validatorOrOpts) && typeof validatorOrOpts === 'object';
}
function assertControlPresent(parent, isGroup, key) {
const controls = parent.controls;
const collection = isGroup ? Object.keys(controls) : controls;
if (!collection.length) {
throw new _RuntimeError(1000, typeof ngDevMode === 'undefined' || ngDevMode ? noControlsError(isGroup) : '');
}
if (!controls[key]) {
throw new _RuntimeError(1001, typeof ngDevMode === 'undefined' || ngDevMode ? missingControlError(isGroup, key) : '');
}
}
function assertAllValuesPresent(control, isGroup, value) {
control._forEachChild((_, key) => {
if (value[key] === undefined) {
throw new _RuntimeError(1002, typeof ngDevMode === 'undefined' || ngDevMode ? missingControlValueError(isGroup, key) : '');
}
});
}
class AbstractControl {
_pendingDirty = false;
_hasOwnPendingAsyncValidator = null;
_pendingTouched = false;
_onCollectionChange = () => {};
_updateOn;
_parent = null;
_asyncValidationSubscription;
_composedValidatorFn;
_composedAsyncValidatorFn;
_rawValidators;
_rawAsyncValidators;
value;
constructor(validators, asyncValidators) {
this._assignValidators(validators);
this._assignAsyncValidators(asyncValidators);
}
get validator() {
return this._composedValidatorFn;
}
set validator(validatorFn) {
this._rawValidators = this._composedValidatorFn = validatorFn;
}
get asyncValidator() {
return this._composedAsyncValidatorFn;
}
set asyncValidator(asyncValidatorFn) {
this._rawAsyncValidators = this._composedAsyncValidatorFn = asyncValidatorFn;
}
get parent() {
return this._parent;
}
get status() {
return untracked(this.statusReactive);
}
set status(v) {
untracked(() => this.statusReactive.set(v));
}
_status = computed(() => this.statusReactive(), ...(ngDevMode ? [{
debugName: "_status"
}] : []));
statusReactive = signal(undefined, ...(ngDevMode ? [{
debugName: "statusReactive"
}] : []));
get valid() {
return this.status === VALID;
}
get invalid() {
return this.status === INVALID;
}
get pending() {
return this.status == PENDING;
}
get disabled() {
return this.status === DISABLED;
}
get enabled() {
return this.status !== DISABLED;
}
errors;
get pristine() {
return untracked(this.pristineReactive);
}
set pristine(v) {
untracked(() => this.pristineReactive.set(v));
}
_pristine = computed(() => this.pristineReactive(), ...(ngDevMode ? [{
debugName: "_pristine"
}] : []));
pristineReactive = signal(true, ...(ngDevMode ? [{
debugName: "pristineReactive"
}] : []));
get dirty() {
return !this.pristine;
}
get touched() {
return untracked(this.touchedReactive);
}
set touched(v) {
untracked(() => this.touchedReactive.set(v));
}
_touched = computed(() => this.touchedReactive(), ...(ngDevMode ? [{
debugName: "_touched"
}] : []));
touchedReactive = signal(false, ...(ngDevMode ? [{
debugName: "touchedReactive"
}] : []));
get untouched() {
return !this.touched;
}
_events = new Subject();
events = this._events.asObservable();
valueChanges;
statusChanges;
get updateOn() {
return this._updateOn ? this._updateOn : this.parent ? this.parent.updateOn : 'change';
}
setValidators(validators) {
this._assignValidators(validators);
}
setAsyncValidators(validators) {
this._assignAsyncValidators(validators);
}
addValidators(validators) {
this.setValidators(addValidators(validators, this._rawValidators));
}
addAsyncValidators(validators) {
this.setAsyncValidators(addValidators(validators, this._rawAsyncValidators));
}
removeValidators(validators) {
this.setValidators(removeValidators(validators, this._rawValidators));
}
removeAsyncValidators(validators) {
this.setAsyncValidators(removeValidators(validators, this._rawAsyncValidators));
}
hasValidator(validator) {
return hasValidator(this._rawValidators, validator);
}
hasAsyncValidator(validator) {
return hasValidator(this._rawAsyncValidators, validator);
}
clearValidators() {
this.validator = null;
}
clearAsyncValidators() {
this.asyncValidator = null;
}
markAsTouched(opts = {}) {
const changed = this.touched === false;
this.touched = true;
const sourceControl = opts.sourceControl ?? this;
if (this._parent && !opts.onlySelf) {
this._parent.markAsTouched({
...opts,
sourceControl
});
}
if (changed && opts.emitEvent !== false) {
this._events.next(new TouchedChangeEvent(true, sourceControl));
}
}
markAllAsDirty(opts = {}) {
this.markAsDirty({
onlySelf: true,
emitEvent: opts.emitEvent,
sourceControl: this
});
this._forEachChild(control => control.markAllAsDirty(opts));
}
markAllAsTouched(opts = {}) {
this.markAsTouched({
onlySelf: true,
emitEvent: opts.emitEvent,
sourceControl: this
});
this._forEachChild(control => control.markAllAsTouched(opts));
}
markAsUntouched(opts = {}) {
const changed = this.touched === true;
this.touched = false;
this._pendingTouched = false;
const sourceControl = opts.sourceControl ?? this;
this._forEachChild(control => {
control.markAsUntouched({
onlySelf: true,
emitEvent: opts.emitEvent,
sourceControl
});
});
if (this._parent && !opts.onlySelf) {
this._parent._updateTouched(opts, sourceControl);
}
if (changed && opts.emitEvent !== false) {
this._events.next(new TouchedChangeEvent(false, sourceControl));
}
}
markAsDirty(opts = {}) {
const changed = this.pristine === true;
this.pristine = false;
const sourceControl = opts.sourceControl ?? this;
if (this._parent && !opts.onlySelf) {
this._parent.markAsDirty({
...opts,
sourceControl
});
}
if (changed && opts.emitEvent !== false) {
this._events.next(new PristineChangeEvent(false, sourceControl));
}
}
markAsPristine(opts = {}) {
const changed = this.pristine === false;
this.pristine = true;
this._pendingDirty = false;
const sourceControl = opts.sourceControl ?? this;
this._forEachChild(control => {
control.markAsPristine({
onlySelf: true,
emitEvent: opts.emitEvent
});
});
if (this._parent && !opts.onlySelf) {
this._parent._updatePristine(opts, sourceControl);
}
if (changed && opts.emitEvent !== false) {
this._events.next(new PristineChangeEvent(true, sourceControl));
}
}
markAsPending(opts = {}) {
this.status = PENDING;
const sourceControl = opts.sourceControl ?? this;
if (opts.emitEvent !== false) {
this._events.next(new StatusChangeEvent(this.status, sourceControl));
this.statusChanges.emit(this.status);
}
if (this._parent && !opts.onlySelf) {
this._parent.markAsPending({
...opts,
sourceControl
});
}
}
disable(opts = {}) {
const skipPristineCheck = this._parentMarkedDirty(opts.onlySelf);
this.status = DISABLED;
this.errors = null;
this._forEachChild(control => {
control.disable({
...opts,
onlySelf: true
});
});
this._updateValue();
const sourceControl = opts.sourceControl ?? this;
if (opts.emitEvent !== false) {
this._events.next(new ValueChangeEvent(this.value, sourceControl));
this._events.next(new StatusChangeEvent(this.status, sourceControl));
this.valueChanges.emit(this.value);
this.statusChanges.emit(this.status);
}
this._updateAncestors({
...opts,
skipPristineCheck
}, this);
this._onDisabledChange.forEach(changeFn => changeFn(true));
}
enable(opts = {}) {
const skipPristineCheck = this._parentMarkedDirty(opts.onlySelf);
this.status = VALID;
this._forEachChild(control => {
control.enable({
...opts,
onlySelf: true
});
});
this.updateValueAndValidity({
onlySelf: true,
emitEvent: opts.emitEvent
});
this._updateAncestors({
...opts,
skipPristineCheck
}, this);
this._onDisabledChange.forEach(changeFn => changeFn(false));
}
_updateAncestors(opts, sourceControl) {
if (this._parent && !opts.onlySelf) {
this._parent.updateValueAndValidity(opts);
if (!opts.skipPristineCheck) {
this._parent._updatePristine({}, sourceControl);
}
this._parent._updateTouched({}, sourceControl);
}
}
setParent(parent) {
this._parent = parent;
}
getRawValue() {
return this.value;
}
updateValueAndValidity(opts = {}) {
this._setInitialStatus();
this._updateValue();
if (this.enabled) {
const shouldHaveEmitted = this._cancelExistingSubscription();
this.errors = this._runValidator();
this.status = this._calculateStatus();
if (this.status === VALID || this.status === PENDING) {
this._runAsyncValidator(shouldHaveEmitted, opts.emitEvent);
}
}
const sourceControl = opts.sourceControl ?? this;
if (opts.emitEvent !== false) {
this._events.next(new ValueChangeEvent(this.value, sourceControl));
this._events.next(new StatusChangeEvent(this.status, sourceControl));
this.valueChanges.emit(this.value);
this.statusChanges.emit(this.status);
}
if (this._parent && !opts.onlySelf) {
this._parent.updateValueAndValidity({
...opts,
sourceControl
});
}
}
_updateTreeValidity(opts = {
emitEvent: true
}) {
this._forEachChild(ctrl => ctrl._updateTreeValidity(opts));
this.updateValueAndValidity({
onlySelf: true,
emitEvent: opts.emitEvent
});
}
_setInitialStatus() {
this.status = this._allControlsDisabled() ? DISABLED : VALID;
}
_runValidator() {
return this.validator ? this.validator(this) : null;
}
_runAsyncValidator(shouldHaveEmitted, emitEvent) {
if (this.asyncValidator) {
this.status = PENDING;
this._hasOwnPendingAsyncValidator = {
emitEvent: emitEvent !== false,
shouldHaveEmitted: shouldHaveEmitted !== false
};
const obs = toObservable(this.asyncValidator(this));
this._asyncValidationSubscription = obs.subscribe(errors => {
this._hasOwnPendingAsyncValidator = null;
this.setErrors(errors, {
emitEvent,
shouldHaveEmitted
});
});
}
}
_cancelExistingSubscription() {
if (this._asyncValidationSubscription) {
this._asyncValidationSubscription.unsubscribe();
const shouldHaveEmitted = (this._hasOwnPendingAsyncValidator?.emitEvent || this._hasOwnPendingAsyncValidator?.shouldHaveEmitted) ?? false;
this._hasOwnPendingAsyncValidator = null;
return shouldHaveEmitted;
}
return false;
}
setErrors(errors, opts = {}) {
this.errors = errors;
this._updateControlsErrors(opts.emitEvent !== false, this, opts.shouldHaveEmitted);
}
get(path) {
let currPath = path;
if (currPath == null) return null;
if (!Array.isArray(currPath)) currPath = currPath.split('.');
if (currPath.length === 0) return null;
return currPath.reduce((control, name) => control && control._find(name), this);
}
getError(errorCode, path) {
const control = path ? this.get(path) : this;
return control && control.errors ? control.errors[errorCode] : null;
}
hasError(errorCode, path) {
return !!this.getError(errorCode, path);
}
get root() {
let x = this;
while (x._parent) {
x = x._parent;
}
return x;
}
_updateControlsErrors(emitEvent, changedControl, shouldHaveEmitted) {
this.status = this._calculateStatus();
if (emitEvent) {
this.statusChanges.emit(this.status);
}
if (emitEvent || shouldHaveEmitted) {
this._events.next(new StatusChangeEvent(this.status, changedControl));
}
if (this._parent) {
this._parent._updateControlsErrors(emitEvent, changedControl, shouldHaveEmitted);
}
}
_initObservables() {
this.valueChanges = new EventEmitter();
this.statusChanges = new EventEmitter();
}
_calculateStatus() {
if (this._allControlsDisabled()) return DISABLED;
if (this.errors) return INVALID;
if (this._hasOwnPendingAsyncValidator || this._anyControlsHaveStatus(PENDING)) return PENDING;
if (this._anyControlsHaveStatus(INVALID)) return INVALID;
return VALID;
}
_anyControlsHaveStatus(status) {
return this._anyControls(control => control.status === status);
}
_anyControlsDirty() {
return this._anyControls(control => control.dirty);
}
_anyControlsTouched() {
return this._anyControls(control => control.touched);
}
_updatePristine(opts, changedControl) {
const newPristine = !this._anyControlsDirty();
const changed = this.pristine !== newPristine;
this.pristine = newPristine;
if (this._parent && !opts.onlySelf) {
this._parent._updatePristine(opts, changedControl);
}
if (changed) {
this._events.next(new PristineChangeEvent(this.pristine, changedControl));
}
}
_updateTouched(opts = {}, changedControl) {
this.touched = this._anyControlsTouched();
this._events.next(new TouchedChangeEvent(this.touched, changedControl));
if (this._parent && !opts.onlySelf) {
this._parent._updateTouched(opts, changedControl);
}
}
_onDisabledChange = [];
_registerOnCollectionChange(fn) {
this._onCollectionChange = fn;
}
_setUpdateStrategy(opts) {
if (isOptionsObj(opts) && opts.updateOn != null) {
this._updateOn = opts.updateOn;
}
}
_parentMarkedDirty(onlySelf) {
const parentDirty = this._parent && this._parent.dirty;
return !onlySelf && !!parentDirty && !this._parent._anyControlsDirty();
}
_find(name) {
return null;
}
_assignValidators(validators) {
this._rawValidators = Array.isArray(validators) ? validators.slice() : validators;
this._composedValidatorFn = coerceToValidator(this._rawValidators);
}
_assignAsyncValidators(validators) {
this._rawAsyncValidators = Array.isArray(validators) ? validators.slice() : validators;
this._composedAsyncValidatorFn = coerceToAsyncValidator(this._rawAsyncValidators);
}
}
class FormGroup extends AbstractControl {
constructor(controls, validatorOrOpts, asyncValidator) {
super(pickValidators(validatorOrOpts), pickAsyncValidators(asyncValidator, validatorOrOpts));
(typeof ngDevMode === 'undefined' || ngDevMode) && validateFormGroupControls(controls);
this.controls = controls;
this._initObservables();
this._setUpdateStrategy(validatorOrOpts);
this._setUpControls();
this.updateValueAndValidity({
onlySelf: true,
emitEvent: !!this.asyncValidator
});
}
controls;
registerControl(name, control) {
if (this.controls[name]) return this.controls[name];
this.controls[name] = control;
control.setParent(this);
control._registerOnCollectionChange(this._onCollectionChange);
return control;
}
addControl(name, control, options = {}) {
this.registerControl(name, control);
this.updateValueAndValidity({
emitEvent: options.emitEvent
});
this._onCollectionChange();
}
removeControl(name, options = {}) {
if (this.controls[name]) this.controls[name]._registerOnCollectionChange(() => {});
delete this.controls[name];
this.updateValueAndValidity({
emitEvent: options.emitEvent
});
this._onCollectionChange();
}
setControl(name, control, options = {}) {
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();
}
contains(controlName) {
return this.controls.hasOwnProperty(controlName) && this.controls[controlName].enabled;
}
setValue(value, options = {}) {
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);
}
patchValue(value, options = {}) {
if (value == null) return;
Object.keys(value).forEach(name => {
const control = this.controls[name];
if (control) {
control.patchValue(value[name], {
onlySelf: true,
emitEvent: options.emitEvent
});
}
});
this.updateValueAndValidity(options);
}
reset(value = {}, options = {}) {
this._forEachChild((control, name) => {
control.reset(value ? value[name] : null, {
...options,
onlySelf: true
});
});
this._updatePristine(options, this);
this._updateTouched(options, this);
this.updateValueAndValidity(options);
if (options?.emitEvent !== false) {
this._events.next(new FormResetEvent(this));
}
}
getRawValue() {
return this._reduceChildren({}, (acc, control, name) => {
acc[name] = control.getRawValue();
return acc;
});
}
_syncPendingControls() {
let subtreeUpdated = this._reduceChildren(false, (updated, child) => {
return child._syncPendingControls() ? true : updated;
});
if (subtreeUpdated) this.updateValueAndValidity({
onlySelf: true
});
return subtreeUpdated;
}
_forEachChild(cb) {
Object.keys(this.controls).forEach(key => {
const control = this.controls[key];
control && cb(control, key);
});
}
_setUpControls() {
this._forEachChild(control => {
control.setParent(this);
control._registerOnCollectionChange(this._onCollectionChange);
});
}
_updateValue() {
this.value = this._reduceValue();
}
_anyControls(condition) {
for (const [controlName, control] of Object.entries(this.controls)) {
if (this.contains(controlName) && condition(control)) {
return true;
}
}
return false;
}
_reduceValue() {
let acc = {};
return this._reduceChildren(acc, (acc, control, name) => {
if (control.enabled || this.disabled) {
acc[name] = control.value;
}
return acc;
});
}
_reduceChildren(initValue, fn) {
let res = initValue;
this._forEachChild((control, name) => {
res = fn(res, control, name);
});
return res;
}
_allControlsDisabled() {
for (const controlName of Object.keys(this.controls)) {
if (this.controls[controlName].enabled) {
return false;
}
}
return Object.keys(this.controls).length > 0 || this.disabled;
}
_find(name) {
return this.controls.hasOwnProperty(name) ? this.controls[name] : null;
}
}
function validateFormGroupControls(controls) {
const invalidKeys = Object.keys(controls).filter(key => key.includes('.'));
if (invalidKeys.length > 0) {
console.warn(`FormGroup keys cannot include \`.\`, please replace the keys for: ${invalidKeys.join(',')}.`);
}
}
const UntypedFormGroup = FormGroup;
const isFormGroup = control => control instanceof FormGroup;
class FormRecord extends FormGroup {}
const isFormRecord = control => control instanceof FormRecord;
const CALL_SET_DISABLED_STATE = new InjectionToken(typeof ngDevMode === 'undefined' || ngDevMode ? 'CallSetDisabledState' : '', {
factory: () => setDisabledStateDefault
});
const setDisabledStateDefault = 'always';
function controlPath(name, parent) {
return [...parent.path, name];
}
function setUpControl(control, dir, callSetDisabledState = setDisabledStateDefault) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (!control) _throwError(dir, 'Cannot find control with');
if (!dir.valueAccessor) _throwMissingValueAccessorError(dir);
}
setUpValidators(control, dir);
dir.valueAccessor.writeValue(control.value);
if (control.disabled || callSetDisabledState === 'always') {
dir.valueAccessor.setDisabledState?.(control.disabled);
}
setUpViewChangePipeline(control, dir);
setUpModelChangePipeline(control, dir);
setUpBlurPipeline(control, dir);
setUpDisabledChangeHandler(control, dir);
}
function cleanUpControl(control, dir, validateControlPresenceOnChange = true) {
const noop = () => {
if (validateControlPresenceOnChange && (typeof ngDevMode === 'undefined' || ngDevMode)) {
_noControlError(dir);
}
};
if (dir.valueAccessor) {
dir.valueAccessor.registerOnChange(noop);
dir.valueAccessor.registerOnTouched(noop);
}
cleanUpValidators(control, dir);
if (control) {
dir._invokeOnDestroyCallbacks();
control._registerOnCollectionChange(() => {});
}
}
function registerOnValidatorChange(validators, onChange) {
validators.forEach(validator => {
if (validator.registerOnValidatorChange) validator.registerOnValidatorChange(onChange);
});
}
function setUpDisabledChangeHandler(control, dir) {
if (dir.valueAccessor.setDisabledState) {
const onDisabledChange = isDisabled => {
dir.valueAccessor.setDisabledState(isDisabled);
};
control.registerOnDisabledChange(onDisabledChange);
dir._registerOnDestroy(() => {