@angular/forms
Version:
Angular - directives and services for creating forms
669 lines (644 loc) • 18.9 kB
JavaScript
/**
* @license Angular v21.0.5
* (c) 2010-2025 Google LLC. https://angular.dev/
* License: MIT
*/
import * as i0 from '@angular/core';
import { InjectionToken, inject, ElementRef, Injector, input, computed, ɵCONTROL as _CONTROL, effect, Directive, ɵɵcontrolCreate as __controlCreate, ɵcontrolUpdate as _controlUpdate, ɵisPromise as _isPromise, resource } from '@angular/core';
import { Validators, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
import { assertPathIsCurrent, FieldPathNode, isArray, addDefaultField, metadata, createMetadataKey, MAX, MAX_LENGTH, MIN, MIN_LENGTH, PATTERN, REQUIRED, createManagedMetadataKey, DEBOUNCER } from './_structure-chunk.mjs';
export { MetadataKey, MetadataReducer, apply, applyEach, applyWhen, applyWhenValue, form, schema, submit } from './_structure-chunk.mjs';
import { httpResource } from '@angular/common/http';
import '@angular/core/primitives/signals';
const SIGNAL_FORMS_CONFIG = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'SIGNAL_FORMS_CONFIG' : '');
function provideSignalFormsConfig(config) {
return [{
provide: SIGNAL_FORMS_CONFIG,
useValue: config
}];
}
class InteropNgControl {
field;
constructor(field) {
this.field = field;
}
control = this;
get value() {
return this.field().value();
}
get valid() {
return this.field().valid();
}
get invalid() {
return this.field().invalid();
}
get pending() {
return this.field().pending();
}
get disabled() {
return this.field().disabled();
}
get enabled() {
return !this.field().disabled();
}
get errors() {
const errors = this.field().errors();
if (errors.length === 0) {
return null;
}
const errObj = {};
for (const error of errors) {
errObj[error.kind] = error;
}
return errObj;
}
get pristine() {
return !this.field().dirty();
}
get dirty() {
return this.field().dirty();
}
get touched() {
return this.field().touched();
}
get untouched() {
return !this.field().touched();
}
get status() {
if (this.field().disabled()) {
return 'DISABLED';
}
if (this.field().valid()) {
return 'VALID';
}
if (this.field().invalid()) {
return 'INVALID';
}
if (this.field().pending()) {
return 'PENDING';
}
throw Error('AssertionError: unknown form control status');
}
valueAccessor = null;
hasValidator(validator) {
if (validator === Validators.required) {
return this.field().required();
}
return false;
}
updateValueAndValidity() {}
}
const FIELD = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'FIELD' : '');
const controlInstructions = {
create: __controlCreate,
update: _controlUpdate
};
class Field {
element = inject(ElementRef).nativeElement;
injector = inject(Injector);
field = input.required(...(ngDevMode ? [{
debugName: "field"
}] : []));
state = computed(() => this.field()(), ...(ngDevMode ? [{
debugName: "state"
}] : []));
[_CONTROL] = controlInstructions;
config = inject(SIGNAL_FORMS_CONFIG, {
optional: true
});
classes = Object.entries(this.config?.classes ?? {}).map(([className, computation]) => [className, computed(() => computation(this.state()))]);
controlValueAccessors = inject(NG_VALUE_ACCESSOR, {
optional: true,
self: true
});
interopNgControl;
get ɵinteropControl() {
return this.controlValueAccessors?.[0] ?? this.interopNgControl?.valueAccessor ?? undefined;
}
getOrCreateNgControl() {
return this.interopNgControl ??= new InteropNgControl(this.state);
}
ɵregister() {
effect(onCleanup => {
const fieldNode = this.state();
fieldNode.nodeState.fieldBindings.update(controls => [...controls, this]);
onCleanup(() => {
fieldNode.nodeState.fieldBindings.update(controls => controls.filter(c => c !== this));
});
}, {
injector: this.injector
});
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.5",
ngImport: i0,
type: Field,
deps: [],
target: i0.ɵɵFactoryTarget.Directive
});
static ɵdir = i0.ɵɵngDeclareDirective({
minVersion: "17.1.0",
version: "21.0.5",
type: Field,
isStandalone: true,
selector: "[field]",
inputs: {
field: {
classPropertyName: "field",
publicName: "field",
isSignal: true,
isRequired: true,
transformFunction: null
}
},
providers: [{
provide: FIELD,
useExisting: Field
}, {
provide: NgControl,
useFactory: () => inject(Field).getOrCreateNgControl()
}],
ngImport: i0
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.5",
ngImport: i0,
type: Field,
decorators: [{
type: Directive,
args: [{
selector: '[field]',
providers: [{
provide: FIELD,
useExisting: Field
}, {
provide: NgControl,
useFactory: () => inject(Field).getOrCreateNgControl()
}]
}]
}],
propDecorators: {
field: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "field",
required: true
}]
}]
}
});
function disabled(path, logic) {
assertPathIsCurrent(path);
const pathNode = FieldPathNode.unwrapFieldPath(path);
pathNode.builder.addDisabledReasonRule(ctx => {
let result = true;
if (typeof logic === 'string') {
result = logic;
} else if (logic) {
result = logic(ctx);
}
if (typeof result === 'string') {
return {
field: ctx.field,
message: result
};
}
return result ? {
field: ctx.field
} : undefined;
});
}
function hidden(path, logic) {
assertPathIsCurrent(path);
const pathNode = FieldPathNode.unwrapFieldPath(path);
pathNode.builder.addHiddenRule(logic);
}
function readonly(path, logic = () => true) {
assertPathIsCurrent(path);
const pathNode = FieldPathNode.unwrapFieldPath(path);
pathNode.builder.addReadonlyRule(logic);
}
function requiredError(options) {
return new RequiredValidationError(options);
}
function minError(min, options) {
return new MinValidationError(min, options);
}
function maxError(max, options) {
return new MaxValidationError(max, options);
}
function minLengthError(minLength, options) {
return new MinLengthValidationError(minLength, options);
}
function maxLengthError(maxLength, options) {
return new MaxLengthValidationError(maxLength, options);
}
function patternError(pattern, options) {
return new PatternValidationError(pattern, options);
}
function emailError(options) {
return new EmailValidationError(options);
}
function standardSchemaError(issue, options) {
return new StandardSchemaValidationError(issue, options);
}
function customError(obj) {
return new CustomValidationError(obj);
}
class CustomValidationError {
__brand = undefined;
kind = '';
field;
message;
constructor(options) {
if (options) {
Object.assign(this, options);
}
}
}
class _NgValidationError {
__brand = undefined;
kind = '';
field;
message;
constructor(options) {
if (options) {
Object.assign(this, options);
}
}
}
class RequiredValidationError extends _NgValidationError {
kind = 'required';
}
class MinValidationError extends _NgValidationError {
min;
kind = 'min';
constructor(min, options) {
super(options);
this.min = min;
}
}
class MaxValidationError extends _NgValidationError {
max;
kind = 'max';
constructor(max, options) {
super(options);
this.max = max;
}
}
class MinLengthValidationError extends _NgValidationError {
minLength;
kind = 'minLength';
constructor(minLength, options) {
super(options);
this.minLength = minLength;
}
}
class MaxLengthValidationError extends _NgValidationError {
maxLength;
kind = 'maxLength';
constructor(maxLength, options) {
super(options);
this.maxLength = maxLength;
}
}
class PatternValidationError extends _NgValidationError {
pattern;
kind = 'pattern';
constructor(pattern, options) {
super(options);
this.pattern = pattern;
}
}
class EmailValidationError extends _NgValidationError {
kind = 'email';
}
class StandardSchemaValidationError extends _NgValidationError {
issue;
kind = 'standardSchema';
constructor(issue, options) {
super(options);
this.issue = issue;
}
}
const NgValidationError = _NgValidationError;
function getLengthOrSize(value) {
const v = value;
return typeof v.length === 'number' ? v.length : v.size;
}
function getOption(opt, ctx) {
return opt instanceof Function ? opt(ctx) : opt;
}
function isEmpty(value) {
if (typeof value === 'number') {
return isNaN(value);
}
return value === '' || value === false || value == null;
}
function isPlainError(error) {
return typeof error === 'object' && (Object.getPrototypeOf(error) === Object.prototype || Object.getPrototypeOf(error) === null);
}
function ensureCustomValidationError(error) {
if (isPlainError(error)) {
return customError(error);
}
return error;
}
function ensureCustomValidationResult(result) {
if (result === null || result === undefined) {
return result;
}
if (isArray(result)) {
return result.map(ensureCustomValidationError);
}
return ensureCustomValidationError(result);
}
function validate(path, logic) {
assertPathIsCurrent(path);
const pathNode = FieldPathNode.unwrapFieldPath(path);
pathNode.builder.addSyncErrorRule(ctx => {
return ensureCustomValidationResult(addDefaultField(logic(ctx), ctx.field));
});
}
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])?)*$/;
function email(path, config) {
validate(path, ctx => {
if (isEmpty(ctx.value())) {
return undefined;
}
if (!EMAIL_REGEXP.test(ctx.value())) {
if (config?.error) {
return getOption(config.error, ctx);
} else {
return emailError({
message: getOption(config?.message, ctx)
});
}
}
return undefined;
});
}
function max(path, maxValue, config) {
const MAX_MEMO = metadata(path, createMetadataKey(), ctx => typeof maxValue === 'number' ? maxValue : maxValue(ctx));
metadata(path, MAX, ({
state
}) => state.metadata(MAX_MEMO)());
validate(path, ctx => {
if (isEmpty(ctx.value())) {
return undefined;
}
const max = ctx.state.metadata(MAX_MEMO)();
if (max === undefined || Number.isNaN(max)) {
return undefined;
}
const value = ctx.value();
const numValue = !value && value !== 0 ? NaN : Number(value);
if (numValue > max) {
if (config?.error) {
return getOption(config.error, ctx);
} else {
return maxError(max, {
message: getOption(config?.message, ctx)
});
}
}
return undefined;
});
}
function maxLength(path, maxLength, config) {
const MAX_LENGTH_MEMO = metadata(path, createMetadataKey(), ctx => typeof maxLength === 'number' ? maxLength : maxLength(ctx));
metadata(path, MAX_LENGTH, ({
state
}) => state.metadata(MAX_LENGTH_MEMO)());
validate(path, ctx => {
if (isEmpty(ctx.value())) {
return undefined;
}
const maxLength = ctx.state.metadata(MAX_LENGTH_MEMO)();
if (maxLength === undefined) {
return undefined;
}
if (getLengthOrSize(ctx.value()) > maxLength) {
if (config?.error) {
return getOption(config.error, ctx);
} else {
return maxLengthError(maxLength, {
message: getOption(config?.message, ctx)
});
}
}
return undefined;
});
}
function min(path, minValue, config) {
const MIN_MEMO = metadata(path, createMetadataKey(), ctx => typeof minValue === 'number' ? minValue : minValue(ctx));
metadata(path, MIN, ({
state
}) => state.metadata(MIN_MEMO)());
validate(path, ctx => {
if (isEmpty(ctx.value())) {
return undefined;
}
const min = ctx.state.metadata(MIN_MEMO)();
if (min === undefined || Number.isNaN(min)) {
return undefined;
}
const value = ctx.value();
const numValue = !value && value !== 0 ? NaN : Number(value);
if (numValue < min) {
if (config?.error) {
return getOption(config.error, ctx);
} else {
return minError(min, {
message: getOption(config?.message, ctx)
});
}
}
return undefined;
});
}
function minLength(path, minLength, config) {
const MIN_LENGTH_MEMO = metadata(path, createMetadataKey(), ctx => typeof minLength === 'number' ? minLength : minLength(ctx));
metadata(path, MIN_LENGTH, ({
state
}) => state.metadata(MIN_LENGTH_MEMO)());
validate(path, ctx => {
if (isEmpty(ctx.value())) {
return undefined;
}
const minLength = ctx.state.metadata(MIN_LENGTH_MEMO)();
if (minLength === undefined) {
return undefined;
}
if (getLengthOrSize(ctx.value()) < minLength) {
if (config?.error) {
return getOption(config.error, ctx);
} else {
return minLengthError(minLength, {
message: getOption(config?.message, ctx)
});
}
}
return undefined;
});
}
function pattern(path, pattern, config) {
const PATTERN_MEMO = metadata(path, createMetadataKey(), ctx => pattern instanceof RegExp ? pattern : pattern(ctx));
metadata(path, PATTERN, ({
state
}) => state.metadata(PATTERN_MEMO)());
validate(path, ctx => {
if (isEmpty(ctx.value())) {
return undefined;
}
const pattern = ctx.state.metadata(PATTERN_MEMO)();
if (pattern === undefined) {
return undefined;
}
if (!pattern.test(ctx.value())) {
if (config?.error) {
return getOption(config.error, ctx);
} else {
return patternError(pattern, {
message: getOption(config?.message, ctx)
});
}
}
return undefined;
});
}
function required(path, config) {
const REQUIRED_MEMO = metadata(path, createMetadataKey(), ctx => config?.when ? config.when(ctx) : true);
metadata(path, REQUIRED, ({
state
}) => state.metadata(REQUIRED_MEMO)());
validate(path, ctx => {
if (ctx.state.metadata(REQUIRED_MEMO)() && isEmpty(ctx.value())) {
if (config?.error) {
return getOption(config.error, ctx);
} else {
return requiredError({
message: getOption(config?.message, ctx)
});
}
}
return undefined;
});
}
function validateAsync(path, opts) {
assertPathIsCurrent(path);
const pathNode = FieldPathNode.unwrapFieldPath(path);
const RESOURCE = createManagedMetadataKey(opts.factory);
metadata(path, RESOURCE, ctx => {
const node = ctx.stateOf(path);
const validationState = node.validationState;
if (validationState.shouldSkipValidation() || !validationState.syncValid()) {
return undefined;
}
return opts.params(ctx);
});
pathNode.builder.addAsyncErrorRule(ctx => {
const res = ctx.state.metadata(RESOURCE);
let errors;
switch (res.status()) {
case 'idle':
return undefined;
case 'loading':
case 'reloading':
return 'pending';
case 'resolved':
case 'local':
if (!res.hasValue()) {
return undefined;
}
errors = opts.onSuccess(res.value(), ctx);
return addDefaultField(errors, ctx.field);
case 'error':
errors = opts.onError(res.error(), ctx);
return addDefaultField(errors, ctx.field);
}
});
}
function validateTree(path, logic) {
assertPathIsCurrent(path);
const pathNode = FieldPathNode.unwrapFieldPath(path);
pathNode.builder.addSyncTreeErrorRule(ctx => addDefaultField(logic(ctx), ctx.field));
}
function validateStandardSchema(path, schema) {
const VALIDATOR_MEMO = metadata(path, createMetadataKey(), ({
value
}) => {
return schema['~standard'].validate(value());
});
validateTree(path, ({
state,
fieldTreeOf
}) => {
const result = state.metadata(VALIDATOR_MEMO)();
if (_isPromise(result)) {
return [];
}
return result?.issues?.map(issue => standardIssueToFormTreeError(fieldTreeOf(path), issue)) ?? [];
});
validateAsync(path, {
params: ({
state
}) => {
const result = state.metadata(VALIDATOR_MEMO)();
return _isPromise(result) ? result : undefined;
},
factory: params => {
return resource({
params,
loader: async ({
params
}) => (await params)?.issues ?? []
});
},
onSuccess: (issues, {
fieldTreeOf
}) => {
return issues.map(issue => standardIssueToFormTreeError(fieldTreeOf(path), issue));
},
onError: () => {}
});
}
function standardIssueToFormTreeError(field, issue) {
let target = field;
for (const pathPart of issue.path ?? []) {
const pathKey = typeof pathPart === 'object' ? pathPart.key : pathPart;
target = target[pathKey];
}
return addDefaultField(standardSchemaError(issue, {
message: issue.message
}), target);
}
function validateHttp(path, opts) {
validateAsync(path, {
params: opts.request,
factory: request => httpResource(request, opts.options),
onSuccess: opts.onSuccess,
onError: opts.onError
});
}
function debounce(path, durationOrDebouncer) {
assertPathIsCurrent(path);
const pathNode = FieldPathNode.unwrapFieldPath(path);
const debouncer = typeof durationOrDebouncer === 'function' ? durationOrDebouncer : durationOrDebouncer > 0 ? debounceForDuration(durationOrDebouncer) : immediate;
pathNode.builder.addMetadataRule(DEBOUNCER, () => debouncer);
}
function debounceForDuration(durationInMilliseconds) {
return (_context, abortSignal) => {
return new Promise(resolve => {
const timeoutId = setTimeout(resolve, durationInMilliseconds);
abortSignal.addEventListener('abort', () => clearTimeout(timeoutId));
});
};
}
function immediate() {}
export { CustomValidationError, EmailValidationError, FIELD, Field, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MaxLengthValidationError, MaxValidationError, MinLengthValidationError, MinValidationError, NgValidationError, PATTERN, PatternValidationError, REQUIRED, RequiredValidationError, StandardSchemaValidationError, createManagedMetadataKey, createMetadataKey, customError, debounce, disabled, email, emailError, hidden, max, maxError, maxLength, maxLengthError, metadata, min, minError, minLength, minLengthError, pattern, patternError, provideSignalFormsConfig, readonly, required, requiredError, standardSchemaError, validate, validateAsync, validateHttp, validateStandardSchema, validateTree };
//# sourceMappingURL=signals.mjs.map