@angular/forms 21.0.0-rc.1 → 21.0.0-rc.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,5 +1,5 @@
1
1
  /**
2
- * @license Angular v21.0.0-rc.1
2
+ * @license Angular v21.0.0-rc.2
3
3
  * (c) 2010-2025 Google LLC. https://angular.dev/
4
4
  * License: MIT
5
5
  */
@@ -7,7 +7,7 @@
7
7
  import { httpResource } from '@angular/common/http';
8
8
  import * as i0 from '@angular/core';
9
9
  import { computed, untracked, InjectionToken, inject, Injector, input, ɵCONTROL as _CONTROL, effect, Directive, runInInjectionContext, linkedSignal, signal, APP_ID, ɵisPromise as _isPromise, resource } from '@angular/core';
10
- import { Validators, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
10
+ import { Validators, NG_VALUE_ACCESSOR, NgControl, AbstractControl } from '@angular/forms';
11
11
  import { SIGNAL } from '@angular/core/primitives/signals';
12
12
 
13
13
  function isArray(value) {
@@ -150,6 +150,21 @@ function addDefaultField(errors, field) {
150
150
  return errors;
151
151
  }
152
152
 
153
+ let boundPathDepth = 0;
154
+ function getBoundPathDepth() {
155
+ return boundPathDepth;
156
+ }
157
+ function setBoundPathDepthForResolution(fn, depth) {
158
+ return (...args) => {
159
+ try {
160
+ boundPathDepth = depth;
161
+ return fn(...args);
162
+ } finally {
163
+ boundPathDepth = 0;
164
+ }
165
+ };
166
+ }
167
+
153
168
  const DYNAMIC = Symbol();
154
169
  const IGNORED = Symbol();
155
170
  class AbstractLogic {
@@ -307,21 +322,6 @@ class LogicContainer {
307
322
  }
308
323
  }
309
324
 
310
- let boundPathDepth = 0;
311
- function getBoundPathDepth() {
312
- return boundPathDepth;
313
- }
314
- function setBoundPathDepthForResolution(fn, depth) {
315
- return (...args) => {
316
- try {
317
- boundPathDepth = depth;
318
- return fn(...args);
319
- } finally {
320
- boundPathDepth = 0;
321
- }
322
- };
323
- }
324
-
325
325
  class AbstractLogicNodeBuilder {
326
326
  depth;
327
327
  constructor(depth) {
@@ -362,6 +362,12 @@ class LogicNodeBuilder extends AbstractLogicNodeBuilder {
362
362
  this.getCurrent().addMetadataFactory(key, factory);
363
363
  }
364
364
  getChild(key) {
365
+ if (key === DYNAMIC) {
366
+ const children = this.getCurrent().children;
367
+ if (children.size > (children.has(DYNAMIC) ? 1 : 0)) {
368
+ this.current = undefined;
369
+ }
370
+ }
365
371
  return this.getCurrent().getChild(key);
366
372
  }
367
373
  hasLogic(builder) {
@@ -510,16 +516,16 @@ function getAllChildBuilders(builder, key) {
510
516
  return children;
511
517
  });
512
518
  } else if (builder instanceof NonMergeableLogicNodeBuilder) {
513
- if (builder.children.has(key)) {
514
- return [{
515
- builder: builder.children.get(key),
516
- predicates: []
517
- }];
518
- }
519
+ return [...(key !== DYNAMIC && builder.children.has(DYNAMIC) ? [{
520
+ builder: builder.getChild(DYNAMIC),
521
+ predicates: []
522
+ }] : []), ...(builder.children.has(key) ? [{
523
+ builder: builder.getChild(key),
524
+ predicates: []
525
+ }] : [])];
519
526
  } else {
520
527
  throw new Error('Unknown LogicNodeBuilder type');
521
528
  }
522
- return [];
523
529
  }
524
530
  function createLogic(builder, predicates, depth) {
525
531
  const logic = new LogicContainer(predicates);
@@ -569,9 +575,6 @@ class FieldPathNode {
569
575
  }
570
576
  return this.parent.builder.getChild(this.keyInParent);
571
577
  }
572
- get element() {
573
- return this.getChild(DYNAMIC);
574
- }
575
578
  getChild(key) {
576
579
  if (!this.children.has(key)) {
577
580
  this.children.set(key, new FieldPathNode([...this.keys, key], this.root, this, key));
@@ -954,6 +957,21 @@ function validateHttp(path, opts) {
954
957
  });
955
958
  }
956
959
 
960
+ const DEBOUNCER = reducedMetadataKey((_, item) => item, () => undefined);
961
+
962
+ function debounce(path, durationOrDebouncer) {
963
+ assertPathIsCurrent(path);
964
+ const pathNode = FieldPathNode.unwrapFieldPath(path);
965
+ const debouncer = typeof durationOrDebouncer === 'function' ? durationOrDebouncer : durationOrDebouncer > 0 ? debounceForDuration(durationOrDebouncer) : immediate;
966
+ pathNode.builder.addAggregateMetadataRule(DEBOUNCER, () => debouncer);
967
+ }
968
+ function debounceForDuration(durationInMilliseconds) {
969
+ return () => {
970
+ return new Promise(resolve => setTimeout(resolve, durationInMilliseconds));
971
+ };
972
+ }
973
+ function immediate() {}
974
+
957
975
  class InteropNgControl {
958
976
  field;
959
977
  constructor(field) {
@@ -1041,33 +1059,12 @@ class Field {
1041
1059
  self: true
1042
1060
  });
1043
1061
  interopNgControl;
1044
- get controlValueAccessor() {
1062
+ get ɵinteropControl() {
1045
1063
  return this.controlValueAccessors?.[0] ?? this.interopNgControl?.valueAccessor ?? undefined;
1046
1064
  }
1047
- get ɵhasInteropControl() {
1048
- return this.controlValueAccessor !== undefined;
1049
- }
1050
- ɵgetOrCreateNgControl() {
1065
+ getOrCreateNgControl() {
1051
1066
  return this.interopNgControl ??= new InteropNgControl(this.state);
1052
1067
  }
1053
- ɵinteropControlCreate() {
1054
- const controlValueAccessor = this.controlValueAccessor;
1055
- controlValueAccessor.registerOnChange(value => {
1056
- const state = this.state();
1057
- state.value.set(value);
1058
- state.markAsDirty();
1059
- });
1060
- controlValueAccessor.registerOnTouched(() => this.state().markAsTouched());
1061
- }
1062
- ɵinteropControlUpdate() {
1063
- const controlValueAccessor = this.controlValueAccessor;
1064
- const value = this.state().value();
1065
- const disabled = this.state().disabled();
1066
- untracked(() => {
1067
- controlValueAccessor.writeValue(value);
1068
- controlValueAccessor.setDisabledState?.(disabled);
1069
- });
1070
- }
1071
1068
  ɵregister() {
1072
1069
  effect(onCleanup => {
1073
1070
  const fieldNode = this.state();
@@ -1081,7 +1078,7 @@ class Field {
1081
1078
  }
1082
1079
  static ɵfac = i0.ɵɵngDeclareFactory({
1083
1080
  minVersion: "12.0.0",
1084
- version: "21.0.0-rc.1",
1081
+ version: "21.0.0-rc.2",
1085
1082
  ngImport: i0,
1086
1083
  type: Field,
1087
1084
  deps: [],
@@ -1089,7 +1086,7 @@ class Field {
1089
1086
  });
1090
1087
  static ɵdir = i0.ɵɵngDeclareDirective({
1091
1088
  minVersion: "17.1.0",
1092
- version: "21.0.0-rc.1",
1089
+ version: "21.0.0-rc.2",
1093
1090
  type: Field,
1094
1091
  isStandalone: true,
1095
1092
  selector: "[field]",
@@ -1107,14 +1104,14 @@ class Field {
1107
1104
  useExisting: Field
1108
1105
  }, {
1109
1106
  provide: NgControl,
1110
- useFactory: () => inject(Field)getOrCreateNgControl()
1107
+ useFactory: () => inject(Field).getOrCreateNgControl()
1111
1108
  }],
1112
1109
  ngImport: i0
1113
1110
  });
1114
1111
  }
1115
1112
  i0.ɵɵngDeclareClassMetadata({
1116
1113
  minVersion: "12.0.0",
1117
- version: "21.0.0-rc.1",
1114
+ version: "21.0.0-rc.2",
1118
1115
  ngImport: i0,
1119
1116
  type: Field,
1120
1117
  decorators: [{
@@ -1126,7 +1123,7 @@ i0.ɵɵngDeclareClassMetadata({
1126
1123
  useExisting: Field
1127
1124
  }, {
1128
1125
  provide: NgControl,
1129
- useFactory: () => inject(Field)getOrCreateNgControl()
1126
+ useFactory: () => inject(Field).getOrCreateNgControl()
1130
1127
  }]
1131
1128
  }]
1132
1129
  }],
@@ -1187,6 +1184,9 @@ class FieldNodeContext {
1187
1184
  get key() {
1188
1185
  return this.node.structure.keyInParent;
1189
1186
  }
1187
+ get pathKeys() {
1188
+ return this.node.structure.pathKeys;
1189
+ }
1190
1190
  index = computed(() => {
1191
1191
  const key = this.key();
1192
1192
  if (!isArray(untracked(this.node.structure.parent.value))) {
@@ -1196,9 +1196,15 @@ class FieldNodeContext {
1196
1196
  }, ...(ngDevMode ? [{
1197
1197
  debugName: "index"
1198
1198
  }] : []));
1199
- fieldOf = p => this.resolve(p);
1199
+ fieldTreeOf = p => this.resolve(p);
1200
1200
  stateOf = p => this.resolve(p)();
1201
- valueOf = p => this.resolve(p)().value();
1201
+ valueOf = p => {
1202
+ const result = this.resolve(p)().value();
1203
+ if (result instanceof AbstractControl) {
1204
+ throw new Error(`Tried to read an 'AbstractControl' value form a 'form()'. Did you mean to use 'compatForm()' instead?`);
1205
+ }
1206
+ return result;
1207
+ };
1202
1208
  }
1203
1209
 
1204
1210
  class FieldMetadataState {
@@ -1235,7 +1241,7 @@ class FieldMetadataState {
1235
1241
  }
1236
1242
 
1237
1243
  const FIELD_PROXY_HANDLER = {
1238
- get(getTgt, p) {
1244
+ get(getTgt, p, receiver) {
1239
1245
  const tgt = getTgt();
1240
1246
  const child = tgt.structure.getChild(p);
1241
1247
  if (child !== undefined) {
@@ -1250,7 +1256,28 @@ const FIELD_PROXY_HANDLER = {
1250
1256
  return Array.prototype[p];
1251
1257
  }
1252
1258
  }
1259
+ if (isObject(value)) {
1260
+ if (p === Symbol.iterator) {
1261
+ return function* () {
1262
+ for (const key in receiver) {
1263
+ yield [key, receiver[key]];
1264
+ }
1265
+ };
1266
+ }
1267
+ }
1253
1268
  return undefined;
1269
+ },
1270
+ getOwnPropertyDescriptor(getTgt, prop) {
1271
+ const value = untracked(getTgt().value);
1272
+ const desc = Reflect.getOwnPropertyDescriptor(value, prop);
1273
+ if (desc && !desc.configurable) {
1274
+ desc.configurable = true;
1275
+ }
1276
+ return desc;
1277
+ },
1278
+ ownKeys(getTgt) {
1279
+ const value = untracked(getTgt().value);
1280
+ return typeof value === 'object' && value !== null ? Reflect.ownKeys(value) : [];
1254
1281
  }
1255
1282
  };
1256
1283
 
@@ -1507,8 +1534,8 @@ class FieldNode {
1507
1534
  metadataState;
1508
1535
  nodeState;
1509
1536
  submitState;
1510
- _context = undefined;
1511
1537
  fieldAdapter;
1538
+ _context = undefined;
1512
1539
  get context() {
1513
1540
  return this._context ??= new FieldNodeContext(this);
1514
1541
  }
@@ -1521,12 +1548,26 @@ class FieldNode {
1521
1548
  this.metadataState = new FieldMetadataState(this);
1522
1549
  this.submitState = new FieldSubmitState(this);
1523
1550
  }
1551
+ pendingSync = linkedSignal(...(ngDevMode ? [{
1552
+ debugName: "pendingSync",
1553
+ source: () => this.value(),
1554
+ computation: () => undefined
1555
+ }] : [{
1556
+ source: () => this.value(),
1557
+ computation: () => undefined
1558
+ }]));
1524
1559
  get logicNode() {
1525
1560
  return this.structure.logic;
1526
1561
  }
1527
1562
  get value() {
1528
1563
  return this.structure.value;
1529
1564
  }
1565
+ _controlValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{
1566
+ debugName: "_controlValue"
1567
+ }] : []));
1568
+ get controlValue() {
1569
+ return this._controlValue.asReadonly();
1570
+ }
1530
1571
  get keyInParent() {
1531
1572
  return this.structure.keyInParent;
1532
1573
  }
@@ -1601,6 +1642,7 @@ class FieldNode {
1601
1642
  }
1602
1643
  markAsTouched() {
1603
1644
  this.nodeState.markAsTouched();
1645
+ this.sync();
1604
1646
  }
1605
1647
  markAsDirty() {
1606
1648
  this.nodeState.markAsDirty();
@@ -1612,6 +1654,28 @@ class FieldNode {
1612
1654
  child.reset();
1613
1655
  }
1614
1656
  }
1657
+ setControlValue(newValue) {
1658
+ this._controlValue.set(newValue);
1659
+ this.markAsDirty();
1660
+ this.debounceSync();
1661
+ }
1662
+ sync() {
1663
+ this.value.set(this.controlValue());
1664
+ this.pendingSync.set(undefined);
1665
+ }
1666
+ debounceSync() {
1667
+ const promise = this.nodeState.debouncer();
1668
+ if (promise) {
1669
+ promise.then(() => {
1670
+ if (promise === this.pendingSync()) {
1671
+ this.sync();
1672
+ }
1673
+ });
1674
+ this.pendingSync.set(promise);
1675
+ } else {
1676
+ this.sync();
1677
+ }
1678
+ }
1615
1679
  static newRoot(fieldManager, value, pathNode, adapter) {
1616
1680
  return adapter.newRoot(fieldManager, value, pathNode, adapter);
1617
1681
  }
@@ -1688,6 +1752,16 @@ class FieldNodeState {
1688
1752
  }, ...(ngDevMode ? [{
1689
1753
  debugName: "name"
1690
1754
  }] : []));
1755
+ debouncer() {
1756
+ if (this.node.logicNode.logic.hasAggregateMetadata(DEBOUNCER)) {
1757
+ const debouncerLogic = this.node.logicNode.logic.getAggregateMetadata(DEBOUNCER);
1758
+ const debouncer = debouncerLogic.compute(this.node.context);
1759
+ if (debouncer) {
1760
+ return debouncer(this.node.context);
1761
+ }
1762
+ }
1763
+ return this.node.structure.parent?.nodeState.debouncer();
1764
+ }
1691
1765
  isNonInteractive = computed(() => this.hidden() || this.disabled() || this.readonly(), ...(ngDevMode ? [{
1692
1766
  debugName: "isNonInteractive"
1693
1767
  }] : []));
@@ -1766,6 +1840,7 @@ function normalizeFormArgs(args) {
1766
1840
  }
1767
1841
  return [model, schema, options];
1768
1842
  }
1843
+
1769
1844
  function form(...args) {
1770
1845
  const [model, schema, options] = normalizeFormArgs(args);
1771
1846
  const injector = options?.injector ?? inject(Injector);
@@ -1778,7 +1853,7 @@ function form(...args) {
1778
1853
  }
1779
1854
  function applyEach(path, schema) {
1780
1855
  assertPathIsCurrent(path);
1781
- const elementPath = FieldPathNode.unwrapFieldPath(path).element.fieldPathProxy;
1856
+ const elementPath = FieldPathNode.unwrapFieldPath(path).getChild(DYNAMIC).fieldPathProxy;
1782
1857
  apply(elementPath, schema);
1783
1858
  }
1784
1859
  function apply(path, schema) {
@@ -2018,13 +2093,13 @@ function validateStandardSchema(path, schema) {
2018
2093
  });
2019
2094
  validateTree(path, ({
2020
2095
  state,
2021
- fieldOf
2096
+ fieldTreeOf
2022
2097
  }) => {
2023
2098
  const result = state.metadata(VALIDATOR_MEMO)();
2024
2099
  if (_isPromise(result)) {
2025
2100
  return [];
2026
2101
  }
2027
- return result.issues?.map(issue => standardIssueToFormTreeError(fieldOf(path), issue)) ?? [];
2102
+ return result.issues?.map(issue => standardIssueToFormTreeError(fieldTreeOf(path), issue)) ?? [];
2028
2103
  });
2029
2104
  validateAsync(path, {
2030
2105
  params: ({
@@ -2042,9 +2117,9 @@ function validateStandardSchema(path, schema) {
2042
2117
  });
2043
2118
  },
2044
2119
  onSuccess: (issues, {
2045
- fieldOf
2120
+ fieldTreeOf
2046
2121
  }) => {
2047
- return issues.map(issue => standardIssueToFormTreeError(fieldOf(path), issue));
2122
+ return issues.map(issue => standardIssueToFormTreeError(fieldTreeOf(path), issue));
2048
2123
  },
2049
2124
  onError: () => {}
2050
2125
  });
@@ -2058,5 +2133,5 @@ function standardIssueToFormTreeError(field, issue) {
2058
2133
  return addDefaultField(standardSchemaError(issue), target);
2059
2134
  }
2060
2135
 
2061
- export { AggregateMetadataKey, CustomValidationError, EmailValidationError, FIELD, Field, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MaxLengthValidationError, MaxValidationError, MetadataKey, MinLengthValidationError, MinValidationError, NgValidationError, PATTERN, PatternValidationError, REQUIRED, RequiredValidationError, StandardSchemaValidationError, aggregateMetadata, andMetadataKey, apply, applyEach, applyWhen, applyWhenValue, createMetadataKey, customError, disabled, email, emailError, form, hidden, listMetadataKey, max, maxError, maxLength, maxLengthError, maxMetadataKey, metadata, min, minError, minLength, minLengthError, minMetadataKey, orMetadataKey, pattern, patternError, readonly, reducedMetadataKey, required, requiredError, schema, standardSchemaError, submit, validate, validateAsync, validateHttp, validateStandardSchema, validateTree };
2136
+ export { AggregateMetadataKey, CustomValidationError, EmailValidationError, FIELD, Field, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MaxLengthValidationError, MaxValidationError, MetadataKey, MinLengthValidationError, MinValidationError, NgValidationError, PATTERN, PatternValidationError, REQUIRED, RequiredValidationError, StandardSchemaValidationError, aggregateMetadata, andMetadataKey, apply, applyEach, applyWhen, applyWhenValue, createMetadataKey, customError, debounce, disabled, email, emailError, form, hidden, listMetadataKey, max, maxError, maxLength, maxLengthError, maxMetadataKey, metadata, min, minError, minLength, minLengthError, minMetadataKey, orMetadataKey, pattern, patternError, readonly, reducedMetadataKey, required, requiredError, schema, standardSchemaError, submit, validate, validateAsync, validateHttp, validateStandardSchema, validateTree };
2062
2137
  //# sourceMappingURL=signals.mjs.map