@angular/forms 21.0.0-next.7 → 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 +161 -134
- package/fesm2022/forms.mjs.map +1 -1
- package/fesm2022/signals.mjs +158 -29
- package/fesm2022/signals.mjs.map +1 -1
- package/package.json +4 -4
- package/types/forms.d.ts +34 -3
- package/types/signals.d.ts +94 -60
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
|
/**
|
|
@@ -1425,23 +1426,105 @@ function validateHttp(path, opts) {
|
|
|
1425
1426
|
}
|
|
1426
1427
|
|
|
1427
1428
|
/**
|
|
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.
|
|
1429
1434
|
*/
|
|
1430
|
-
|
|
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
|
+
|
|
1512
|
+
/**
|
|
1513
|
+
* Lightweight DI token provided by the {@link Field} directive.
|
|
1514
|
+
*/
|
|
1515
|
+
const FIELD = new InjectionToken(typeof ngDevMode !== undefined && ngDevMode ? 'FIELD' : '');
|
|
1431
1516
|
/**
|
|
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.
|
|
@@ -1449,31 +1532,74 @@ const CONTROL = new InjectionToken(typeof ngDevMode !== undefined && ngDevMode ?
|
|
|
1449
1532
|
* @category control
|
|
1450
1533
|
* @experimental 21.0.0
|
|
1451
1534
|
*/
|
|
1452
|
-
class
|
|
1535
|
+
class Field {
|
|
1453
1536
|
injector = inject(Injector);
|
|
1454
|
-
field = input.required(...(ngDevMode ? [{ debugName: "field"
|
|
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
|
-
// (as opposed to just passing the field through to its `
|
|
1575
|
+
// (as opposed to just passing the field through to its `field` input).
|
|
1462
1576
|
effect((onCleanup) => {
|
|
1463
1577
|
const fieldNode = this.state();
|
|
1464
|
-
fieldNode.nodeState.
|
|
1578
|
+
fieldNode.nodeState.fieldBindings.update((controls) => [
|
|
1579
|
+
...controls,
|
|
1580
|
+
this,
|
|
1581
|
+
]);
|
|
1465
1582
|
onCleanup(() => {
|
|
1466
|
-
fieldNode.nodeState.
|
|
1583
|
+
fieldNode.nodeState.fieldBindings.update((controls) => controls.filter((c) => c !== this));
|
|
1467
1584
|
});
|
|
1468
1585
|
}, { injector: this.injector });
|
|
1469
1586
|
}
|
|
1470
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0-next.
|
|
1471
|
-
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 });
|
|
1472
1592
|
}
|
|
1473
|
-
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: [{
|
|
1474
1594
|
type: Directive,
|
|
1475
|
-
args: [{
|
|
1476
|
-
|
|
1595
|
+
args: [{
|
|
1596
|
+
selector: '[field]',
|
|
1597
|
+
providers: [
|
|
1598
|
+
{ provide: FIELD, useExisting: Field },
|
|
1599
|
+
{ provide: NgControl, useFactory: () => inject(Field).ɵgetOrCreateNgControl() },
|
|
1600
|
+
],
|
|
1601
|
+
}]
|
|
1602
|
+
}], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }] } });
|
|
1477
1603
|
|
|
1478
1604
|
/**
|
|
1479
1605
|
* `FieldContext` implementation, backed by a `FieldNode`.
|
|
@@ -2083,8 +2209,8 @@ class FieldNode {
|
|
|
2083
2209
|
get readonly() {
|
|
2084
2210
|
return this.nodeState.readonly;
|
|
2085
2211
|
}
|
|
2086
|
-
get
|
|
2087
|
-
return this.nodeState.
|
|
2212
|
+
get fieldBindings() {
|
|
2213
|
+
return this.nodeState.fieldBindings;
|
|
2088
2214
|
}
|
|
2089
2215
|
get submitting() {
|
|
2090
2216
|
return this.submitState.submitting;
|
|
@@ -2092,23 +2218,26 @@ class FieldNode {
|
|
|
2092
2218
|
get name() {
|
|
2093
2219
|
return this.nodeState.name;
|
|
2094
2220
|
}
|
|
2221
|
+
propertyOrUndefined(prop) {
|
|
2222
|
+
return this.hasProperty(prop) ? this.property(prop) : undefined;
|
|
2223
|
+
}
|
|
2095
2224
|
get max() {
|
|
2096
|
-
return this.
|
|
2225
|
+
return this.propertyOrUndefined(MAX);
|
|
2097
2226
|
}
|
|
2098
2227
|
get maxLength() {
|
|
2099
|
-
return this.
|
|
2228
|
+
return this.propertyOrUndefined(MAX_LENGTH);
|
|
2100
2229
|
}
|
|
2101
2230
|
get min() {
|
|
2102
|
-
return this.
|
|
2231
|
+
return this.propertyOrUndefined(MIN);
|
|
2103
2232
|
}
|
|
2104
2233
|
get minLength() {
|
|
2105
|
-
return this.
|
|
2234
|
+
return this.propertyOrUndefined(MIN_LENGTH);
|
|
2106
2235
|
}
|
|
2107
2236
|
get pattern() {
|
|
2108
|
-
return this.
|
|
2237
|
+
return this.propertyOrUndefined(PATTERN);
|
|
2109
2238
|
}
|
|
2110
2239
|
get required() {
|
|
2111
|
-
return this.
|
|
2240
|
+
return this.propertyOrUndefined(REQUIRED);
|
|
2112
2241
|
}
|
|
2113
2242
|
property(prop) {
|
|
2114
2243
|
return this.propertyState.get(prop);
|
|
@@ -2203,8 +2332,8 @@ class FieldNodeState {
|
|
|
2203
2332
|
markAsUntouched() {
|
|
2204
2333
|
this.selfTouched.set(false);
|
|
2205
2334
|
}
|
|
2206
|
-
/** The
|
|
2207
|
-
|
|
2335
|
+
/** The {@link Field} directives that bind this field to a UI control. */
|
|
2336
|
+
fieldBindings = signal([], ...(ngDevMode ? [{ debugName: "fieldBindings" }] : []));
|
|
2208
2337
|
constructor(node) {
|
|
2209
2338
|
this.node = node;
|
|
2210
2339
|
}
|
|
@@ -3190,5 +3319,5 @@ function standardIssueToFormTreeError(field, issue) {
|
|
|
3190
3319
|
return addDefaultField(standardSchemaError(issue), target);
|
|
3191
3320
|
}
|
|
3192
3321
|
|
|
3193
|
-
export { AggregateProperty,
|
|
3322
|
+
export { AggregateProperty, CustomValidationError, EmailValidationError, FIELD, Field, MAX, MAX_LENGTH, MIN, MIN_LENGTH, MaxLengthValidationError, MaxValidationError, MinLengthValidationError, MinValidationError, NgValidationError, PATTERN, PatternValidationError, Property, REQUIRED, RequiredValidationError, StandardSchemaValidationError, aggregateProperty, andProperty, apply, applyEach, applyWhen, applyWhenValue, createProperty, customError, disabled, email, emailError, form, hidden, listProperty, max, maxError, maxLength, maxLengthError, maxProperty, min, minError, minLength, minLengthError, minProperty, orProperty, pattern, patternError, property, readonly, reducedProperty, required, requiredError, schema, standardSchemaError, submit, validate, validateAsync, validateHttp, validateStandardSchema, validateTree };
|
|
3194
3323
|
//# sourceMappingURL=signals.mjs.map
|