@angular/forms 21.0.0-next.8 → 21.0.0-next.9

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-next.8
2
+ * @license Angular v21.0.0-next.9
3
3
  * (c) 2010-2025 Google LLC. https://angular.dev/
4
4
  * License: MIT
5
5
  */
@@ -7,6 +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
11
  import { SIGNAL } from '@angular/core/primitives/signals';
11
12
 
12
13
  /**
@@ -1424,6 +1425,90 @@ function validateHttp(path, opts) {
1424
1425
  });
1425
1426
  }
1426
1427
 
1428
+ /**
1429
+ * A fake version of `NgControl` provided by the `Field` directive. This allows interoperability
1430
+ * with a wider range of components designed to work with reactive forms, in particular ones that
1431
+ * inject the `NgControl`. The interop control does not implement *all* properties and methods of
1432
+ * the real `NgControl`, but does implement some of the most commonly used ones that have a clear
1433
+ * equivalent in signal forms.
1434
+ */
1435
+ class InteropNgControl {
1436
+ field;
1437
+ constructor(field) {
1438
+ this.field = field;
1439
+ }
1440
+ control = this;
1441
+ get value() {
1442
+ return this.field().value();
1443
+ }
1444
+ get valid() {
1445
+ return this.field().valid();
1446
+ }
1447
+ get invalid() {
1448
+ return this.field().invalid();
1449
+ }
1450
+ get pending() {
1451
+ return this.field().pending();
1452
+ }
1453
+ get disabled() {
1454
+ return this.field().disabled();
1455
+ }
1456
+ get enabled() {
1457
+ return !this.field().disabled();
1458
+ }
1459
+ get errors() {
1460
+ const errors = this.field().errors();
1461
+ if (errors.length === 0) {
1462
+ return null;
1463
+ }
1464
+ const errObj = {};
1465
+ for (const error of errors) {
1466
+ errObj[error.kind] = error;
1467
+ }
1468
+ return errObj;
1469
+ }
1470
+ get pristine() {
1471
+ return !this.field().dirty();
1472
+ }
1473
+ get dirty() {
1474
+ return this.field().dirty();
1475
+ }
1476
+ get touched() {
1477
+ return this.field().touched();
1478
+ }
1479
+ get untouched() {
1480
+ return !this.field().touched();
1481
+ }
1482
+ get status() {
1483
+ if (this.field().disabled()) {
1484
+ return 'DISABLED';
1485
+ }
1486
+ if (this.field().valid()) {
1487
+ return 'VALID';
1488
+ }
1489
+ if (this.field().invalid()) {
1490
+ return 'INVALID';
1491
+ }
1492
+ if (this.field().pending()) {
1493
+ return 'PENDING';
1494
+ }
1495
+ throw Error('AssertionError: unknown form control status');
1496
+ }
1497
+ valueAccessor = null;
1498
+ hasValidator(validator) {
1499
+ // This addresses a common case where users look for the presence of `Validators.required` to
1500
+ // determine whether or not to show a required "*" indicator in the UI.
1501
+ if (validator === Validators.required) {
1502
+ return this.field().property(REQUIRED)();
1503
+ }
1504
+ return false;
1505
+ }
1506
+ updateValueAndValidity() {
1507
+ // No-op since value and validity are always up to date in signal forms.
1508
+ // We offer this method so that reactive forms code attempting to call it doesn't error.
1509
+ }
1510
+ }
1511
+
1427
1512
  /**
1428
1513
  * Lightweight DI token provided by the {@link Field} directive.
1429
1514
  */
@@ -1432,16 +1517,14 @@ const FIELD = new InjectionToken(typeof ngDevMode !== undefined && ngDevMode ? '
1432
1517
  * Binds a form `FieldTree` to a UI control that edits it. A UI control can be one of several things:
1433
1518
  * 1. A native HTML input or textarea
1434
1519
  * 2. A signal forms custom control that implements `FormValueControl` or `FormCheckboxControl`
1435
- * 3. TODO: https://github.com/orgs/angular/projects/60/views/1?pane=issue&itemId=131712274. A
1436
- * component that provides a ControlValueAccessor. This should only be used to backwards
1520
+ * 3. A component that provides a `ControlValueAccessor`. This should only be used for backwards
1437
1521
  * compatibility with reactive forms. Prefer options (1) and (2).
1438
1522
  *
1439
1523
  * This directive has several responsibilities:
1440
1524
  * 1. Two-way binds the field's value with the UI control's value
1441
1525
  * 2. Binds additional forms related state on the field to the UI control (disabled, required, etc.)
1442
1526
  * 3. Relays relevant events on the control to the field (e.g. marks field touched on blur)
1443
- * 4. TODO: https://github.com/orgs/angular/projects/60/views/1?pane=issue&itemId=131712274.
1444
- * Provides a fake `NgControl` that implements a subset of the features available on the
1527
+ * 4. Provides a fake `NgControl` that implements a subset of the features available on the
1445
1528
  * reactive forms `NgControl`. This is provided to improve interoperability with controls
1446
1529
  * designed to work with reactive forms. It should not be used by controls written for signal
1447
1530
  * forms.
@@ -1454,8 +1537,39 @@ class Field {
1454
1537
  field = input.required(...(ngDevMode ? [{ debugName: "field" }] : []));
1455
1538
  state = computed(() => this.field()(), ...(ngDevMode ? [{ debugName: "state" }] : []));
1456
1539
  [_CONTROL] = undefined;
1540
+ /** Any `ControlValueAccessor` instances provided on the host element. */
1541
+ controlValueAccessors = inject(NG_VALUE_ACCESSOR, { optional: true, self: true });
1542
+ /** A lazily instantiated fake `NgControl`. */
1543
+ interopNgControl;
1544
+ /** A `ControlValueAccessor`, if configured, for the host component. */
1545
+ get controlValueAccessor() {
1546
+ return this.controlValueAccessors?.[0] ?? this.interopNgControl?.valueAccessor ?? undefined;
1547
+ }
1548
+ get ɵhasInteropControl() {
1549
+ return this.controlValueAccessor !== undefined;
1550
+ }
1551
+ /** Lazily instantiates a fake `NgControl` for this field. */
1552
+ ɵgetOrCreateNgControl() {
1553
+ return (this.interopNgControl ??= new InteropNgControl(this.state));
1554
+ }
1555
+ ɵinteropControlCreate() {
1556
+ const controlValueAccessor = this.controlValueAccessor;
1557
+ controlValueAccessor.registerOnChange((value) => {
1558
+ const state = this.state();
1559
+ state.value.set(value);
1560
+ state.markAsDirty();
1561
+ });
1562
+ controlValueAccessor.registerOnTouched(() => this.state().markAsTouched());
1563
+ }
1564
+ ɵinteropControlUpdate() {
1565
+ const controlValueAccessor = this.controlValueAccessor;
1566
+ // TODO: https://github.com/orgs/angular/projects/60/views/1?pane=issue&itemId=131711472
1567
+ // * check if values changed since last update before writing.
1568
+ controlValueAccessor.writeValue(this.state().value());
1569
+ controlValueAccessor.setDisabledState?.(this.state().disabled());
1570
+ }
1457
1571
  // TODO: https://github.com/orgs/angular/projects/60/views/1?pane=issue&itemId=131861631
1458
- register() {
1572
+ ɵregister() {
1459
1573
  // Register this control on the field it is currently bound to. We do this at the end of
1460
1574
  // initialization so that it only runs if we are actually syncing with this control
1461
1575
  // (as opposed to just passing the field through to its `field` input).
@@ -1470,12 +1584,21 @@ class Field {
1470
1584
  });
1471
1585
  }, { injector: this.injector });
1472
1586
  }
1473
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0-next.8", ngImport: i0, type: Field, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1474
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.0-next.8", type: Field, isStandalone: true, selector: "[field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null } }, providers: [{ provide: FIELD, useExisting: Field }], ngImport: i0 });
1587
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0-next.9", ngImport: i0, type: Field, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1588
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.0-next.9", type: Field, isStandalone: true, selector: "[field]", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
1589
+ { provide: FIELD, useExisting: Field },
1590
+ { provide: NgControl, useFactory: () => inject(Field).ɵgetOrCreateNgControl() },
1591
+ ], ngImport: i0 });
1475
1592
  }
1476
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0-next.8", ngImport: i0, type: Field, decorators: [{
1593
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0-next.9", ngImport: i0, type: Field, decorators: [{
1477
1594
  type: Directive,
1478
- args: [{ selector: '[field]', providers: [{ provide: FIELD, useExisting: Field }] }]
1595
+ args: [{
1596
+ selector: '[field]',
1597
+ providers: [
1598
+ { provide: FIELD, useExisting: Field },
1599
+ { provide: NgControl, useFactory: () => inject(Field).ɵgetOrCreateNgControl() },
1600
+ ],
1601
+ }]
1479
1602
  }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }] } });
1480
1603
 
1481
1604
  /**
@@ -2095,23 +2218,26 @@ class FieldNode {
2095
2218
  get name() {
2096
2219
  return this.nodeState.name;
2097
2220
  }
2221
+ propertyOrUndefined(prop) {
2222
+ return this.hasProperty(prop) ? this.property(prop) : undefined;
2223
+ }
2098
2224
  get max() {
2099
- return this.property(MAX);
2225
+ return this.propertyOrUndefined(MAX);
2100
2226
  }
2101
2227
  get maxLength() {
2102
- return this.property(MAX_LENGTH);
2228
+ return this.propertyOrUndefined(MAX_LENGTH);
2103
2229
  }
2104
2230
  get min() {
2105
- return this.property(MIN);
2231
+ return this.propertyOrUndefined(MIN);
2106
2232
  }
2107
2233
  get minLength() {
2108
- return this.property(MIN_LENGTH);
2234
+ return this.propertyOrUndefined(MIN_LENGTH);
2109
2235
  }
2110
2236
  get pattern() {
2111
- return this.property(PATTERN);
2237
+ return this.propertyOrUndefined(PATTERN);
2112
2238
  }
2113
2239
  get required() {
2114
- return this.property(REQUIRED);
2240
+ return this.propertyOrUndefined(REQUIRED);
2115
2241
  }
2116
2242
  property(prop) {
2117
2243
  return this.propertyState.get(prop);