@angular/forms 21.2.0-next.0 → 21.2.0-next.2
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 +149 -27
- package/fesm2022/_structure-chunk.mjs.map +1 -1
- package/fesm2022/forms.mjs +128 -128
- package/fesm2022/forms.mjs.map +1 -1
- package/fesm2022/signals-compat.mjs +389 -47
- package/fesm2022/signals-compat.mjs.map +1 -1
- package/fesm2022/signals.mjs +419 -70
- 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 +415 -1176
- package/types/forms.d.ts +1 -1
- package/types/signals-compat.d.ts +131 -8
- package/types/signals.d.ts +17 -46
|
@@ -1,13 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v21.2.0-next.
|
|
2
|
+
* @license Angular v21.2.0-next.2
|
|
3
3
|
* (c) 2010-2026 Google LLC. https://angular.dev/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { FormGroup, FormArray, AbstractControl } from '@angular/forms';
|
|
7
8
|
import { untracked, ɵRuntimeError as _RuntimeError, computed, runInInjectionContext, Injector, linkedSignal, signal, APP_ID, effect, inject } from '@angular/core';
|
|
8
|
-
import { AbstractControl } from '@angular/forms';
|
|
9
9
|
import { SIGNAL } from '@angular/core/primitives/signals';
|
|
10
10
|
|
|
11
|
+
class CompatValidationError {
|
|
12
|
+
kind = 'compat';
|
|
13
|
+
control;
|
|
14
|
+
fieldTree;
|
|
15
|
+
context;
|
|
16
|
+
message;
|
|
17
|
+
constructor({
|
|
18
|
+
context,
|
|
19
|
+
kind,
|
|
20
|
+
control
|
|
21
|
+
}) {
|
|
22
|
+
this.context = context;
|
|
23
|
+
this.kind = kind;
|
|
24
|
+
this.control = control;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function signalErrorsToValidationErrors(errors) {
|
|
28
|
+
if (errors.length === 0) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const errObj = {};
|
|
32
|
+
for (const error of errors) {
|
|
33
|
+
errObj[error.kind] = error instanceof CompatValidationError ? error.context : error;
|
|
34
|
+
}
|
|
35
|
+
return errObj;
|
|
36
|
+
}
|
|
37
|
+
function reactiveErrorsToSignalErrors(errors, control) {
|
|
38
|
+
if (errors === null) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
return Object.entries(errors).map(([kind, context]) => {
|
|
42
|
+
return new CompatValidationError({
|
|
43
|
+
context,
|
|
44
|
+
kind,
|
|
45
|
+
control
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function extractNestedReactiveErrors(control) {
|
|
50
|
+
const errors = [];
|
|
51
|
+
if (control.errors) {
|
|
52
|
+
errors.push(...reactiveErrorsToSignalErrors(control.errors, control));
|
|
53
|
+
}
|
|
54
|
+
if (control instanceof FormGroup || control instanceof FormArray) {
|
|
55
|
+
for (const c of Object.values(control.controls)) {
|
|
56
|
+
errors.push(...extractNestedReactiveErrors(c));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return errors;
|
|
60
|
+
}
|
|
61
|
+
|
|
11
62
|
let boundPathDepth = 0;
|
|
12
63
|
function getBoundPathDepth() {
|
|
13
64
|
return boundPathDepth;
|
|
@@ -647,10 +698,19 @@ class FieldValidationState {
|
|
|
647
698
|
}, ...(ngDevMode ? [{
|
|
648
699
|
debugName: "asyncErrors"
|
|
649
700
|
}] : []));
|
|
650
|
-
|
|
701
|
+
parseErrors = computed(() => this.node.formFieldBindings().flatMap(field => field.parseErrors()), ...(ngDevMode ? [{
|
|
702
|
+
debugName: "parseErrors"
|
|
703
|
+
}] : []));
|
|
704
|
+
errors = computed(() => [...this.parseErrors(), ...this.syncErrors(), ...this.asyncErrors().filter(err => err !== 'pending')], ...(ngDevMode ? [{
|
|
651
705
|
debugName: "errors"
|
|
652
706
|
}] : []));
|
|
653
|
-
errorSummary = computed(() =>
|
|
707
|
+
errorSummary = computed(() => {
|
|
708
|
+
const errors = this.node.structure.reduceChildren(this.errors(), (child, result) => [...result, ...child.errorSummary()]);
|
|
709
|
+
if (typeof ngServerMode === 'undefined' || !ngServerMode) {
|
|
710
|
+
untracked(() => errors.sort(compareErrorPosition));
|
|
711
|
+
}
|
|
712
|
+
return errors;
|
|
713
|
+
}, ...(ngDevMode ? [{
|
|
654
714
|
debugName: "errorSummary"
|
|
655
715
|
}] : []));
|
|
656
716
|
pending = computed(() => this.node.structure.reduceChildren(this.asyncErrors().includes('pending'), (child, value) => value || child.validationState.asyncErrors().includes('pending')), ...(ngDevMode ? [{
|
|
@@ -701,6 +761,20 @@ function addDefaultField(errors, fieldTree) {
|
|
|
701
761
|
}
|
|
702
762
|
return errors;
|
|
703
763
|
}
|
|
764
|
+
function getFirstBoundElement(error) {
|
|
765
|
+
if (error.formField) return error.formField.element;
|
|
766
|
+
return error.fieldTree().formFieldBindings().reduce((el, binding) => {
|
|
767
|
+
if (!el || !binding.element) return el ?? binding.element;
|
|
768
|
+
return el.compareDocumentPosition(binding.element) & Node.DOCUMENT_POSITION_PRECEDING ? binding.element : el;
|
|
769
|
+
}, undefined);
|
|
770
|
+
}
|
|
771
|
+
function compareErrorPosition(a, b) {
|
|
772
|
+
const aEl = getFirstBoundElement(a);
|
|
773
|
+
const bEl = getFirstBoundElement(b);
|
|
774
|
+
if (aEl === bEl) return 0;
|
|
775
|
+
if (aEl === undefined || bEl === undefined) return aEl === undefined ? 1 : -1;
|
|
776
|
+
return aEl.compareDocumentPosition(bEl) & Node.DOCUMENT_POSITION_PRECEDING ? 1 : -1;
|
|
777
|
+
}
|
|
704
778
|
|
|
705
779
|
const DEBOUNCER = createMetadataKey();
|
|
706
780
|
|
|
@@ -1163,6 +1237,7 @@ class FieldNode {
|
|
|
1163
1237
|
nodeState;
|
|
1164
1238
|
submitState;
|
|
1165
1239
|
fieldAdapter;
|
|
1240
|
+
controlValue;
|
|
1166
1241
|
_context = undefined;
|
|
1167
1242
|
get context() {
|
|
1168
1243
|
return this._context ??= new FieldNodeContext(this);
|
|
@@ -1177,6 +1252,7 @@ class FieldNode {
|
|
|
1177
1252
|
this.nodeState = this.fieldAdapter.createNodeState(this, options);
|
|
1178
1253
|
this.metadataState = new FieldMetadataState(this);
|
|
1179
1254
|
this.submitState = new FieldSubmitState(this);
|
|
1255
|
+
this.controlValue = this.controlValueSignal();
|
|
1180
1256
|
}
|
|
1181
1257
|
focusBoundControl(options) {
|
|
1182
1258
|
this.getBindingForFocus()?.focus(options);
|
|
@@ -1202,18 +1278,15 @@ class FieldNode {
|
|
|
1202
1278
|
get value() {
|
|
1203
1279
|
return this.structure.value;
|
|
1204
1280
|
}
|
|
1205
|
-
_controlValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{
|
|
1206
|
-
debugName: "_controlValue"
|
|
1207
|
-
}] : []));
|
|
1208
|
-
get controlValue() {
|
|
1209
|
-
return this._controlValue.asReadonly();
|
|
1210
|
-
}
|
|
1211
1281
|
get keyInParent() {
|
|
1212
1282
|
return this.structure.keyInParent;
|
|
1213
1283
|
}
|
|
1214
1284
|
get errors() {
|
|
1215
1285
|
return this.validationState.errors;
|
|
1216
1286
|
}
|
|
1287
|
+
get parseErrors() {
|
|
1288
|
+
return this.validationState.parseErrors;
|
|
1289
|
+
}
|
|
1217
1290
|
get errorSummary() {
|
|
1218
1291
|
return this.validationState.errorSummary;
|
|
1219
1292
|
}
|
|
@@ -1286,6 +1359,12 @@ class FieldNode {
|
|
|
1286
1359
|
markAsDirty() {
|
|
1287
1360
|
this.nodeState.markAsDirty();
|
|
1288
1361
|
}
|
|
1362
|
+
markAsPristine() {
|
|
1363
|
+
this.nodeState.markAsPristine();
|
|
1364
|
+
}
|
|
1365
|
+
markAsUntouched() {
|
|
1366
|
+
this.nodeState.markAsUntouched();
|
|
1367
|
+
}
|
|
1289
1368
|
reset(value) {
|
|
1290
1369
|
untracked(() => this._reset(value));
|
|
1291
1370
|
}
|
|
@@ -1299,12 +1378,25 @@ class FieldNode {
|
|
|
1299
1378
|
child._reset();
|
|
1300
1379
|
}
|
|
1301
1380
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1381
|
+
controlValueSignal() {
|
|
1382
|
+
const controlValue = linkedSignal(this.value, ...(ngDevMode ? [{
|
|
1383
|
+
debugName: "controlValue"
|
|
1384
|
+
}] : []));
|
|
1385
|
+
const {
|
|
1386
|
+
set,
|
|
1387
|
+
update
|
|
1388
|
+
} = controlValue;
|
|
1389
|
+
controlValue.set = newValue => {
|
|
1390
|
+
set(newValue);
|
|
1305
1391
|
this.markAsDirty();
|
|
1306
1392
|
this.debounceSync();
|
|
1307
|
-
}
|
|
1393
|
+
};
|
|
1394
|
+
controlValue.update = updateFn => {
|
|
1395
|
+
update(updateFn);
|
|
1396
|
+
this.markAsDirty();
|
|
1397
|
+
this.debounceSync();
|
|
1398
|
+
};
|
|
1399
|
+
return controlValue;
|
|
1308
1400
|
}
|
|
1309
1401
|
sync() {
|
|
1310
1402
|
this.value.set(this.controlValue());
|
|
@@ -1317,8 +1409,10 @@ class FieldNode {
|
|
|
1317
1409
|
}
|
|
1318
1410
|
}
|
|
1319
1411
|
async debounceSync() {
|
|
1320
|
-
|
|
1321
|
-
|
|
1412
|
+
const debouncer = untracked(() => {
|
|
1413
|
+
this.pendingSync()?.abort();
|
|
1414
|
+
return this.nodeState.debouncer();
|
|
1415
|
+
});
|
|
1322
1416
|
if (debouncer) {
|
|
1323
1417
|
const controller = new AbortController();
|
|
1324
1418
|
const promise = debouncer(controller.signal);
|
|
@@ -1476,9 +1570,11 @@ class BasicFieldAdapter {
|
|
|
1476
1570
|
class FormFieldManager {
|
|
1477
1571
|
injector;
|
|
1478
1572
|
rootName;
|
|
1479
|
-
|
|
1573
|
+
submitOptions;
|
|
1574
|
+
constructor(injector, rootName, submitOptions) {
|
|
1480
1575
|
this.injector = injector;
|
|
1481
1576
|
this.rootName = rootName ?? `${this.injector.get(APP_ID)}.form${nextFormId++}`;
|
|
1577
|
+
this.submitOptions = submitOptions;
|
|
1482
1578
|
}
|
|
1483
1579
|
structures = new Set();
|
|
1484
1580
|
createFieldManagementEffect(root) {
|
|
@@ -1526,7 +1622,7 @@ function form(...args) {
|
|
|
1526
1622
|
const [model, schema, options] = normalizeFormArgs(args);
|
|
1527
1623
|
const injector = options?.injector ?? inject(Injector);
|
|
1528
1624
|
const pathNode = runInInjectionContext(injector, () => SchemaImpl.rootCompile(schema));
|
|
1529
|
-
const fieldManager = new FormFieldManager(injector, options?.name);
|
|
1625
|
+
const fieldManager = new FormFieldManager(injector, options?.name, options?.submission);
|
|
1530
1626
|
const adapter = options?.adapter ?? new BasicFieldAdapter();
|
|
1531
1627
|
const fieldRoot = FieldNode.newRoot(fieldManager, model, pathNode, adapter);
|
|
1532
1628
|
fieldManager.createFieldManagementEffect(fieldRoot.structure);
|
|
@@ -1555,16 +1651,39 @@ function applyWhenValue(path, predicate, schema) {
|
|
|
1555
1651
|
value
|
|
1556
1652
|
}) => predicate(value()), schema);
|
|
1557
1653
|
}
|
|
1558
|
-
async function submit(form,
|
|
1654
|
+
async function submit(form, options) {
|
|
1559
1655
|
const node = form();
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1656
|
+
const opts = typeof options === 'function' ? {
|
|
1657
|
+
action: options
|
|
1658
|
+
} : {
|
|
1659
|
+
...(node.structure.fieldManager.submitOptions ?? {}),
|
|
1660
|
+
...(options ?? {})
|
|
1661
|
+
};
|
|
1662
|
+
const action = opts?.action;
|
|
1663
|
+
if (!action) {
|
|
1664
|
+
throw new _RuntimeError(1915, ngDevMode && 'Cannot submit form with no submit action. Specify the action when creating the form, or as an additional argument to `submit()`.');
|
|
1665
|
+
}
|
|
1666
|
+
const onInvalid = opts?.onInvalid;
|
|
1667
|
+
const ignoreValidators = opts?.ignoreValidators ?? 'pending';
|
|
1668
|
+
let shouldRunAction = true;
|
|
1669
|
+
untracked(() => {
|
|
1670
|
+
markAllAsTouched(node);
|
|
1671
|
+
if (ignoreValidators === 'none') {
|
|
1672
|
+
shouldRunAction = node.valid();
|
|
1673
|
+
} else if (ignoreValidators === 'pending') {
|
|
1674
|
+
shouldRunAction = !node.invalid();
|
|
1675
|
+
}
|
|
1676
|
+
});
|
|
1565
1677
|
try {
|
|
1566
|
-
|
|
1567
|
-
|
|
1678
|
+
if (shouldRunAction) {
|
|
1679
|
+
node.submitState.selfSubmitting.set(true);
|
|
1680
|
+
const errors = await untracked(() => action?.(form));
|
|
1681
|
+
errors && setSubmissionErrors(node, errors);
|
|
1682
|
+
return !errors || isArray(errors) && errors.length === 0;
|
|
1683
|
+
} else {
|
|
1684
|
+
untracked(() => onInvalid?.(form));
|
|
1685
|
+
}
|
|
1686
|
+
return false;
|
|
1568
1687
|
} finally {
|
|
1569
1688
|
node.submitState.selfSubmitting.set(false);
|
|
1570
1689
|
}
|
|
@@ -1592,11 +1711,14 @@ function schema(fn) {
|
|
|
1592
1711
|
return SchemaImpl.create(fn);
|
|
1593
1712
|
}
|
|
1594
1713
|
function markAllAsTouched(node) {
|
|
1714
|
+
if (node.validationState.shouldSkipValidation()) {
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1595
1717
|
node.markAsTouched();
|
|
1596
1718
|
for (const child of node.structure.children()) {
|
|
1597
1719
|
markAllAsTouched(child);
|
|
1598
1720
|
}
|
|
1599
1721
|
}
|
|
1600
1722
|
|
|
1601
|
-
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 };
|
|
1723
|
+
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 };
|
|
1602
1724
|
//# sourceMappingURL=_structure-chunk.mjs.map
|