@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.
- package/fesm2022/forms.mjs +133 -133
- package/fesm2022/forms.mjs.map +1 -1
- package/fesm2022/signals.mjs +142 -16
- package/fesm2022/signals.mjs.map +1 -1
- package/package.json +4 -4
- package/types/forms.d.ts +6 -2
- package/types/signals.d.ts +59 -25
package/fesm2022/signals.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v21.0.0-next.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
1474
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.0-next.
|
|
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.
|
|
1593
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.0-next.9", ngImport: i0, type: Field, decorators: [{
|
|
1477
1594
|
type: Directive,
|
|
1478
|
-
args: [{
|
|
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.
|
|
2225
|
+
return this.propertyOrUndefined(MAX);
|
|
2100
2226
|
}
|
|
2101
2227
|
get maxLength() {
|
|
2102
|
-
return this.
|
|
2228
|
+
return this.propertyOrUndefined(MAX_LENGTH);
|
|
2103
2229
|
}
|
|
2104
2230
|
get min() {
|
|
2105
|
-
return this.
|
|
2231
|
+
return this.propertyOrUndefined(MIN);
|
|
2106
2232
|
}
|
|
2107
2233
|
get minLength() {
|
|
2108
|
-
return this.
|
|
2234
|
+
return this.propertyOrUndefined(MIN_LENGTH);
|
|
2109
2235
|
}
|
|
2110
2236
|
get pattern() {
|
|
2111
|
-
return this.
|
|
2237
|
+
return this.propertyOrUndefined(PATTERN);
|
|
2112
2238
|
}
|
|
2113
2239
|
get required() {
|
|
2114
|
-
return this.
|
|
2240
|
+
return this.propertyOrUndefined(REQUIRED);
|
|
2115
2241
|
}
|
|
2116
2242
|
property(prop) {
|
|
2117
2243
|
return this.propertyState.get(prop);
|