@angular/forms
Version:
Angular - directives and services for creating forms
1,350 lines (1,341 loc) • 287 kB
JavaScript
/**
* @license Angular v17.2.3
* (c) 2010-2022 Google LLC. https://angular.io/
* License: MIT
*/
import * as i0 from '@angular/core';
import { Directive, InjectionToken, forwardRef, Optional, Inject, ɵisPromise, ɵisSubscribable, ɵRuntimeError, Self, EventEmitter, Input, Host, SkipSelf, booleanAttribute, ChangeDetectorRef, Output, Injectable, inject, NgModule, Version } from '@angular/core';
import { ɵgetDOM } from '@angular/common';
import { from, forkJoin } from 'rxjs';
import { map } from 'rxjs/operators';
/**
* Base class for all ControlValueAccessor classes defined in Forms package.
* Contains common logic and utility functions.
*
* Note: this is an *internal-only* class and should not be extended or used directly in
* applications code.
*/
class BaseControlValueAccessor {
constructor(_renderer, _elementRef) {
this._renderer = _renderer;
this._elementRef = _elementRef;
/**
* The registered callback function called when a change or input event occurs on the input
* element.
* @nodoc
*/
this.onChange = (_) => { };
/**
* The registered callback function called when a blur event occurs on the input element.
* @nodoc
*/
this.onTouched = () => { };
}
/**
* Helper method that sets a property on a target element using the current Renderer
* implementation.
* @nodoc
*/
setProperty(key, value) {
this._renderer.setProperty(this._elementRef.nativeElement, key, value);
}
/**
* Registers a function called when the control is touched.
* @nodoc
*/
registerOnTouched(fn) {
this.onTouched = fn;
}
/**
* Registers a function called when the control value changes.
* @nodoc
*/
registerOnChange(fn) {
this.onChange = fn;
}
/**
* Sets the "disabled" property on the range input element.
* @nodoc
*/
setDisabledState(isDisabled) {
this.setProperty('disabled', isDisabled);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.3", ngImport: i0, type: BaseControlValueAccessor, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.3", type: BaseControlValueAccessor, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.3", ngImport: i0, type: BaseControlValueAccessor, decorators: [{
type: Directive
}], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }] });
/**
* Base class for all built-in ControlValueAccessor classes (except DefaultValueAccessor, which is
* used in case no other CVAs can be found). We use this class to distinguish between default CVA,
* built-in CVAs and custom CVAs, so that Forms logic can recognize built-in CVAs and treat custom
* ones with higher priority (when both built-in and custom CVAs are present).
*
* Note: this is an *internal-only* class and should not be extended or used directly in
* applications code.
*/
class BuiltInControlValueAccessor extends BaseControlValueAccessor {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.3", ngImport: i0, type: BuiltInControlValueAccessor, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.3", type: BuiltInControlValueAccessor, usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.3", ngImport: i0, type: BuiltInControlValueAccessor, decorators: [{
type: Directive
}] });
/**
* Used to provide a `ControlValueAccessor` for form controls.
*
* See `DefaultValueAccessor` for how to implement one.
*
* @publicApi
*/
const NG_VALUE_ACCESSOR = new InjectionToken(ngDevMode ? 'NgValueAccessor' : '');
const CHECKBOX_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckboxControlValueAccessor),
multi: true,
};
/**
* @description
* A `ControlValueAccessor` for writing a value and listening to changes on a checkbox input
* element.
*
* @usageNotes
*
* ### Using a checkbox with a reactive form.
*
* The following example shows how to use a checkbox with a reactive form.
*
* ```ts
* const rememberLoginControl = new FormControl();
* ```
*
* ```
* <input type="checkbox" [formControl]="rememberLoginControl">
* ```
*
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
class CheckboxControlValueAccessor extends BuiltInControlValueAccessor {
/**
* Sets the "checked" property on the input element.
* @nodoc
*/
writeValue(value) {
this.setProperty('checked', value);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.3", ngImport: i0, type: CheckboxControlValueAccessor, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.3", type: CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]", host: { listeners: { "change": "onChange($event.target.checked)", "blur": "onTouched()" } }, providers: [CHECKBOX_VALUE_ACCESSOR], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.3", ngImport: i0, type: CheckboxControlValueAccessor, decorators: [{
type: Directive,
args: [{
selector: 'input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]',
host: { '(change)': 'onChange($event.target.checked)', '(blur)': 'onTouched()' },
providers: [CHECKBOX_VALUE_ACCESSOR]
}]
}] });
const DEFAULT_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true
};
/**
* We must check whether the agent is Android because composition events
* behave differently between iOS and Android.
*/
function _isAndroid() {
const userAgent = ɵgetDOM() ? ɵgetDOM().getUserAgent() : '';
return /android (\d+)/.test(userAgent.toLowerCase());
}
/**
* @description
* Provide this token to control if form directives buffer IME input until
* the "compositionend" event occurs.
* @publicApi
*/
const COMPOSITION_BUFFER_MODE = new InjectionToken(ngDevMode ? 'CompositionEventMode' : '');
/**
* The default `ControlValueAccessor` for writing a value and listening to changes on input
* elements. The accessor is used by the `FormControlDirective`, `FormControlName`, and
* `NgModel` directives.
*
* {@searchKeywords ngDefaultControl}
*
* @usageNotes
*
* ### Using the default value accessor
*
* The following example shows how to use an input element that activates the default value accessor
* (in this case, a text field).
*
* ```ts
* const firstNameControl = new FormControl();
* ```
*
* ```
* <input type="text" [formControl]="firstNameControl">
* ```
*
* This value accessor is used by default for `<input type="text">` and `<textarea>` elements, but
* you could also use it for custom components that have similar behavior and do not require special
* processing. In order to attach the default value accessor to a custom element, add the
* `ngDefaultControl` attribute as shown below.
*
* ```
* <custom-input-component ngDefaultControl [(ngModel)]="value"></custom-input-component>
* ```
*
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
class DefaultValueAccessor extends BaseControlValueAccessor {
constructor(renderer, elementRef, _compositionMode) {
super(renderer, elementRef);
this._compositionMode = _compositionMode;
/** Whether the user is creating a composition string (IME events). */
this._composing = false;
if (this._compositionMode == null) {
this._compositionMode = !_isAndroid();
}
}
/**
* Sets the "value" property on the input element.
* @nodoc
*/
writeValue(value) {
const normalizedValue = value == null ? '' : value;
this.setProperty('value', normalizedValue);
}
/** @internal */
_handleInput(value) {
if (!this._compositionMode || (this._compositionMode && !this._composing)) {
this.onChange(value);
}
}
/** @internal */
_compositionStart() {
this._composing = true;
}
/** @internal */
_compositionEnd(value) {
this._composing = false;
this._compositionMode && this.onChange(value);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.3", ngImport: i0, type: DefaultValueAccessor, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: COMPOSITION_BUFFER_MODE, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.3", type: DefaultValueAccessor, 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": "$any(this)._handleInput($event.target.value)", "blur": "onTouched()", "compositionstart": "$any(this)._compositionStart()", "compositionend": "$any(this)._compositionEnd($event.target.value)" } }, providers: [DEFAULT_VALUE_ACCESSOR], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.3", 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]',
// TODO: vsavkin replace the above selector with the one below it once
// https://github.com/angular/angular/issues/3011 is implemented
// selector: '[ngModel],[formControl],[formControlName]',
host: {
'(input)': '$any(this)._handleInput($event.target.value)',
'(blur)': 'onTouched()',
'(compositionstart)': '$any(this)._compositionStart()',
'(compositionend)': '$any(this)._compositionEnd($event.target.value)'
},
providers: [DEFAULT_VALUE_ACCESSOR]
}]
}], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [COMPOSITION_BUFFER_MODE]
}] }] });
function isEmptyInputValue(value) {
/**
* Check if the object is a string or array before evaluating the length attribute.
* This avoids falsely rejecting objects that contain a custom length attribute.
* For example, the object {id: 1, length: 0, width: 0} should not be returned as empty.
*/
return value == null ||
((typeof value === 'string' || Array.isArray(value)) && value.length === 0);
}
function hasValidLength(value) {
// non-strict comparison is intentional, to check for both `null` and `undefined` values
return value != null && typeof value.length === 'number';
}
/**
* @description
* An `InjectionToken` for registering additional synchronous validators used with
* `AbstractControl`s.
*
* @see {@link NG_ASYNC_VALIDATORS}
*
* @usageNotes
*
* ### Providing a custom validator
*
* The following example registers a custom validator directive. Adding the validator to the
* existing collection of validators requires the `multi: true` option.
*
* ```typescript
* @Directive({
* selector: '[customValidator]',
* providers: [{provide: NG_VALIDATORS, useExisting: CustomValidatorDirective, multi: true}]
* })
* class CustomValidatorDirective implements Validator {
* validate(control: AbstractControl): ValidationErrors | null {
* return { 'custom': true };
* }
* }
* ```
*
* @publicApi
*/
const NG_VALIDATORS = new InjectionToken(ngDevMode ? 'NgValidators' : '');
/**
* @description
* An `InjectionToken` for registering additional asynchronous validators used with
* `AbstractControl`s.
*
* @see {@link NG_VALIDATORS}
*
* @usageNotes
*
* ### Provide a custom async validator directive
*
* The following example implements the `AsyncValidator` interface to create an
* async validator directive with a custom error key.
*
* ```typescript
* @Directive({
* selector: '[customAsyncValidator]',
* providers: [{provide: NG_ASYNC_VALIDATORS, useExisting: CustomAsyncValidatorDirective, multi:
* true}]
* })
* class CustomAsyncValidatorDirective implements AsyncValidator {
* validate(control: AbstractControl): Promise<ValidationErrors|null> {
* return Promise.resolve({'custom': true});
* }
* }
* ```
*
* @publicApi
*/
const NG_ASYNC_VALIDATORS = new InjectionToken(ngDevMode ? 'NgAsyncValidators' : '');
/**
* A regular expression that matches valid e-mail addresses.
*
* At a high level, this regexp matches e-mail addresses of the format `local-part@tld`, where:
* - `local-part` consists of one or more of the allowed characters (alphanumeric and some
* punctuation symbols).
* - `local-part` cannot begin or end with a period (`.`).
* - `local-part` cannot be longer than 64 characters.
* - `tld` consists of one or more `labels` separated by periods (`.`). For example `localhost` or
* `foo.com`.
* - A `label` consists of one or more of the allowed characters (alphanumeric, dashes (`-`) and
* periods (`.`)).
* - A `label` cannot begin or end with a dash (`-`) or a period (`.`).
* - A `label` cannot be longer than 63 characters.
* - The whole address cannot be longer than 254 characters.
*
* ## Implementation background
*
* This regexp was ported over from AngularJS (see there for git history):
* https://github.com/angular/angular.js/blob/c133ef836/src/ng/directive/input.js#L27
* It is based on the
* [WHATWG version](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address) with
* some enhancements to incorporate more RFC rules (such as rules related to domain names and the
* lengths of different parts of the address). The main differences from the WHATWG version are:
* - Disallow `local-part` to begin or end with a period (`.`).
* - Disallow `local-part` length to exceed 64 characters.
* - Disallow total address length to exceed 254 characters.
*
* See [this commit](https://github.com/angular/angular.js/commit/f3f5cf72e) for more details.
*/
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])?)*$/;
/**
* @description
* Provides a set of built-in validators that can be used by form controls.
*
* A validator is a function that processes a `FormControl` or collection of
* controls and returns an error map or null. A null map means that validation has passed.
*
* @see [Form Validation](/guide/form-validation)
*
* @publicApi
*/
class Validators {
/**
* @description
* Validator that requires the control's value to be greater than or equal to the provided number.
*
* @usageNotes
*
* ### Validate against a minimum of 3
*
* ```typescript
* const control = new FormControl(2, Validators.min(3));
*
* console.log(control.errors); // {min: {min: 3, actual: 2}}
* ```
*
* @returns A validator function that returns an error map with the
* `min` property if the validation check fails, otherwise `null`.
*
* @see {@link updateValueAndValidity()}
*
*/
static min(min) {
return minValidator(min);
}
/**
* @description
* Validator that requires the control's value to be less than or equal to the provided number.
*
* @usageNotes
*
* ### Validate against a maximum of 15
*
* ```typescript
* const control = new FormControl(16, Validators.max(15));
*
* console.log(control.errors); // {max: {max: 15, actual: 16}}
* ```
*
* @returns A validator function that returns an error map with the
* `max` property if the validation check fails, otherwise `null`.
*
* @see {@link updateValueAndValidity()}
*
*/
static max(max) {
return maxValidator(max);
}
/**
* @description
* Validator that requires the control have a non-empty value.
*
* @usageNotes
*
* ### Validate that the field is non-empty
*
* ```typescript
* const control = new FormControl('', Validators.required);
*
* console.log(control.errors); // {required: true}
* ```
*
* @returns An error map with the `required` property
* if the validation check fails, otherwise `null`.
*
* @see {@link updateValueAndValidity()}
*
*/
static required(control) {
return requiredValidator(control);
}
/**
* @description
* Validator that requires the control's value be true. This validator is commonly
* used for required checkboxes.
*
* @usageNotes
*
* ### Validate that the field value is true
*
* ```typescript
* const control = new FormControl('some value', Validators.requiredTrue);
*
* console.log(control.errors); // {required: true}
* ```
*
* @returns An error map that contains the `required` property
* set to `true` if the validation check fails, otherwise `null`.
*
* @see {@link updateValueAndValidity()}
*
*/
static requiredTrue(control) {
return requiredTrueValidator(control);
}
/**
* @description
* Validator that requires the control's value pass an email validation test.
*
* Tests the value using a [regular
* expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
* pattern suitable for common use cases. The pattern is based on the definition of a valid email
* address in the [WHATWG HTML
* specification](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address) with
* some enhancements to incorporate more RFC rules (such as rules related to domain names and the
* lengths of different parts of the address).
*
* The differences from the WHATWG version include:
* - Disallow `local-part` (the part before the `@` symbol) to begin or end with a period (`.`).
* - Disallow `local-part` to be longer than 64 characters.
* - Disallow the whole address to be longer than 254 characters.
*
* If this pattern does not satisfy your business needs, you can use `Validators.pattern()` to
* validate the value against a different pattern.
*
* @usageNotes
*
* ### Validate that the field matches a valid email pattern
*
* ```typescript
* const control = new FormControl('bad@', Validators.email);
*
* console.log(control.errors); // {email: true}
* ```
*
* @returns An error map with the `email` property
* if the validation check fails, otherwise `null`.
*
* @see {@link updateValueAndValidity()}
*
*/
static email(control) {
return emailValidator(control);
}
/**
* @description
* Validator that requires the length of the control's value to be greater than or equal
* to the provided minimum length. This validator is also provided by default if you use the
* the HTML5 `minlength` attribute. Note that the `minLength` validator is intended to be used
* only for types that have a numeric `length` property, such as strings or arrays. The
* `minLength` validator logic is also not invoked for values when their `length` property is 0
* (for example in case of an empty string or an empty array), to support optional controls. You
* can use the standard `required` validator if empty values should not be considered valid.
*
* @usageNotes
*
* ### Validate that the field has a minimum of 3 characters
*
* ```typescript
* const control = new FormControl('ng', Validators.minLength(3));
*
* console.log(control.errors); // {minlength: {requiredLength: 3, actualLength: 2}}
* ```
*
* ```html
* <input minlength="5">
* ```
*
* @returns A validator function that returns an error map with the
* `minlength` property if the validation check fails, otherwise `null`.
*
* @see {@link updateValueAndValidity()}
*
*/
static minLength(minLength) {
return minLengthValidator(minLength);
}
/**
* @description
* Validator that requires the length of the control's value to be less than or equal
* to the provided maximum length. This validator is also provided by default if you use the
* the HTML5 `maxlength` attribute. Note that the `maxLength` validator is intended to be used
* only for types that have a numeric `length` property, such as strings or arrays.
*
* @usageNotes
*
* ### Validate that the field has maximum of 5 characters
*
* ```typescript
* const control = new FormControl('Angular', Validators.maxLength(5));
*
* console.log(control.errors); // {maxlength: {requiredLength: 5, actualLength: 7}}
* ```
*
* ```html
* <input maxlength="5">
* ```
*
* @returns A validator function that returns an error map with the
* `maxlength` property if the validation check fails, otherwise `null`.
*
* @see {@link updateValueAndValidity()}
*
*/
static maxLength(maxLength) {
return maxLengthValidator(maxLength);
}
/**
* @description
* Validator that requires the control's value to match a regex pattern. This validator is also
* provided by default if you use the HTML5 `pattern` attribute.
*
* @usageNotes
*
* ### Validate that the field only contains letters or spaces
*
* ```typescript
* const control = new FormControl('1', Validators.pattern('[a-zA-Z ]*'));
*
* console.log(control.errors); // {pattern: {requiredPattern: '^[a-zA-Z ]*$', actualValue: '1'}}
* ```
*
* ```html
* <input pattern="[a-zA-Z ]*">
* ```
*
* ### Pattern matching with the global or sticky flag
*
* `RegExp` objects created with the `g` or `y` flags that are passed into `Validators.pattern`
* can produce different results on the same input when validations are run consecutively. This is
* due to how the behavior of `RegExp.prototype.test` is
* specified in [ECMA-262](https://tc39.es/ecma262/#sec-regexpbuiltinexec)
* (`RegExp` preserves the index of the last match when the global or sticky flag is used).
* Due to this behavior, it is recommended that when using
* `Validators.pattern` you **do not** pass in a `RegExp` object with either the global or sticky
* flag enabled.
*
* ```typescript
* // Not recommended (since the `g` flag is used)
* const controlOne = new FormControl('1', Validators.pattern(/foo/g));
*
* // Good
* const controlTwo = new FormControl('1', Validators.pattern(/foo/));
* ```
*
* @param pattern A regular expression to be used as is to test the values, or a string.
* If a string is passed, the `^` character is prepended and the `$` character is
* appended to the provided string (if not already present), and the resulting regular
* expression is used to test the values.
*
* @returns A validator function that returns an error map with the
* `pattern` property if the validation check fails, otherwise `null`.
*
* @see {@link updateValueAndValidity()}
*
*/
static pattern(pattern) {
return patternValidator(pattern);
}
/**
* @description
* Validator that performs no operation.
*
* @see {@link updateValueAndValidity()}
*
*/
static nullValidator(control) {
return nullValidator(control);
}
static compose(validators) {
return compose(validators);
}
/**
* @description
* Compose multiple async validators into a single function that returns the union
* of the individual error objects for the provided control.
*
* @returns A validator function that returns an error map with the
* merged error objects of the async validators if the validation check fails, otherwise `null`.
*
* @see {@link updateValueAndValidity()}
*
*/
static composeAsync(validators) {
return composeAsync(validators);
}
}
/**
* Validator that requires the control's value to be greater than or equal to the provided number.
* See `Validators.min` for additional information.
*/
function minValidator(min) {
return (control) => {
if (isEmptyInputValue(control.value) || isEmptyInputValue(min)) {
return null; // don't validate empty values to allow optional controls
}
const value = parseFloat(control.value);
// Controls with NaN values after parsing should be treated as not having a
// minimum, per the HTML forms spec: https://www.w3.org/TR/html5/forms.html#attr-input-min
return !isNaN(value) && value < min ? { 'min': { 'min': min, 'actual': control.value } } : null;
};
}
/**
* Validator that requires the control's value to be less than or equal to the provided number.
* See `Validators.max` for additional information.
*/
function maxValidator(max) {
return (control) => {
if (isEmptyInputValue(control.value) || isEmptyInputValue(max)) {
return null; // don't validate empty values to allow optional controls
}
const value = parseFloat(control.value);
// Controls with NaN values after parsing should be treated as not having a
// maximum, per the HTML forms spec: https://www.w3.org/TR/html5/forms.html#attr-input-max
return !isNaN(value) && value > max ? { 'max': { 'max': max, 'actual': control.value } } : null;
};
}
/**
* Validator that requires the control have a non-empty value.
* See `Validators.required` for additional information.
*/
function requiredValidator(control) {
return isEmptyInputValue(control.value) ? { 'required': true } : null;
}
/**
* Validator that requires the control's value be true. This validator is commonly
* used for required checkboxes.
* See `Validators.requiredTrue` for additional information.
*/
function requiredTrueValidator(control) {
return control.value === true ? null : { 'required': true };
}
/**
* Validator that requires the control's value pass an email validation test.
* See `Validators.email` for additional information.
*/
function emailValidator(control) {
if (isEmptyInputValue(control.value)) {
return null; // don't validate empty values to allow optional controls
}
return EMAIL_REGEXP.test(control.value) ? null : { 'email': true };
}
/**
* Validator that requires the length of the control's value to be greater than or equal
* to the provided minimum length. See `Validators.minLength` for additional information.
*/
function minLengthValidator(minLength) {
return (control) => {
if (isEmptyInputValue(control.value) || !hasValidLength(control.value)) {
// don't validate empty values to allow optional controls
// don't validate values without `length` property
return null;
}
return control.value.length < minLength ?
{ 'minlength': { 'requiredLength': minLength, 'actualLength': control.value.length } } :
null;
};
}
/**
* Validator that requires the length of the control's value to be less than or equal
* to the provided maximum length. See `Validators.maxLength` for additional information.
*/
function maxLengthValidator(maxLength) {
return (control) => {
return hasValidLength(control.value) && control.value.length > maxLength ?
{ 'maxlength': { 'requiredLength': maxLength, 'actualLength': control.value.length } } :
null;
};
}
/**
* Validator that requires the control's value to match a regex pattern.
* See `Validators.pattern` for additional information.
*/
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; // don't validate empty values to allow optional controls
}
const value = control.value;
return regex.test(value) ? null :
{ 'pattern': { 'requiredPattern': regexStr, 'actualValue': value } };
};
}
/**
* Function that has `ValidatorFn` shape, but performs no operation.
*/
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.`;
// A synchronous validator will return object or null.
if (typeof value === 'object') {
errorMessage +=
' Are you using a synchronous validator where an async validator is expected?';
}
throw new ɵRuntimeError(-1101 /* RuntimeErrorCode.WRONG_VALIDATOR_RETURN_TYPE */, 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;
}
/**
* Given the list of validators that may contain both functions as well as classes, return the list
* of validator functions (convert validator classes into validator functions). This is needed to
* have consistent structure in validators list before composing them.
*
* @param validators The set of validators that may contain validators both in plain function form
* as well as represented as a validator class.
*/
function normalizeValidators(validators) {
return validators.map(validator => {
return isValidatorFn(validator) ?
validator :
((c) => validator.validate(c));
});
}
/**
* Merges synchronous validators into a single validator function.
* See `Validators.compose` for additional information.
*/
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));
};
}
/**
* Accepts a list of validators of different possible shapes (`Validator` and `ValidatorFn`),
* normalizes the list (converts everything to `ValidatorFn`) and merges them into a single
* validator function.
*/
function composeValidators(validators) {
return validators != null ? compose(normalizeValidators(validators)) : null;
}
/**
* Merges asynchronous validators into a single validator function.
* See `Validators.composeAsync` for additional information.
*/
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));
};
}
/**
* Accepts a list of async validators of different possible shapes (`AsyncValidator` and
* `AsyncValidatorFn`), normalizes the list (converts everything to `AsyncValidatorFn`) and merges
* them into a single validator function.
*/
function composeAsyncValidators(validators) {
return validators != null ? composeAsync(normalizeValidators(validators)) :
null;
}
/**
* Merges raw control validators with a given directive validator and returns the combined list of
* validators as an array.
*/
function mergeValidators(controlValidators, dirValidator) {
if (controlValidators === null)
return [dirValidator];
return Array.isArray(controlValidators) ? [...controlValidators, dirValidator] :
[controlValidators, dirValidator];
}
/**
* Retrieves the list of raw synchronous validators attached to a given control.
*/
function getControlValidators(control) {
return control._rawValidators;
}
/**
* Retrieves the list of raw asynchronous validators attached to a given control.
*/
function getControlAsyncValidators(control) {
return control._rawAsyncValidators;
}
/**
* Accepts a singleton validator, an array, or null, and returns an array type with the provided
* validators.
*
* @param validators A validator, validators, or null.
* @returns A validators array.
*/
function makeValidatorsArray(validators) {
if (!validators)
return [];
return Array.isArray(validators) ? validators : [validators];
}
/**
* Determines whether a validator or validators array has a given validator.
*
* @param validators The validator or validators to compare against.
* @param validator The validator to check.
* @returns Whether the validator is present.
*/
function hasValidator(validators, validator) {
return Array.isArray(validators) ? validators.includes(validator) : validators === validator;
}
/**
* Combines two arrays of validators into one. If duplicates are provided, only one will be added.
*
* @param validators The new validators.
* @param currentValidators The base array of current validators.
* @returns An array of validators.
*/
function addValidators(validators, currentValidators) {
const current = makeValidatorsArray(currentValidators);
const validatorsToAdd = makeValidatorsArray(validators);
validatorsToAdd.forEach((v) => {
// Note: if there are duplicate entries in the new validators array,
// only the first one would be added to the current list of validators.
// Duplicate ones would be ignored since `hasValidator` would detect
// the presence of a validator function and we update the current list in place.
if (!hasValidator(current, v)) {
current.push(v);
}
});
return current;
}
function removeValidators(validators, currentValidators) {
return makeValidatorsArray(currentValidators).filter(v => !hasValidator(validators, v));
}
/**
* @description
* Base class for control directives.
*
* This class is only used internally in the `ReactiveFormsModule` and the `FormsModule`.
*
* @publicApi
*/
class AbstractControlDirective {
constructor() {
/**
* Set of synchronous validators as they were provided while calling `setValidators` function.
* @internal
*/
this._rawValidators = [];
/**
* Set of asynchronous validators as they were provided while calling `setAsyncValidators`
* function.
* @internal
*/
this._rawAsyncValidators = [];
/*
* The set of callbacks to be invoked when directive instance is being destroyed.
*/
this._onDestroyCallbacks = [];
}
/**
* @description
* Reports the value of the control if it is present, otherwise null.
*/
get value() {
return this.control ? this.control.value : null;
}
/**
* @description
* Reports whether the control is valid. A control is considered valid if no
* validation errors exist with the current value.
* If the control is not present, null is returned.
*/
get valid() {
return this.control ? this.control.valid : null;
}
/**
* @description
* Reports whether the control is invalid, meaning that an error exists in the input value.
* If the control is not present, null is returned.
*/
get invalid() {
return this.control ? this.control.invalid : null;
}
/**
* @description
* Reports whether a control is pending, meaning that async validation is occurring and
* errors are not yet available for the input value. If the control is not present, null is
* returned.
*/
get pending() {
return this.control ? this.control.pending : null;
}
/**
* @description
* Reports whether the control is disabled, meaning that the control is disabled
* in the UI and is exempt from validation checks and excluded from aggregate
* values of ancestor controls. If the control is not present, null is returned.
*/
get disabled() {
return this.control ? this.control.disabled : null;
}
/**
* @description
* Reports whether the control is enabled, meaning that the control is included in ancestor
* calculations of validity or value. If the control is not present, null is returned.
*/
get enabled() {
return this.control ? this.control.enabled : null;
}
/**
* @description
* Reports the control's validation errors. If the control is not present, null is returned.
*/
get errors() {
return this.control ? this.control.errors : null;
}
/**
* @description
* Reports whether the control is pristine, meaning that the user has not yet changed
* the value in the UI. If the control is not present, null is returned.
*/
get pristine() {
return this.control ? this.control.pristine : null;
}
/**
* @description
* Reports whether the control is dirty, meaning that the user has changed
* the value in the UI. If the control is not present, null is returned.
*/
get dirty() {
return this.control ? this.control.dirty : null;
}
/**
* @description
* Reports whether the control is touched, meaning that the user has triggered
* a `blur` event on it. If the control is not present, null is returned.
*/
get touched() {
return this.control ? this.control.touched : null;
}
/**
* @description
* Reports the validation status of the control. Possible values include:
* 'VALID', 'INVALID', 'DISABLED', and 'PENDING'.
* If the control is not present, null is returned.
*/
get status() {
return this.control ? this.control.status : null;
}
/**
* @description
* Reports whether the control is untouched, meaning that the user has not yet triggered
* a `blur` event on it. If the control is not present, null is returned.
*/
get untouched() {
return this.control ? this.control.untouched : null;
}
/**
* @description
* Returns a multicasting observable that emits a validation status whenever it is
* calculated for the control. If the control is not present, null is returned.
*/
get statusChanges() {
return this.control ? this.control.statusChanges : null;
}
/**
* @description
* Returns a multicasting observable of value changes for the control that emits every time the
* value of the control changes in the UI or programmatically.
* If the control is not present, null is returned.
*/
get valueChanges() {
return this.control ? this.control.valueChanges : null;
}
/**
* @description
* Returns an array that represents the path from the top-level form to this control.
* Each index is the string name of the control on that level.
*/
get path() {
return null;
}
/**
* Sets synchronous validators for this directive.
* @internal
*/
_setValidators(validators) {
this._rawValidators = validators || [];
this._composedValidatorFn = composeValidators(this._rawValidators);
}
/**
* Sets asynchronous validators for this directive.
* @internal
*/
_setAsyncValidators(validators) {
this._rawAsyncValidators = validators || [];
this._composedAsyncValidatorFn = composeAsyncValidators(this._rawAsyncValidators);
}
/**
* @description
* Synchronous validator function composed of all the synchronous validators registered with this
* directive.
*/
get validator() {
return this._composedValidatorFn || null;
}
/**
* @description
* Asynchronous validator function composed of all the asynchronous validators registered with
* this directive.
*/
get asyncValidator() {
return this._composedAsyncValidatorFn || null;
}
/**
* Internal function to register callbacks that should be invoked
* when directive instance is being destroyed.
* @internal
*/
_registerOnDestroy(fn) {
this._onDestroyCallbacks.push(fn);
}
/**
* Internal function to invoke all registered "on destroy" callbacks.
* Note: calling this function also clears the list of callbacks.
* @internal
*/
_invokeOnDestroyCallbacks() {
this._onDestroyCallbacks.forEach(fn => fn());
this._onDestroyCallbacks = [];
}
/**
* @description
* Resets the control with the provided value if the control is present.
*/
reset(value = undefined) {
if (this.control)
this.control.reset(value);
}
/**
* @description
* Reports whether the control with the given path has the error specified.
*
* @param errorCode The code of the error to check
* @param path A list of control names that designates how to move from the current control
* to the control that should be queried for errors.
*
* @usageNotes
* For example, for the following `FormGroup`:
*
* ```
* form = new FormGroup({
* address: new FormGroup({ street: new FormControl() })
* });
* ```
*
* The path to the 'street' control from the root form would be 'address' -> 'street'.
*
* It can be provided to this method in one of two formats:
*
* 1. An array of string control names, e.g. `['address', 'street']`
* 1. A period-delimited list of control names in one string, e.g. `'address.street'`
*
* If no path is given, this method checks for the error on the current control.
*
* @returns whether the given error is present in the control at the given path.
*
* If the control is not present, false is returned.
*/
hasError(errorCode, path) {
return this.control ? this.control.hasError(errorCode, path) : false;
}
/**
* @description
* Reports error data for the control with the given path.
*
* @param errorCode The code of the error to check
* @param path A list of control names that designates how to move from the current control
* to the control that should be queried for errors.
*
* @usageNotes
* For example, for the following `FormGroup`:
*
* ```
* form = new FormGroup({
* address: new FormGroup({ street: new FormControl() })
* });
* ```
*
* The path to the 'street' control from the root form would be 'address' -> 'street'.
*
* It can be provided to this method in one of two formats:
*
* 1. An array of string control names, e.g. `['address', 'street']`
* 1. A period-delimited list of control names in one string, e.g. `'address.street'`
*
* @returns error data for that particular error. If the control or error is not present,
* null is returned.
*/
getError(errorCode, path) {
return this.control ? this.control.getError(errorCode, path) : null;
}
}
/**
* @description
* A base class for directives that contain multiple registered instances of `NgControl`.
* Only used by the forms module.
*
* @publicApi
*/
class ControlContainer extends AbstractControlDirective {
/**
* @description
* The top-level form directive for the control.
*/
get formDirective() {
return null;
}
/**
* @description
* The path to this group.
*/
get path() {
return null;
}
}
/**
* @description
* A base class that all `FormControl`-based directives extend. It binds a `FormControl`
* object to a DOM element.
*
* @publicApi
*/
class NgControl extends AbstractControlDirective {
constructor() {
super(...arguments);
/**
* @description
* The parent form for the control.
*
* @internal
*/
this._parent = null;
/**
* @description
* The name for the control
*/
this.name = null;
/**
* @description
* The value accessor for the control
*/
this.valueAccessor = null;
}
}
// DO NOT REFACTOR!
// Each status is represented by a separate function to make sure that
// advanced Closure Compiler optimizations related to property renaming
// can work correctly.
class AbstractControlStatus {
constructor(cd) {
this._cd = cd;
}
get isTouched() {
return !!this._cd?.control?.touched;
}
get isUntouched() {
return !!this._cd?.control?.untouched;
}
get isPristine() {
return !!this._cd?.control?.pristine;
}
get isDirty() {
return !!this._cd?.control?.dirty;
}
get isValid() {
return !!this._cd?.control?.valid;
}
get isInvalid() {
return !!this._cd?.control?.invalid;
}
get isPending() {
return !!this._cd?.control?.pending;
}
get isSubmitted() {
// We check for the `submitted` field from `NgForm` and `FormGroupDirective` classes, but
// we avoid instanceof checks to prevent non-tree-shakable references to those types.
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',
};
const ngGroupStatusHost = {
...ngControlStatusHost,
'[class.ng-submitted]': 'isSubmitted',
};
/**
* @description
* Directive automatically applied to Angular form controls that sets CSS classes
* based on control status.
*
* @usageNotes
*
* ### CSS classes applied
*
* The following classes are applied as the properties become true:
*
* * ng-valid
* * ng-invalid
* * ng-pending
* * ng-pristine
* * ng-dirty
* * ng-untouched
* * ng-touched
*
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
class NgControlStatus extends AbstractControlStatus {
constructor(cd) {
super(cd);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.3", ngImport: i0, type: NgControlStatus, deps: [{ token: NgControl, self: true }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.2.3", type: NgControlStatus, 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: "17.2.3", ngImport: i0, type: NgControlStatus, decorators: [{
type: Directive,
args: [{ selector: '[formControlName],[ngModel],[formControl]', host: ngControlStatusHost }]
}], ctorParameters: () => [{ type: NgControl, decorators: [{
type: Self
}] }] });
/**
* @description
* Directive automatically applied to Angular form groups that sets CSS classes
* based on control status (valid/invalid/dirty/etc). On groups, this includes the additional
* class ng-submitted.
*
* @see {@link NgControlStatus}
*
* @ngModule ReactiveFormsModule
* @ngModule FormsModule
* @publicApi
*/
class NgControlStatusGroup extends AbstractControlStatus {
constructor(cd) {
super(cd);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.2.3", ngImport: i0, type: NgControlStatusGr