@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.
@@ -1,11 +1,11 @@
1
1
  /**
2
- * @license Angular v21.2.0-next.1
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.fieldProxy), ...(ngDevMode ? [{
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.fieldProxy);
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.fieldProxy;
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.fieldProxy;
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.fieldProxy);
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
- setControlValue(newValue) {
1329
- untracked(() => {
1330
- this._controlValue.set(newValue);
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
- this.pendingSync()?.abort();
1347
- const debouncer = this.nodeState.debouncer();
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
- constructor(injector, rootName) {
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.fieldProxy;
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, action) {
1585
- const node = form();
1586
- const invalid = untracked(() => {
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
- return node.invalid();
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
- const errors = await action(form);
1596
- errors && setSubmissionErrors(node, errors);
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.fieldProxy);
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
- function schema(fn) {
1621
- return SchemaImpl.create(fn);
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 markAllAsTouched(node) {
1624
- if (node.validationState.shouldSkipValidation()) {
1625
- return;
1693
+ function signalErrorsToValidationErrors(errors) {
1694
+ if (errors.length === 0) {
1695
+ return null;
1626
1696
  }
1627
- node.markAsTouched();
1628
- for (const child of node.structure.children()) {
1629
- markAllAsTouched(child);
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=_structure-chunk.mjs.map
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