@angular/forms 21.2.0-next.1 → 21.2.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{_structure-chunk.mjs → _validation_errors-chunk.mjs} +138 -43
- package/fesm2022/_validation_errors-chunk.mjs.map +1 -0
- package/fesm2022/forms.mjs +170 -189
- package/fesm2022/forms.mjs.map +1 -1
- package/fesm2022/signals-compat.mjs +386 -47
- package/fesm2022/signals-compat.mjs.map +1 -1
- package/fesm2022/signals.mjs +683 -238
- package/fesm2022/signals.mjs.map +1 -1
- package/package.json +4 -4
- package/resources/code-examples.db +0 -0
- package/types/_structure-chunk.d.ts +642 -1406
- package/types/forms.d.ts +1 -1
- package/types/signals-compat.d.ts +131 -8
- package/types/signals.d.ts +107 -48
- package/fesm2022/_structure-chunk.mjs.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v21.2.0-next.
|
|
2
|
+
* @license Angular v21.2.0-next.3
|
|
3
3
|
* (c) 2010-2026 Google LLC. https://angular.dev/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { untracked, ɵRuntimeError as _RuntimeError, computed, runInInjectionContext, Injector, linkedSignal, signal, APP_ID, effect, inject } from '@angular/core';
|
|
8
|
-
import { AbstractControl } from '@angular/forms';
|
|
8
|
+
import { AbstractControl, FormGroup, FormArray } from '@angular/forms';
|
|
9
9
|
import { SIGNAL } from '@angular/core/primitives/signals';
|
|
10
10
|
|
|
11
11
|
let boundPathDepth = 0;
|
|
@@ -628,7 +628,7 @@ class FieldValidationState {
|
|
|
628
628
|
}, ...(ngDevMode ? [{
|
|
629
629
|
debugName: "syncValid"
|
|
630
630
|
}] : []));
|
|
631
|
-
syncTreeErrors = computed(() => this.rawSyncTreeErrors().filter(err => err.fieldTree === this.node.
|
|
631
|
+
syncTreeErrors = computed(() => this.rawSyncTreeErrors().filter(err => err.fieldTree === this.node.fieldTree), ...(ngDevMode ? [{
|
|
632
632
|
debugName: "syncTreeErrors"
|
|
633
633
|
}] : []));
|
|
634
634
|
rawAsyncErrors = computed(() => {
|
|
@@ -643,7 +643,7 @@ class FieldValidationState {
|
|
|
643
643
|
if (this.shouldSkipValidation()) {
|
|
644
644
|
return [];
|
|
645
645
|
}
|
|
646
|
-
return this.rawAsyncErrors().filter(err => err === 'pending' || err.fieldTree === this.node.
|
|
646
|
+
return this.rawAsyncErrors().filter(err => err === 'pending' || err.fieldTree === this.node.fieldTree);
|
|
647
647
|
}, ...(ngDevMode ? [{
|
|
648
648
|
debugName: "asyncErrors"
|
|
649
649
|
}] : []));
|
|
@@ -752,7 +752,7 @@ class FieldNodeContext {
|
|
|
752
752
|
throw new _RuntimeError(1901, ngDevMode && `Cannot resolve path .${targetPathNode.keys.join('.')} relative to field ${['<root>', ...this.node.structure.pathKeys()].join('.')}.`);
|
|
753
753
|
}
|
|
754
754
|
}
|
|
755
|
-
return field.
|
|
755
|
+
return field.fieldTree;
|
|
756
756
|
}, ...(ngDevMode ? [{
|
|
757
757
|
debugName: "resolver"
|
|
758
758
|
}] : []));
|
|
@@ -830,7 +830,7 @@ const FIELD_PROXY_HANDLER = {
|
|
|
830
830
|
const tgt = getTgt();
|
|
831
831
|
const child = tgt.structure.getChild(p);
|
|
832
832
|
if (child !== undefined) {
|
|
833
|
-
return child.
|
|
833
|
+
return child.fieldTree;
|
|
834
834
|
}
|
|
835
835
|
const value = untracked(tgt.value);
|
|
836
836
|
if (isArray(value)) {
|
|
@@ -840,7 +840,7 @@ const FIELD_PROXY_HANDLER = {
|
|
|
840
840
|
if (p === Symbol.iterator) {
|
|
841
841
|
return () => {
|
|
842
842
|
tgt.value();
|
|
843
|
-
return Array.prototype[Symbol.iterator].apply(tgt.
|
|
843
|
+
return Array.prototype[Symbol.iterator].apply(tgt.fieldTree);
|
|
844
844
|
};
|
|
845
845
|
}
|
|
846
846
|
}
|
|
@@ -1186,6 +1186,7 @@ class FieldNode {
|
|
|
1186
1186
|
nodeState;
|
|
1187
1187
|
submitState;
|
|
1188
1188
|
fieldAdapter;
|
|
1189
|
+
controlValue;
|
|
1189
1190
|
_context = undefined;
|
|
1190
1191
|
get context() {
|
|
1191
1192
|
return this._context ??= new FieldNodeContext(this);
|
|
@@ -1200,6 +1201,7 @@ class FieldNode {
|
|
|
1200
1201
|
this.nodeState = this.fieldAdapter.createNodeState(this, options);
|
|
1201
1202
|
this.metadataState = new FieldMetadataState(this);
|
|
1202
1203
|
this.submitState = new FieldSubmitState(this);
|
|
1204
|
+
this.controlValue = this.controlValueSignal();
|
|
1203
1205
|
}
|
|
1204
1206
|
focusBoundControl(options) {
|
|
1205
1207
|
this.getBindingForFocus()?.focus(options);
|
|
@@ -1219,18 +1221,15 @@ class FieldNode {
|
|
|
1219
1221
|
return undefined;
|
|
1220
1222
|
}
|
|
1221
1223
|
});
|
|
1224
|
+
get fieldTree() {
|
|
1225
|
+
return this.fieldProxy;
|
|
1226
|
+
}
|
|
1222
1227
|
get logicNode() {
|
|
1223
1228
|
return this.structure.logic;
|
|
1224
1229
|
}
|
|
1225
1230
|
get value() {
|
|
1226
1231
|
return this.structure.value;
|
|
1227
1232
|
}
|
|
1228
|
-
_controlValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{
|
|
1229
|
-
debugName: "_controlValue"
|
|
1230
|
-
}] : []));
|
|
1231
|
-
get controlValue() {
|
|
1232
|
-
return this._controlValue.asReadonly();
|
|
1233
|
-
}
|
|
1234
1233
|
get keyInParent() {
|
|
1235
1234
|
return this.structure.keyInParent;
|
|
1236
1235
|
}
|
|
@@ -1312,6 +1311,12 @@ class FieldNode {
|
|
|
1312
1311
|
markAsDirty() {
|
|
1313
1312
|
this.nodeState.markAsDirty();
|
|
1314
1313
|
}
|
|
1314
|
+
markAsPristine() {
|
|
1315
|
+
this.nodeState.markAsPristine();
|
|
1316
|
+
}
|
|
1317
|
+
markAsUntouched() {
|
|
1318
|
+
this.nodeState.markAsUntouched();
|
|
1319
|
+
}
|
|
1315
1320
|
reset(value) {
|
|
1316
1321
|
untracked(() => this._reset(value));
|
|
1317
1322
|
}
|
|
@@ -1325,12 +1330,25 @@ class FieldNode {
|
|
|
1325
1330
|
child._reset();
|
|
1326
1331
|
}
|
|
1327
1332
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1333
|
+
controlValueSignal() {
|
|
1334
|
+
const controlValue = linkedSignal(this.value, ...(ngDevMode ? [{
|
|
1335
|
+
debugName: "controlValue"
|
|
1336
|
+
}] : []));
|
|
1337
|
+
const {
|
|
1338
|
+
set,
|
|
1339
|
+
update
|
|
1340
|
+
} = controlValue;
|
|
1341
|
+
controlValue.set = newValue => {
|
|
1342
|
+
set(newValue);
|
|
1331
1343
|
this.markAsDirty();
|
|
1332
1344
|
this.debounceSync();
|
|
1333
|
-
}
|
|
1345
|
+
};
|
|
1346
|
+
controlValue.update = updateFn => {
|
|
1347
|
+
update(updateFn);
|
|
1348
|
+
this.markAsDirty();
|
|
1349
|
+
this.debounceSync();
|
|
1350
|
+
};
|
|
1351
|
+
return controlValue;
|
|
1334
1352
|
}
|
|
1335
1353
|
sync() {
|
|
1336
1354
|
this.value.set(this.controlValue());
|
|
@@ -1343,8 +1361,10 @@ class FieldNode {
|
|
|
1343
1361
|
}
|
|
1344
1362
|
}
|
|
1345
1363
|
async debounceSync() {
|
|
1346
|
-
|
|
1347
|
-
|
|
1364
|
+
const debouncer = untracked(() => {
|
|
1365
|
+
this.pendingSync()?.abort();
|
|
1366
|
+
return this.nodeState.debouncer();
|
|
1367
|
+
});
|
|
1348
1368
|
if (debouncer) {
|
|
1349
1369
|
const controller = new AbortController();
|
|
1350
1370
|
const promise = debouncer(controller.signal);
|
|
@@ -1502,9 +1522,11 @@ class BasicFieldAdapter {
|
|
|
1502
1522
|
class FormFieldManager {
|
|
1503
1523
|
injector;
|
|
1504
1524
|
rootName;
|
|
1505
|
-
|
|
1525
|
+
submitOptions;
|
|
1526
|
+
constructor(injector, rootName, submitOptions) {
|
|
1506
1527
|
this.injector = injector;
|
|
1507
1528
|
this.rootName = rootName ?? `${this.injector.get(APP_ID)}.form${nextFormId++}`;
|
|
1529
|
+
this.submitOptions = submitOptions;
|
|
1508
1530
|
}
|
|
1509
1531
|
structures = new Set();
|
|
1510
1532
|
createFieldManagementEffect(root) {
|
|
@@ -1552,11 +1574,11 @@ function form(...args) {
|
|
|
1552
1574
|
const [model, schema, options] = normalizeFormArgs(args);
|
|
1553
1575
|
const injector = options?.injector ?? inject(Injector);
|
|
1554
1576
|
const pathNode = runInInjectionContext(injector, () => SchemaImpl.rootCompile(schema));
|
|
1555
|
-
const fieldManager = new FormFieldManager(injector, options?.name);
|
|
1577
|
+
const fieldManager = new FormFieldManager(injector, options?.name, options?.submission);
|
|
1556
1578
|
const adapter = options?.adapter ?? new BasicFieldAdapter();
|
|
1557
1579
|
const fieldRoot = FieldNode.newRoot(fieldManager, model, pathNode, adapter);
|
|
1558
1580
|
fieldManager.createFieldManagementEffect(fieldRoot.structure);
|
|
1559
|
-
return fieldRoot.
|
|
1581
|
+
return fieldRoot.fieldTree;
|
|
1560
1582
|
}
|
|
1561
1583
|
function applyEach(path, schema) {
|
|
1562
1584
|
assertPathIsCurrent(path);
|
|
@@ -1581,30 +1603,64 @@ function applyWhenValue(path, predicate, schema) {
|
|
|
1581
1603
|
value
|
|
1582
1604
|
}) => predicate(value()), schema);
|
|
1583
1605
|
}
|
|
1584
|
-
async function submit(form,
|
|
1585
|
-
const node = form
|
|
1586
|
-
const
|
|
1606
|
+
async function submit(form, options) {
|
|
1607
|
+
const node = untracked(form);
|
|
1608
|
+
const field = options === undefined ? node.structure.root.fieldProxy : form;
|
|
1609
|
+
const detail = {
|
|
1610
|
+
root: node.structure.root.fieldProxy,
|
|
1611
|
+
submitted: form
|
|
1612
|
+
};
|
|
1613
|
+
options = typeof options === 'function' ? {
|
|
1614
|
+
action: options
|
|
1615
|
+
} : options ?? node.structure.fieldManager.submitOptions;
|
|
1616
|
+
const action = options?.action;
|
|
1617
|
+
if (!action) {
|
|
1618
|
+
throw new _RuntimeError(1915, (typeof ngDevMode === 'undefined' || ngDevMode) && 'Cannot submit form with no submit action. Specify the action when creating the form, or as an additional argument to `submit()`.');
|
|
1619
|
+
}
|
|
1620
|
+
const onInvalid = options?.onInvalid;
|
|
1621
|
+
const ignoreValidators = options?.ignoreValidators ?? 'pending';
|
|
1622
|
+
let shouldRunAction = true;
|
|
1623
|
+
untracked(() => {
|
|
1587
1624
|
markAllAsTouched(node);
|
|
1588
|
-
|
|
1625
|
+
if (ignoreValidators === 'none') {
|
|
1626
|
+
shouldRunAction = node.valid();
|
|
1627
|
+
} else if (ignoreValidators === 'pending') {
|
|
1628
|
+
shouldRunAction = !node.invalid();
|
|
1629
|
+
}
|
|
1589
1630
|
});
|
|
1590
|
-
if (invalid) {
|
|
1591
|
-
return;
|
|
1592
|
-
}
|
|
1593
|
-
node.submitState.selfSubmitting.set(true);
|
|
1594
1631
|
try {
|
|
1595
|
-
|
|
1596
|
-
|
|
1632
|
+
if (shouldRunAction) {
|
|
1633
|
+
node.submitState.selfSubmitting.set(true);
|
|
1634
|
+
const errors = await untracked(() => action?.(field, detail));
|
|
1635
|
+
errors && setSubmissionErrors(node, errors);
|
|
1636
|
+
return !errors || isArray(errors) && errors.length === 0;
|
|
1637
|
+
} else {
|
|
1638
|
+
untracked(() => onInvalid?.(field, detail));
|
|
1639
|
+
}
|
|
1640
|
+
return false;
|
|
1597
1641
|
} finally {
|
|
1598
1642
|
node.submitState.selfSubmitting.set(false);
|
|
1599
1643
|
}
|
|
1600
1644
|
}
|
|
1645
|
+
function schema(fn) {
|
|
1646
|
+
return SchemaImpl.create(fn);
|
|
1647
|
+
}
|
|
1648
|
+
function markAllAsTouched(node) {
|
|
1649
|
+
if (node.validationState.shouldSkipValidation()) {
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
node.markAsTouched();
|
|
1653
|
+
for (const child of node.structure.children()) {
|
|
1654
|
+
markAllAsTouched(child);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1601
1657
|
function setSubmissionErrors(submittedField, errors) {
|
|
1602
1658
|
if (!isArray(errors)) {
|
|
1603
1659
|
errors = [errors];
|
|
1604
1660
|
}
|
|
1605
1661
|
const errorsByField = new Map();
|
|
1606
1662
|
for (const error of errors) {
|
|
1607
|
-
const errorWithField = addDefaultField(error, submittedField.
|
|
1663
|
+
const errorWithField = addDefaultField(error, submittedField.fieldTree);
|
|
1608
1664
|
const field = errorWithField.fieldTree();
|
|
1609
1665
|
let fieldErrors = errorsByField.get(field);
|
|
1610
1666
|
if (!fieldErrors) {
|
|
@@ -1617,18 +1673,57 @@ function setSubmissionErrors(submittedField, errors) {
|
|
|
1617
1673
|
field.submitState.submissionErrors.set(fieldErrors);
|
|
1618
1674
|
}
|
|
1619
1675
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1676
|
+
|
|
1677
|
+
class CompatValidationError {
|
|
1678
|
+
kind = 'compat';
|
|
1679
|
+
control;
|
|
1680
|
+
fieldTree;
|
|
1681
|
+
context;
|
|
1682
|
+
message;
|
|
1683
|
+
constructor({
|
|
1684
|
+
context,
|
|
1685
|
+
kind,
|
|
1686
|
+
control
|
|
1687
|
+
}) {
|
|
1688
|
+
this.context = context;
|
|
1689
|
+
this.kind = kind;
|
|
1690
|
+
this.control = control;
|
|
1691
|
+
}
|
|
1622
1692
|
}
|
|
1623
|
-
function
|
|
1624
|
-
if (
|
|
1625
|
-
return;
|
|
1693
|
+
function signalErrorsToValidationErrors(errors) {
|
|
1694
|
+
if (errors.length === 0) {
|
|
1695
|
+
return null;
|
|
1626
1696
|
}
|
|
1627
|
-
|
|
1628
|
-
for (const
|
|
1629
|
-
|
|
1697
|
+
const errObj = {};
|
|
1698
|
+
for (const error of errors) {
|
|
1699
|
+
errObj[error.kind] = error instanceof CompatValidationError ? error.context : error;
|
|
1700
|
+
}
|
|
1701
|
+
return errObj;
|
|
1702
|
+
}
|
|
1703
|
+
function reactiveErrorsToSignalErrors(errors, control) {
|
|
1704
|
+
if (errors === null) {
|
|
1705
|
+
return [];
|
|
1630
1706
|
}
|
|
1707
|
+
return Object.entries(errors).map(([kind, context]) => {
|
|
1708
|
+
return new CompatValidationError({
|
|
1709
|
+
context,
|
|
1710
|
+
kind,
|
|
1711
|
+
control
|
|
1712
|
+
});
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
function extractNestedReactiveErrors(control) {
|
|
1716
|
+
const errors = [];
|
|
1717
|
+
if (control.errors) {
|
|
1718
|
+
errors.push(...reactiveErrorsToSignalErrors(control.errors, control));
|
|
1719
|
+
}
|
|
1720
|
+
if (control instanceof FormGroup || control instanceof FormArray) {
|
|
1721
|
+
for (const c of Object.values(control.controls)) {
|
|
1722
|
+
errors.push(...extractNestedReactiveErrors(c));
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return errors;
|
|
1631
1726
|
}
|
|
1632
1727
|
|
|
1633
|
-
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, metadata, normalizeFormArgs, schema, submit };
|
|
1634
|
-
//# sourceMappingURL=
|
|
1728
|
+
export { BasicFieldAdapter, CompatValidationError, DEBOUNCER, FieldNode, FieldNodeState, FieldNodeStructure, FieldPathNode, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MetadataKey, MetadataReducer, PATTERN, REQUIRED, addDefaultField, apply, applyEach, applyWhen, applyWhenValue, assertPathIsCurrent, calculateValidationSelfStatus, createManagedMetadataKey, createMetadataKey, extractNestedReactiveErrors, form, getInjectorFromOptions, metadata, normalizeFormArgs, schema, signalErrorsToValidationErrors, submit };
|
|
1729
|
+
//# sourceMappingURL=_validation_errors-chunk.mjs.map
|