@angular/forms 21.2.0-next.1 → 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.
@@ -1,13 +1,64 @@
1
1
  /**
2
- * @license Angular v21.2.0-next.1
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;
@@ -1186,6 +1237,7 @@ class FieldNode {
1186
1237
  nodeState;
1187
1238
  submitState;
1188
1239
  fieldAdapter;
1240
+ controlValue;
1189
1241
  _context = undefined;
1190
1242
  get context() {
1191
1243
  return this._context ??= new FieldNodeContext(this);
@@ -1200,6 +1252,7 @@ class FieldNode {
1200
1252
  this.nodeState = this.fieldAdapter.createNodeState(this, options);
1201
1253
  this.metadataState = new FieldMetadataState(this);
1202
1254
  this.submitState = new FieldSubmitState(this);
1255
+ this.controlValue = this.controlValueSignal();
1203
1256
  }
1204
1257
  focusBoundControl(options) {
1205
1258
  this.getBindingForFocus()?.focus(options);
@@ -1225,12 +1278,6 @@ class FieldNode {
1225
1278
  get value() {
1226
1279
  return this.structure.value;
1227
1280
  }
1228
- _controlValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{
1229
- debugName: "_controlValue"
1230
- }] : []));
1231
- get controlValue() {
1232
- return this._controlValue.asReadonly();
1233
- }
1234
1281
  get keyInParent() {
1235
1282
  return this.structure.keyInParent;
1236
1283
  }
@@ -1312,6 +1359,12 @@ class FieldNode {
1312
1359
  markAsDirty() {
1313
1360
  this.nodeState.markAsDirty();
1314
1361
  }
1362
+ markAsPristine() {
1363
+ this.nodeState.markAsPristine();
1364
+ }
1365
+ markAsUntouched() {
1366
+ this.nodeState.markAsUntouched();
1367
+ }
1315
1368
  reset(value) {
1316
1369
  untracked(() => this._reset(value));
1317
1370
  }
@@ -1325,12 +1378,25 @@ class FieldNode {
1325
1378
  child._reset();
1326
1379
  }
1327
1380
  }
1328
- setControlValue(newValue) {
1329
- untracked(() => {
1330
- this._controlValue.set(newValue);
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);
1331
1391
  this.markAsDirty();
1332
1392
  this.debounceSync();
1333
- });
1393
+ };
1394
+ controlValue.update = updateFn => {
1395
+ update(updateFn);
1396
+ this.markAsDirty();
1397
+ this.debounceSync();
1398
+ };
1399
+ return controlValue;
1334
1400
  }
1335
1401
  sync() {
1336
1402
  this.value.set(this.controlValue());
@@ -1343,8 +1409,10 @@ class FieldNode {
1343
1409
  }
1344
1410
  }
1345
1411
  async debounceSync() {
1346
- this.pendingSync()?.abort();
1347
- const debouncer = this.nodeState.debouncer();
1412
+ const debouncer = untracked(() => {
1413
+ this.pendingSync()?.abort();
1414
+ return this.nodeState.debouncer();
1415
+ });
1348
1416
  if (debouncer) {
1349
1417
  const controller = new AbortController();
1350
1418
  const promise = debouncer(controller.signal);
@@ -1502,9 +1570,11 @@ class BasicFieldAdapter {
1502
1570
  class FormFieldManager {
1503
1571
  injector;
1504
1572
  rootName;
1505
- constructor(injector, rootName) {
1573
+ submitOptions;
1574
+ constructor(injector, rootName, submitOptions) {
1506
1575
  this.injector = injector;
1507
1576
  this.rootName = rootName ?? `${this.injector.get(APP_ID)}.form${nextFormId++}`;
1577
+ this.submitOptions = submitOptions;
1508
1578
  }
1509
1579
  structures = new Set();
1510
1580
  createFieldManagementEffect(root) {
@@ -1552,7 +1622,7 @@ function form(...args) {
1552
1622
  const [model, schema, options] = normalizeFormArgs(args);
1553
1623
  const injector = options?.injector ?? inject(Injector);
1554
1624
  const pathNode = runInInjectionContext(injector, () => SchemaImpl.rootCompile(schema));
1555
- const fieldManager = new FormFieldManager(injector, options?.name);
1625
+ const fieldManager = new FormFieldManager(injector, options?.name, options?.submission);
1556
1626
  const adapter = options?.adapter ?? new BasicFieldAdapter();
1557
1627
  const fieldRoot = FieldNode.newRoot(fieldManager, model, pathNode, adapter);
1558
1628
  fieldManager.createFieldManagementEffect(fieldRoot.structure);
@@ -1581,19 +1651,39 @@ function applyWhenValue(path, predicate, schema) {
1581
1651
  value
1582
1652
  }) => predicate(value()), schema);
1583
1653
  }
1584
- async function submit(form, action) {
1654
+ async function submit(form, options) {
1585
1655
  const node = form();
1586
- const invalid = untracked(() => {
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(() => {
1587
1670
  markAllAsTouched(node);
1588
- return node.invalid();
1671
+ if (ignoreValidators === 'none') {
1672
+ shouldRunAction = node.valid();
1673
+ } else if (ignoreValidators === 'pending') {
1674
+ shouldRunAction = !node.invalid();
1675
+ }
1589
1676
  });
1590
- if (invalid) {
1591
- return;
1592
- }
1593
- node.submitState.selfSubmitting.set(true);
1594
1677
  try {
1595
- const errors = await action(form);
1596
- errors && setSubmissionErrors(node, errors);
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;
1597
1687
  } finally {
1598
1688
  node.submitState.selfSubmitting.set(false);
1599
1689
  }
@@ -1630,5 +1720,5 @@ function markAllAsTouched(node) {
1630
1720
  }
1631
1721
  }
1632
1722
 
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 };
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 };
1634
1724
  //# sourceMappingURL=_structure-chunk.mjs.map