@angular/forms
Version:
Angular - directives and services for creating forms
1,579 lines (1,554 loc) • 45.8 kB
JavaScript
/**
* @license Angular v21.0.6
* (c) 2010-2025 Google LLC. https://angular.dev/
* License: MIT
*/
import { untracked, ɵRuntimeError as _RuntimeError, computed, runInInjectionContext, Injector, linkedSignal, signal, APP_ID, effect, inject } from '@angular/core';
import { AbstractControl } from '@angular/forms';
import { SIGNAL } from '@angular/core/primitives/signals';
let boundPathDepth = 0;
function getBoundPathDepth() {
return boundPathDepth;
}
function setBoundPathDepthForResolution(fn, depth) {
return (...args) => {
try {
boundPathDepth = depth;
return fn(...args);
} finally {
boundPathDepth = 0;
}
};
}
function shortCircuitFalse(value) {
return !value;
}
function shortCircuitTrue(value) {
return value;
}
function getInjectorFromOptions(options) {
if (options.kind === 'root') {
return options.fieldManager.injector;
}
return options.parent.structure.root.structure.injector;
}
function isArray(value) {
return Array.isArray(value);
}
function isObject(value) {
return (typeof value === 'object' || typeof value === 'function') && value != null;
}
const DYNAMIC = Symbol();
const IGNORED = Symbol();
class AbstractLogic {
predicates;
fns = [];
constructor(predicates) {
this.predicates = predicates;
}
push(logicFn) {
this.fns.push(wrapWithPredicates(this.predicates, logicFn));
}
mergeIn(other) {
const fns = this.predicates ? other.fns.map(fn => wrapWithPredicates(this.predicates, fn)) : other.fns;
this.fns.push(...fns);
}
}
class BooleanOrLogic extends AbstractLogic {
get defaultValue() {
return false;
}
compute(arg) {
return this.fns.some(f => {
const result = f(arg);
return result && result !== IGNORED;
});
}
}
class ArrayMergeIgnoreLogic extends AbstractLogic {
ignore;
static ignoreNull(predicates) {
return new ArrayMergeIgnoreLogic(predicates, e => e === null);
}
constructor(predicates, ignore) {
super(predicates);
this.ignore = ignore;
}
get defaultValue() {
return [];
}
compute(arg) {
return this.fns.reduce((prev, f) => {
const value = f(arg);
if (value === undefined || value === IGNORED) {
return prev;
} else if (isArray(value)) {
return [...prev, ...(this.ignore ? value.filter(e => !this.ignore(e)) : value)];
} else {
if (this.ignore && this.ignore(value)) {
return prev;
}
return [...prev, value];
}
}, []);
}
}
class ArrayMergeLogic extends ArrayMergeIgnoreLogic {
constructor(predicates) {
super(predicates, undefined);
}
}
class MetadataMergeLogic extends AbstractLogic {
key;
get defaultValue() {
return this.key.reducer.getInitial();
}
constructor(predicates, key) {
super(predicates);
this.key = key;
}
compute(ctx) {
if (this.fns.length === 0) {
return this.key.reducer.getInitial();
}
let acc = this.key.reducer.getInitial();
for (let i = 0; i < this.fns.length; i++) {
const item = this.fns[i](ctx);
if (item !== IGNORED) {
acc = this.key.reducer.reduce(acc, item);
}
}
return acc;
}
}
function wrapWithPredicates(predicates, logicFn) {
if (predicates.length === 0) {
return logicFn;
}
return arg => {
for (const predicate of predicates) {
let predicateField = arg.stateOf(predicate.path);
const depthDiff = untracked(predicateField.structure.pathKeys).length - predicate.depth;
for (let i = 0; i < depthDiff; i++) {
predicateField = predicateField.structure.parent;
}
if (!predicate.fn(predicateField.context)) {
return IGNORED;
}
}
return logicFn(arg);
};
}
class LogicContainer {
predicates;
hidden;
disabledReasons;
readonly;
syncErrors;
syncTreeErrors;
asyncErrors;
metadata = new Map();
constructor(predicates) {
this.predicates = predicates;
this.hidden = new BooleanOrLogic(predicates);
this.disabledReasons = new ArrayMergeLogic(predicates);
this.readonly = new BooleanOrLogic(predicates);
this.syncErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
this.syncTreeErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
this.asyncErrors = ArrayMergeIgnoreLogic.ignoreNull(predicates);
}
hasMetadata(key) {
return this.metadata.has(key);
}
getMetadataKeys() {
return this.metadata.keys();
}
getMetadata(key) {
if (!this.metadata.has(key)) {
this.metadata.set(key, new MetadataMergeLogic(this.predicates, key));
}
return this.metadata.get(key);
}
mergeIn(other) {
this.hidden.mergeIn(other.hidden);
this.disabledReasons.mergeIn(other.disabledReasons);
this.readonly.mergeIn(other.readonly);
this.syncErrors.mergeIn(other.syncErrors);
this.syncTreeErrors.mergeIn(other.syncTreeErrors);
this.asyncErrors.mergeIn(other.asyncErrors);
for (const key of other.getMetadataKeys()) {
const metadataLogic = other.metadata.get(key);
this.getMetadata(key).mergeIn(metadataLogic);
}
}
}
class AbstractLogicNodeBuilder {
depth;
constructor(depth) {
this.depth = depth;
}
build() {
return new LeafLogicNode(this, [], 0);
}
}
class LogicNodeBuilder extends AbstractLogicNodeBuilder {
constructor(depth) {
super(depth);
}
current;
all = [];
addHiddenRule(logic) {
this.getCurrent().addHiddenRule(logic);
}
addDisabledReasonRule(logic) {
this.getCurrent().addDisabledReasonRule(logic);
}
addReadonlyRule(logic) {
this.getCurrent().addReadonlyRule(logic);
}
addSyncErrorRule(logic) {
this.getCurrent().addSyncErrorRule(logic);
}
addSyncTreeErrorRule(logic) {
this.getCurrent().addSyncTreeErrorRule(logic);
}
addAsyncErrorRule(logic) {
this.getCurrent().addAsyncErrorRule(logic);
}
addMetadataRule(key, logic) {
this.getCurrent().addMetadataRule(key, logic);
}
getChild(key) {
if (key === DYNAMIC) {
const children = this.getCurrent().children;
if (children.size > (children.has(DYNAMIC) ? 1 : 0)) {
this.current = undefined;
}
}
return this.getCurrent().getChild(key);
}
hasLogic(builder) {
if (this === builder) {
return true;
}
return this.all.some(({
builder: subBuilder
}) => subBuilder.hasLogic(builder));
}
mergeIn(other, predicate) {
if (predicate) {
this.all.push({
builder: other,
predicate: {
fn: setBoundPathDepthForResolution(predicate.fn, this.depth),
path: predicate.path
}
});
} else {
this.all.push({
builder: other
});
}
this.current = undefined;
}
getCurrent() {
if (this.current === undefined) {
this.current = new NonMergeableLogicNodeBuilder(this.depth);
this.all.push({
builder: this.current
});
}
return this.current;
}
static newRoot() {
return new LogicNodeBuilder(0);
}
}
class NonMergeableLogicNodeBuilder extends AbstractLogicNodeBuilder {
logic = new LogicContainer([]);
children = new Map();
constructor(depth) {
super(depth);
}
addHiddenRule(logic) {
this.logic.hidden.push(setBoundPathDepthForResolution(logic, this.depth));
}
addDisabledReasonRule(logic) {
this.logic.disabledReasons.push(setBoundPathDepthForResolution(logic, this.depth));
}
addReadonlyRule(logic) {
this.logic.readonly.push(setBoundPathDepthForResolution(logic, this.depth));
}
addSyncErrorRule(logic) {
this.logic.syncErrors.push(setBoundPathDepthForResolution(logic, this.depth));
}
addSyncTreeErrorRule(logic) {
this.logic.syncTreeErrors.push(setBoundPathDepthForResolution(logic, this.depth));
}
addAsyncErrorRule(logic) {
this.logic.asyncErrors.push(setBoundPathDepthForResolution(logic, this.depth));
}
addMetadataRule(key, logic) {
this.logic.getMetadata(key).push(setBoundPathDepthForResolution(logic, this.depth));
}
getChild(key) {
if (!this.children.has(key)) {
this.children.set(key, new LogicNodeBuilder(this.depth + 1));
}
return this.children.get(key);
}
hasLogic(builder) {
return this === builder;
}
}
class LeafLogicNode {
builder;
predicates;
depth;
logic;
constructor(builder, predicates, depth) {
this.builder = builder;
this.predicates = predicates;
this.depth = depth;
this.logic = builder ? createLogic(builder, predicates, depth) : new LogicContainer([]);
}
getChild(key) {
const childBuilders = this.builder ? getAllChildBuilders(this.builder, key) : [];
if (childBuilders.length === 0) {
return new LeafLogicNode(undefined, [], this.depth + 1);
} else if (childBuilders.length === 1) {
const {
builder,
predicates
} = childBuilders[0];
return new LeafLogicNode(builder, [...this.predicates, ...predicates.map(p => bindLevel(p, this.depth))], this.depth + 1);
} else {
const builtNodes = childBuilders.map(({
builder,
predicates
}) => new LeafLogicNode(builder, [...this.predicates, ...predicates.map(p => bindLevel(p, this.depth))], this.depth + 1));
return new CompositeLogicNode(builtNodes);
}
}
hasLogic(builder) {
return this.builder?.hasLogic(builder) ?? false;
}
}
class CompositeLogicNode {
all;
logic;
constructor(all) {
this.all = all;
this.logic = new LogicContainer([]);
for (const node of all) {
this.logic.mergeIn(node.logic);
}
}
getChild(key) {
return new CompositeLogicNode(this.all.flatMap(child => child.getChild(key)));
}
hasLogic(builder) {
return this.all.some(node => node.hasLogic(builder));
}
}
function getAllChildBuilders(builder, key) {
if (builder instanceof LogicNodeBuilder) {
return builder.all.flatMap(({
builder,
predicate
}) => {
const children = getAllChildBuilders(builder, key);
if (predicate) {
return children.map(({
builder,
predicates
}) => ({
builder,
predicates: [...predicates, predicate]
}));
}
return children;
});
} else if (builder instanceof NonMergeableLogicNodeBuilder) {
return [...(key !== DYNAMIC && builder.children.has(DYNAMIC) ? [{
builder: builder.getChild(DYNAMIC),
predicates: []
}] : []), ...(builder.children.has(key) ? [{
builder: builder.getChild(key),
predicates: []
}] : [])];
} else {
throw new _RuntimeError(1909, ngDevMode && 'Unknown LogicNodeBuilder type');
}
}
function createLogic(builder, predicates, depth) {
const logic = new LogicContainer(predicates);
if (builder instanceof LogicNodeBuilder) {
const builtNodes = builder.all.map(({
builder,
predicate
}) => new LeafLogicNode(builder, predicate ? [...predicates, bindLevel(predicate, depth)] : predicates, depth));
for (const node of builtNodes) {
logic.mergeIn(node.logic);
}
} else if (builder instanceof NonMergeableLogicNodeBuilder) {
logic.mergeIn(builder.logic);
} else {
throw new _RuntimeError(1909, ngDevMode && 'Unknown LogicNodeBuilder type');
}
return logic;
}
function bindLevel(predicate, depth) {
return {
...predicate,
depth: depth
};
}
const PATH = Symbol('PATH');
class FieldPathNode {
keys;
parent;
keyInParent;
root;
children = new Map();
fieldPathProxy = new Proxy(this, FIELD_PATH_PROXY_HANDLER);
logicBuilder;
constructor(keys, root, parent, keyInParent) {
this.keys = keys;
this.parent = parent;
this.keyInParent = keyInParent;
this.root = root ?? this;
if (!parent) {
this.logicBuilder = LogicNodeBuilder.newRoot();
}
}
get builder() {
if (this.logicBuilder) {
return this.logicBuilder;
}
return this.parent.builder.getChild(this.keyInParent);
}
getChild(key) {
if (!this.children.has(key)) {
this.children.set(key, new FieldPathNode([...this.keys, key], this.root, this, key));
}
return this.children.get(key);
}
mergeIn(other, predicate) {
const path = other.compile();
this.builder.mergeIn(path.builder, predicate);
}
static unwrapFieldPath(formPath) {
return formPath[PATH];
}
static newRoot() {
return new FieldPathNode([], undefined, undefined, undefined);
}
}
const FIELD_PATH_PROXY_HANDLER = {
get(node, property) {
if (property === PATH) {
return node;
}
return node.getChild(property).fieldPathProxy;
}
};
let currentCompilingNode = undefined;
const compiledSchemas = new Map();
class SchemaImpl {
schemaFn;
constructor(schemaFn) {
this.schemaFn = schemaFn;
}
compile() {
if (compiledSchemas.has(this)) {
return compiledSchemas.get(this);
}
const path = FieldPathNode.newRoot();
compiledSchemas.set(this, path);
let prevCompilingNode = currentCompilingNode;
try {
currentCompilingNode = path;
this.schemaFn(path.fieldPathProxy);
} finally {
currentCompilingNode = prevCompilingNode;
}
return path;
}
static create(schema) {
if (schema instanceof SchemaImpl) {
return schema;
}
return new SchemaImpl(schema);
}
static rootCompile(schema) {
try {
compiledSchemas.clear();
if (schema === undefined) {
return FieldPathNode.newRoot();
}
if (schema instanceof SchemaImpl) {
return schema.compile();
}
return new SchemaImpl(schema).compile();
} finally {
compiledSchemas.clear();
}
}
}
function isSchemaOrSchemaFn(value) {
return value instanceof SchemaImpl || typeof value === 'function';
}
function assertPathIsCurrent(path) {
if (currentCompilingNode !== FieldPathNode.unwrapFieldPath(path).root) {
throw new _RuntimeError(1908, ngDevMode && `A FieldPath can only be used directly within the Schema that owns it, **not** outside of it or within a sub-schema.`);
}
}
function metadata(path, key, logic) {
assertPathIsCurrent(path);
const pathNode = FieldPathNode.unwrapFieldPath(path);
pathNode.builder.addMetadataRule(key, logic);
return key;
}
const MetadataReducer = {
list() {
return {
reduce: (acc, item) => item === undefined ? acc : [...acc, item],
getInitial: () => []
};
},
min() {
return {
reduce: (acc, item) => {
if (acc === undefined || item === undefined) {
return acc ?? item;
}
return Math.min(acc, item);
},
getInitial: () => undefined
};
},
max() {
return {
reduce: (prev, next) => {
if (prev === undefined || next === undefined) {
return prev ?? next;
}
return Math.max(prev, next);
},
getInitial: () => undefined
};
},
or() {
return {
reduce: (prev, next) => prev || next,
getInitial: () => false
};
},
and() {
return {
reduce: (prev, next) => prev && next,
getInitial: () => true
};
},
override
};
function override(getInitial) {
return {
reduce: (_, item) => item,
getInitial: () => getInitial?.()
};
}
class MetadataKey {
reducer;
create;
brand;
constructor(reducer, create) {
this.reducer = reducer;
this.create = create;
}
}
function createMetadataKey(reducer) {
return new MetadataKey(reducer ?? MetadataReducer.override());
}
function createManagedMetadataKey(create, reducer) {
return new MetadataKey(reducer ?? MetadataReducer.override(), create);
}
const REQUIRED = createMetadataKey(MetadataReducer.or());
const MIN = createMetadataKey(MetadataReducer.max());
const MAX = createMetadataKey(MetadataReducer.min());
const MIN_LENGTH = createMetadataKey(MetadataReducer.max());
const MAX_LENGTH = createMetadataKey(MetadataReducer.min());
const PATTERN = createMetadataKey(MetadataReducer.list());
function calculateValidationSelfStatus(state) {
if (state.errors().length > 0) {
return 'invalid';
}
if (state.pending()) {
return 'unknown';
}
return 'valid';
}
class FieldValidationState {
node;
constructor(node) {
this.node = node;
}
rawSyncTreeErrors = computed(() => {
if (this.shouldSkipValidation()) {
return [];
}
return [...this.node.logicNode.logic.syncTreeErrors.compute(this.node.context), ...(this.node.structure.parent?.validationState.rawSyncTreeErrors() ?? [])];
}, ...(ngDevMode ? [{
debugName: "rawSyncTreeErrors"
}] : []));
syncErrors = computed(() => {
if (this.shouldSkipValidation()) {
return [];
}
return [...this.node.logicNode.logic.syncErrors.compute(this.node.context), ...this.syncTreeErrors(), ...normalizeErrors(this.node.submitState.serverErrors())];
}, ...(ngDevMode ? [{
debugName: "syncErrors"
}] : []));
syncValid = computed(() => {
if (this.shouldSkipValidation()) {
return true;
}
return this.node.structure.reduceChildren(this.syncErrors().length === 0, (child, value) => value && child.validationState.syncValid(), shortCircuitFalse);
}, ...(ngDevMode ? [{
debugName: "syncValid"
}] : []));
syncTreeErrors = computed(() => this.rawSyncTreeErrors().filter(err => err.fieldTree === this.node.fieldProxy), ...(ngDevMode ? [{
debugName: "syncTreeErrors"
}] : []));
rawAsyncErrors = computed(() => {
if (this.shouldSkipValidation()) {
return [];
}
return [...this.node.logicNode.logic.asyncErrors.compute(this.node.context), ...(this.node.structure.parent?.validationState.rawAsyncErrors() ?? [])];
}, ...(ngDevMode ? [{
debugName: "rawAsyncErrors"
}] : []));
asyncErrors = computed(() => {
if (this.shouldSkipValidation()) {
return [];
}
return this.rawAsyncErrors().filter(err => err === 'pending' || err.fieldTree === this.node.fieldProxy);
}, ...(ngDevMode ? [{
debugName: "asyncErrors"
}] : []));
errors = computed(() => [...this.syncErrors(), ...this.asyncErrors().filter(err => err !== 'pending')], ...(ngDevMode ? [{
debugName: "errors"
}] : []));
errorSummary = computed(() => this.node.structure.reduceChildren(this.errors(), (child, result) => [...result, ...child.errorSummary()]), ...(ngDevMode ? [{
debugName: "errorSummary"
}] : []));
pending = computed(() => this.node.structure.reduceChildren(this.asyncErrors().includes('pending'), (child, value) => value || child.validationState.asyncErrors().includes('pending')), ...(ngDevMode ? [{
debugName: "pending"
}] : []));
status = computed(() => {
if (this.shouldSkipValidation()) {
return 'valid';
}
let ownStatus = calculateValidationSelfStatus(this);
return this.node.structure.reduceChildren(ownStatus, (child, value) => {
if (value === 'invalid' || child.validationState.status() === 'invalid') {
return 'invalid';
} else if (value === 'unknown' || child.validationState.status() === 'unknown') {
return 'unknown';
}
return 'valid';
}, v => v === 'invalid');
}, ...(ngDevMode ? [{
debugName: "status"
}] : []));
valid = computed(() => this.status() === 'valid', ...(ngDevMode ? [{
debugName: "valid"
}] : []));
invalid = computed(() => this.status() === 'invalid', ...(ngDevMode ? [{
debugName: "invalid"
}] : []));
shouldSkipValidation = computed(() => this.node.hidden() || this.node.disabled() || this.node.readonly(), ...(ngDevMode ? [{
debugName: "shouldSkipValidation"
}] : []));
}
function normalizeErrors(error) {
if (error === undefined) {
return [];
}
if (isArray(error)) {
return error;
}
return [error];
}
function addDefaultField(errors, fieldTree) {
if (isArray(errors)) {
for (const error of errors) {
error.fieldTree ??= fieldTree;
}
} else if (errors) {
errors.fieldTree ??= fieldTree;
}
return errors;
}
const DEBOUNCER = createMetadataKey();
class FieldNodeContext {
node;
cache = new WeakMap();
constructor(node) {
this.node = node;
}
resolve(target) {
if (!this.cache.has(target)) {
const resolver = computed(() => {
const targetPathNode = FieldPathNode.unwrapFieldPath(target);
let field = this.node;
let stepsRemaining = getBoundPathDepth();
while (stepsRemaining > 0 || !field.structure.logic.hasLogic(targetPathNode.root.builder)) {
stepsRemaining--;
field = field.structure.parent;
if (field === undefined) {
throw new _RuntimeError(1900, ngDevMode && 'Path is not part of this field tree.');
}
}
for (let key of targetPathNode.keys) {
field = field.structure.getChild(key);
if (field === undefined) {
throw new _RuntimeError(1901, ngDevMode && `Cannot resolve path .${targetPathNode.keys.join('.')} relative to field ${['<root>', ...this.node.structure.pathKeys()].join('.')}.`);
}
}
return field.fieldProxy;
}, ...(ngDevMode ? [{
debugName: "resolver"
}] : []));
this.cache.set(target, resolver);
}
return this.cache.get(target)();
}
get fieldTree() {
return this.node.fieldProxy;
}
get state() {
return this.node;
}
get value() {
return this.node.structure.value;
}
get key() {
return this.node.structure.keyInParent;
}
get pathKeys() {
return this.node.structure.pathKeys;
}
index = computed(() => {
const key = this.key();
if (!isArray(untracked(this.node.structure.parent.value))) {
throw new _RuntimeError(1906, ngDevMode && 'Cannot access index, parent field is not an array.');
}
return Number(key);
}, ...(ngDevMode ? [{
debugName: "index"
}] : []));
fieldTreeOf = p => this.resolve(p);
stateOf = p => this.resolve(p)();
valueOf = p => {
const result = this.resolve(p)().value();
if (result instanceof AbstractControl) {
throw new _RuntimeError(1907, ngDevMode && `Tried to read an 'AbstractControl' value from a 'form()'. Did you mean to use 'compatForm()' instead?`);
}
return result;
};
}
class FieldMetadataState {
node;
metadata = new Map();
constructor(node) {
this.node = node;
for (const key of this.node.logicNode.logic.getMetadataKeys()) {
if (key.create) {
const logic = this.node.logicNode.logic.getMetadata(key);
const result = untracked(() => runInInjectionContext(this.node.structure.injector, () => key.create(computed(() => logic.compute(this.node.context)))));
this.metadata.set(key, result);
}
}
}
get(key) {
if (this.has(key)) {
if (!this.metadata.has(key)) {
if (key.create) {
throw Error('Managed metadata cannot be created lazily');
}
const logic = this.node.logicNode.logic.getMetadata(key);
this.metadata.set(key, computed(() => logic.compute(this.node.context)));
}
}
return this.metadata.get(key);
}
has(key) {
return this.node.logicNode.logic.hasMetadata(key);
}
}
const FIELD_PROXY_HANDLER = {
get(getTgt, p, receiver) {
const tgt = getTgt();
const child = tgt.structure.getChild(p);
if (child !== undefined) {
return child.fieldProxy;
}
const value = untracked(tgt.value);
if (isArray(value)) {
if (p === 'length') {
return tgt.value().length;
}
if (p === Symbol.iterator) {
return () => {
tgt.value();
return Array.prototype[Symbol.iterator].apply(tgt.fieldProxy);
};
}
}
if (isObject(value)) {
if (p === Symbol.iterator) {
return function* () {
for (const key in receiver) {
yield [key, receiver[key]];
}
};
}
}
return undefined;
},
getOwnPropertyDescriptor(getTgt, prop) {
const value = untracked(getTgt().value);
const desc = Reflect.getOwnPropertyDescriptor(value, prop);
if (desc && !desc.configurable) {
desc.configurable = true;
}
return desc;
},
ownKeys(getTgt) {
const value = untracked(getTgt().value);
return typeof value === 'object' && value !== null ? Reflect.ownKeys(value) : [];
}
};
function deepSignal(source, prop) {
const read = computed(() => source()[prop()]);
read[SIGNAL] = source[SIGNAL];
read.set = value => {
source.update(current => valueForWrite(current, value, prop()));
};
read.update = fn => {
read.set(fn(untracked(read)));
};
read.asReadonly = () => read;
return read;
}
function valueForWrite(sourceValue, newPropValue, prop) {
if (isArray(sourceValue)) {
const newValue = [...sourceValue];
newValue[prop] = newPropValue;
return newValue;
} else {
return {
...sourceValue,
[prop]: newPropValue
};
}
}
class FieldNodeStructure {
logic;
node;
createChildNode;
identitySymbol = Symbol();
_injector = undefined;
get injector() {
this._injector ??= Injector.create({
providers: [],
parent: this.fieldManager.injector
});
return this._injector;
}
constructor(logic, node, createChildNode) {
this.logic = logic;
this.node = node;
this.createChildNode = createChildNode;
}
children() {
const map = this.childrenMap();
if (map === undefined) {
return [];
}
return Array.from(map.byPropertyKey.values()).map(child => untracked(child.reader));
}
getChild(key) {
const strKey = key.toString();
let reader = untracked(this.childrenMap)?.byPropertyKey.get(strKey)?.reader;
if (!reader) {
reader = this.createReader(strKey);
}
return reader();
}
reduceChildren(initialValue, fn, shortCircuit) {
const map = this.childrenMap();
if (!map) {
return initialValue;
}
let value = initialValue;
for (const child of map.byPropertyKey.values()) {
if (shortCircuit?.(value)) {
break;
}
value = fn(untracked(child.reader), value);
}
return value;
}
destroy() {
this.injector.destroy();
}
createKeyInParent(options, identityInParent, initialKeyInParent) {
if (options.kind === 'root') {
return ROOT_KEY_IN_PARENT;
}
if (identityInParent === undefined) {
const key = initialKeyInParent;
return computed(() => {
if (this.parent.structure.getChild(key) !== this.node) {
throw new _RuntimeError(1902, ngDevMode && `Orphan field, looking for property '${key}' of ${getDebugName(this.parent)}`);
}
return key;
});
} else {
let lastKnownKey = initialKeyInParent;
return computed(() => {
const parentValue = this.parent.structure.value();
if (!isArray(parentValue)) {
throw new _RuntimeError(1903, ngDevMode && `Orphan field, expected ${getDebugName(this.parent)} to be an array`);
}
const data = parentValue[lastKnownKey];
if (isObject(data) && data.hasOwnProperty(this.parent.structure.identitySymbol) && data[this.parent.structure.identitySymbol] === identityInParent) {
return lastKnownKey;
}
for (let i = 0; i < parentValue.length; i++) {
const data = parentValue[i];
if (isObject(data) && data.hasOwnProperty(this.parent.structure.identitySymbol) && data[this.parent.structure.identitySymbol] === identityInParent) {
return lastKnownKey = i.toString();
}
}
throw new _RuntimeError(1904, ngDevMode && `Orphan field, can't find element in array ${getDebugName(this.parent)}`);
});
}
}
createChildrenMap() {
return linkedSignal({
source: this.value,
computation: (value, previous) => {
if (!isObject(value)) {
return undefined;
}
const prevData = previous?.value ?? {
byPropertyKey: new Map()
};
let data;
const parentIsArray = isArray(value);
if (prevData !== undefined) {
if (parentIsArray) {
data = maybeRemoveStaleArrayFields(prevData, value, this.identitySymbol);
} else {
data = maybeRemoveStaleObjectFields(prevData, value);
}
}
for (const key of Object.keys(value)) {
let trackingKey = undefined;
const childValue = value[key];
if (childValue === undefined) {
if (prevData.byPropertyKey.has(key)) {
data ??= {
...prevData
};
data.byPropertyKey.delete(key);
}
continue;
}
if (parentIsArray && isObject(childValue) && !isArray(childValue)) {
trackingKey = childValue[this.identitySymbol] ??= Symbol(ngDevMode ? `id:${globalId++}` : '');
}
let childNode;
if (trackingKey) {
if (!prevData.byTrackingKey?.has(trackingKey)) {
data ??= {
...prevData
};
data.byTrackingKey ??= new Map();
data.byTrackingKey.set(trackingKey, this.createChildNode(key, trackingKey, parentIsArray));
}
childNode = (data ?? prevData).byTrackingKey.get(trackingKey);
}
const child = prevData.byPropertyKey.get(key);
if (child === undefined) {
data ??= {
...prevData
};
data.byPropertyKey.set(key, {
reader: this.createReader(key),
node: childNode ?? this.createChildNode(key, trackingKey, parentIsArray)
});
} else if (childNode && childNode !== child.node) {
data ??= {
...prevData
};
child.node = childNode;
}
}
return data ?? prevData;
}
});
}
createReader(key) {
return computed(() => this.childrenMap()?.byPropertyKey.get(key)?.node);
}
}
class RootFieldNodeStructure extends FieldNodeStructure {
fieldManager;
value;
get parent() {
return undefined;
}
get root() {
return this.node;
}
get pathKeys() {
return ROOT_PATH_KEYS;
}
get keyInParent() {
return ROOT_KEY_IN_PARENT;
}
childrenMap;
constructor(node, logic, fieldManager, value, createChildNode) {
super(logic, node, createChildNode);
this.fieldManager = fieldManager;
this.value = value;
this.childrenMap = this.createChildrenMap();
}
}
class ChildFieldNodeStructure extends FieldNodeStructure {
logic;
parent;
root;
pathKeys;
keyInParent;
value;
childrenMap;
get fieldManager() {
return this.root.structure.fieldManager;
}
constructor(node, logic, parent, identityInParent, initialKeyInParent, createChildNode) {
super(logic, node, createChildNode);
this.logic = logic;
this.parent = parent;
this.root = this.parent.structure.root;
this.keyInParent = this.createKeyInParent({
kind: 'child',
parent,
pathNode: undefined,
logic,
initialKeyInParent,
identityInParent,
fieldAdapter: undefined
}, identityInParent, initialKeyInParent);
this.pathKeys = computed(() => [...parent.structure.pathKeys(), this.keyInParent()], ...(ngDevMode ? [{
debugName: "pathKeys"
}] : []));
this.value = deepSignal(this.parent.structure.value, this.keyInParent);
this.childrenMap = this.createChildrenMap();
this.fieldManager.structures.add(this);
}
}
let globalId = 0;
const ROOT_PATH_KEYS = computed(() => [], ...(ngDevMode ? [{
debugName: "ROOT_PATH_KEYS"
}] : []));
const ROOT_KEY_IN_PARENT = computed(() => {
throw new _RuntimeError(1905, ngDevMode && 'The top-level field in the form has no parent.');
}, ...(ngDevMode ? [{
debugName: "ROOT_KEY_IN_PARENT"
}] : []));
function getDebugName(node) {
return `<root>.${node.structure.pathKeys().join('.')}`;
}
function maybeRemoveStaleArrayFields(prevData, value, identitySymbol) {
let data;
const oldKeys = new Set(prevData.byPropertyKey.keys());
const oldTracking = new Set(prevData.byTrackingKey?.keys());
for (let i = 0; i < value.length; i++) {
const childValue = value[i];
oldKeys.delete(i.toString());
if (isObject(childValue) && childValue.hasOwnProperty(identitySymbol)) {
oldTracking.delete(childValue[identitySymbol]);
}
}
if (oldKeys.size > 0) {
data ??= {
...prevData
};
for (const key of oldKeys) {
data.byPropertyKey.delete(key);
}
}
if (oldTracking.size > 0) {
data ??= {
...prevData
};
for (const id of oldTracking) {
data.byTrackingKey?.delete(id);
}
}
return data;
}
function maybeRemoveStaleObjectFields(prevData, value) {
let data;
for (const key of prevData.byPropertyKey.keys()) {
if (!value.hasOwnProperty(key)) {
data ??= {
...prevData
};
data.byPropertyKey.delete(key);
}
}
return data;
}
class FieldSubmitState {
node;
selfSubmitting = signal(false, ...(ngDevMode ? [{
debugName: "selfSubmitting"
}] : []));
serverErrors;
constructor(node) {
this.node = node;
this.serverErrors = linkedSignal({
...(ngDevMode ? {
debugName: "serverErrors"
} : {}),
source: this.node.structure.value,
computation: () => []
});
}
submitting = computed(() => {
return this.selfSubmitting() || (this.node.structure.parent?.submitting() ?? false);
}, ...(ngDevMode ? [{
debugName: "submitting"
}] : []));
}
class FieldNode {
structure;
validationState;
metadataState;
nodeState;
submitState;
fieldAdapter;
_context = undefined;
get context() {
return this._context ??= new FieldNodeContext(this);
}
fieldProxy = new Proxy(() => this, FIELD_PROXY_HANDLER);
pathNode;
constructor(options) {
this.pathNode = options.pathNode;
this.fieldAdapter = options.fieldAdapter;
this.structure = this.fieldAdapter.createStructure(this, options);
this.validationState = this.fieldAdapter.createValidationState(this, options);
this.nodeState = this.fieldAdapter.createNodeState(this, options);
this.metadataState = new FieldMetadataState(this);
this.submitState = new FieldSubmitState(this);
}
pendingSync = linkedSignal({
...(ngDevMode ? {
debugName: "pendingSync"
} : {}),
source: () => this.value(),
computation: (_source, previous) => {
previous?.value?.abort();
return undefined;
}
});
get logicNode() {
return this.structure.logic;
}
get value() {
return this.structure.value;
}
_controlValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{
debugName: "_controlValue"
}] : []));
get controlValue() {
return this._controlValue.asReadonly();
}
get keyInParent() {
return this.structure.keyInParent;
}
get errors() {
return this.validationState.errors;
}
get errorSummary() {
return this.validationState.errorSummary;
}
get pending() {
return this.validationState.pending;
}
get valid() {
return this.validationState.valid;
}
get invalid() {
return this.validationState.invalid;
}
get dirty() {
return this.nodeState.dirty;
}
get touched() {
return this.nodeState.touched;
}
get disabled() {
return this.nodeState.disabled;
}
get disabledReasons() {
return this.nodeState.disabledReasons;
}
get hidden() {
return this.nodeState.hidden;
}
get readonly() {
return this.nodeState.readonly;
}
get fieldBindings() {
return this.nodeState.fieldBindings;
}
get submitting() {
return this.submitState.submitting;
}
get name() {
return this.nodeState.name;
}
get max() {
return this.metadata(MAX);
}
get maxLength() {
return this.metadata(MAX_LENGTH);
}
get min() {
return this.metadata(MIN);
}
get minLength() {
return this.metadata(MIN_LENGTH);
}
get pattern() {
return this.metadata(PATTERN) ?? EMPTY;
}
get required() {
return this.metadata(REQUIRED) ?? FALSE;
}
metadata(key) {
return this.metadataState.get(key);
}
hasMetadata(key) {
return this.metadataState.has(key);
}
markAsTouched() {
this.nodeState.markAsTouched();
this.pendingSync()?.abort();
this.sync();
}
markAsDirty() {
this.nodeState.markAsDirty();
}
reset(value) {
untracked(() => this._reset(value));
}
_reset(value) {
if (value !== undefined) {
this.value.set(value);
}
this.nodeState.markAsUntouched();
this.nodeState.markAsPristine();
for (const child of this.structure.children()) {
child._reset();
}
}
setControlValue(newValue) {
this._controlValue.set(newValue);
this.markAsDirty();
this.debounceSync();
}
sync() {
this.value.set(this.controlValue());
}
async debounceSync() {
this.pendingSync()?.abort();
const debouncer = this.nodeState.debouncer();
if (debouncer) {
const controller = new AbortController();
const promise = debouncer(controller.signal);
if (promise) {
this.pendingSync.set(controller);
await promise;
if (controller.signal.aborted) {
return;
}
}
}
this.sync();
}
static newRoot(fieldManager, value, pathNode, adapter) {
return adapter.newRoot(fieldManager, value, pathNode, adapter);
}
createStructure(options) {
return options.kind === 'root' ? new RootFieldNodeStructure(this, options.logic, options.fieldManager, options.value, this.newChild.bind(this)) : new ChildFieldNodeStructure(this, options.logic, options.parent, options.identityInParent, options.initialKeyInParent, this.newChild.bind(this));
}
newChild(key, trackingId, isArray) {
let childPath;
let childLogic;
if (isArray) {
childPath = this.pathNode.getChild(DYNAMIC);
childLogic = this.structure.logic.getChild(DYNAMIC);
} else {
childPath = this.pathNode.getChild(key);
childLogic = this.structure.logic.getChild(key);
}
return this.fieldAdapter.newChild({
kind: 'child',
parent: this,
pathNode: childPath,
logic: childLogic,
initialKeyInParent: key,
identityInParent: trackingId,
fieldAdapter: this.fieldAdapter
});
}
}
const EMPTY = computed(() => [], ...(ngDevMode ? [{
debugName: "EMPTY"
}] : []));
const FALSE = computed(() => false, ...(ngDevMode ? [{
debugName: "FALSE"
}] : []));
class FieldNodeState {
node;
selfTouched = signal(false, ...(ngDevMode ? [{
debugName: "selfTouched"
}] : []));
selfDirty = signal(false, ...(ngDevMode ? [{
debugName: "selfDirty"
}] : []));
markAsTouched() {
this.selfTouched.set(true);
}
markAsDirty() {
this.selfDirty.set(true);
}
markAsPristine() {
this.selfDirty.set(false);
}
markAsUntouched() {
this.selfTouched.set(false);
}
fieldBindings = signal([], ...(ngDevMode ? [{
debugName: "fieldBindings"
}] : []));
constructor(node) {
this.node = node;
}
dirty = computed(() => {
const selfDirtyValue = this.selfDirty() && !this.isNonInteractive();
return this.node.structure.reduceChildren(selfDirtyValue, (child, value) => value || child.nodeState.dirty(), shortCircuitTrue);
}, ...(ngDevMode ? [{
debugName: "dirty"
}] : []));
touched = computed(() => {
const selfTouchedValue = this.selfTouched() && !this.isNonInteractive();
return this.node.structure.reduceChildren(selfTouchedValue, (child, value) => value || child.nodeState.touched(), shortCircuitTrue);
}, ...(ngDevMode ? [{
debugName: "touched"
}] : []));
disabledReasons = computed(() => [...(this.node.structure.parent?.nodeState.disabledReasons() ?? []), ...this.node.logicNode.logic.disabledReasons.compute(this.node.context)], ...(ngDevMode ? [{
debugName: "disabledReasons"
}] : []));
disabled = computed(() => !!this.disabledReasons().length, ...(ngDevMode ? [{
debugName: "disabled"
}] : []));
readonly = computed(() => (this.node.structure.parent?.nodeState.readonly() || this.node.logicNode.logic.readonly.compute(this.node.context)) ?? false, ...(ngDevMode ? [{
debugName: "readonly"
}] : []));
hidden = computed(() => (this.node.structure.parent?.nodeState.hidden() || this.node.logicNode.logic.hidden.compute(this.node.context)) ?? false, ...(ngDevMode ? [{
debugName: "hidden"
}] : []));
name = computed(() => {
const parent = this.node.structure.parent;
if (!parent) {
return this.node.structure.fieldManager.rootName;
}
return `${parent.name()}.${this.node.structure.keyInParent()}`;
}, ...(ngDevMode ? [{
debugName: "name"
}] : []));
debouncer = computed(() => {
if (this.node.logicNode.logic.hasMetadata(DEBOUNCER)) {
const debouncerLogic = this.node.logicNode.logic.getMetadata(DEBOUNCER);
const debouncer = debouncerLogic.compute(this.node.context);
if (debouncer) {
return signal => debouncer(this.node.context, signal);
}
}
return this.node.structure.parent?.nodeState.debouncer?.();
}, ...(ngDevMode ? [{
debugName: "debouncer"
}] : []));
isNonInteractive = computed(() => this.hidden() || this.disabled() || this.readonly(), ...(ngDevMode ? [{
debugName: "isNonInteractive"
}] : []));
}
class BasicFieldAdapter {
newRoot(fieldManager, value, pathNode, adapter) {
return new FieldNode({
kind: 'root',
fieldManager,
value,
pathNode,
logic: pathNode.builder.build(),
fieldAdapter: adapter
});
}
newChild(options) {
return new FieldNode(options);
}
createNodeState(node) {
return new FieldNodeState(node);
}
createValidationState(node) {
return new FieldValidationState(node);
}
createStructure(node, options) {
return node.createStructure(options);
}
}
class FormFieldManager {
injector;
rootName;
constructor(injector, rootName) {
this.injector = injector;
this.rootName = rootName ?? `${this.injector.get(APP_ID)}.form${nextFormId++}`;
}
structures = new Set();
createFieldManagementEffect(root) {
effect(() => {
const liveStructures = new Set();
this.markStructuresLive(root, liveStructures);
for (const structure of this.structures) {
if (!liveStructures.has(structure)) {
this.structures.delete(structure);
untracked(() => structure.destroy());
}
}
}, {
injector: this.injector
});
}
markStructuresLive(structure, liveStructures) {
liveStructures.add(structure);
for (const child of structure.children()) {
this.markStructuresLive(child.structure, liveStructures);
}
}
}
let nextFormId = 0;
function normalizeFormArgs(args) {
let model;
let schema;
let options;
if (args.length === 3) {
[model, schema, options] = args;
} else if (args.length === 2) {
if (isSchemaOrSchemaFn(args[1])) {
[model, schema] = args;
} else {
[model, options] = args;
}
} else {
[model] = args;
}
return [model, schema, options];
}
function form(...args) {
const [model, schema, options] = normalizeFormArgs(args);
const injector = options?.injector ?? inject(Injector);
const pathNode = runInInjectionContext(injector, () => SchemaImpl.rootCompile(schema));
const fieldManager = new FormFieldManager(injector, options?.name);
const adapter = options?.adapter ?? new BasicFieldAdapter();
const fieldRoot = FieldNode.newRoot(fieldManager, model, pathNode, adapter);
fieldManager.createFieldManagementEffect(fieldRoot.structure);
return fieldRoot.fieldProxy;
}
function applyEach(path, schema) {
assertPathIsCurrent(path);
const elementPath = FieldPathNode.unwrapFieldPath(path).getChild(DYNAMIC).fieldPathProxy;
apply(elementPath, schema);
}
function apply(path, schema) {
assertPathIsCurrent(path);
const pathNode = FieldPathNode.unwrapFieldPath(path);
pathNode.mergeIn(SchemaImpl.create(schema));
}
function applyWhen(path, logic, schema) {
assertPathIsCurrent(path);
const pathNode = FieldPathNode.unwrapFieldPath(path);
pathNode.mergeIn(SchemaImpl.create(schema), {
fn: logic,
path
});
}
function applyWhenValue(path, predicate, schema) {
applyWhen(path, ({
value
}) => predicate(value()), schema);
}
async function submit(form, action) {
const node = form();
markAllAsTouched(node);
if (node.invalid()) {
return;
}
node.submitState.selfSubmitting.set(true);
try {
const errors = await action(form);
errors && setServerErrors(node, errors);
} finally {
node.submitState.selfSubmitting.set(false);
}
}
function setServerErrors(submittedField, errors) {
if (!isArray(errors)) {
errors = [errors];
}
const errorsByField = new Map();
for (const error of errors) {
const errorWithField = addDefaultField(error, submittedField.fieldProxy);
const field = errorWithField.fieldTree();
let fieldErrors = errorsByField.get(field);
if (!fieldErrors) {
fieldErrors = [];
errorsByField.set(field, fieldErrors);
}
fieldErrors.push(errorWithField);
}
for (const [field, fieldErrors] of errorsByField) {
field.submitState.serverErrors.set(fieldErrors);
}
}
function schema(fn) {
return SchemaImpl.create(fn);
}
function markAllAsTouched(node) {
node.markAsTouched();
for (const child of node.structure.children()) {
markAllAsTouched(child);
}
}
export { BasicFieldAdapter, DEBOUNCER, FieldNode, FieldNodeState, FieldNodeStructure, FieldPathNode, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MetadataKey, MetadataReducer, PATTERN, REQUIRED, addDefaultField, apply, applyEach, applyWhen, applyWhenValue, assertPathIsCurrent, calculateValidationSelfStatus, createManagedMetadataKey, createMetadataKey, form, getInjectorFromOptions, isArray, metadata, normalizeFormArgs, schema, submit };
//# sourceMappingURL=_structure-chunk.mjs.map