@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.
- package/fesm2022/{_structure-chunk.mjs → _validation_errors-chunk.mjs} +138 -43
- package/fesm2022/_validation_errors-chunk.mjs.map +1 -0
- package/fesm2022/forms.mjs +170 -189
- package/fesm2022/forms.mjs.map +1 -1
- package/fesm2022/signals-compat.mjs +386 -47
- package/fesm2022/signals-compat.mjs.map +1 -1
- package/fesm2022/signals.mjs +683 -238
- package/fesm2022/signals.mjs.map +1 -1
- package/package.json +4 -4
- package/resources/code-examples.db +0 -0
- package/types/_structure-chunk.d.ts +642 -1406
- package/types/forms.d.ts +1 -1
- package/types/signals-compat.d.ts +131 -8
- package/types/signals.d.ts +107 -48
- package/fesm2022/_structure-chunk.mjs.map +0 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v21.2.0-next.
|
|
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
|
-
import { FieldNode, getInjectorFromOptions, FieldNodeState, FieldNodeStructure, calculateValidationSelfStatus, BasicFieldAdapter, form, normalizeFormArgs } from './
|
|
8
|
-
|
|
9
|
-
import {
|
|
7
|
+
import { FieldNode, getInjectorFromOptions, FieldNodeState, FieldNodeStructure, calculateValidationSelfStatus, extractNestedReactiveErrors, BasicFieldAdapter, form, normalizeFormArgs, signalErrorsToValidationErrors } from './_validation_errors-chunk.mjs';
|
|
8
|
+
export { CompatValidationError } from './_validation_errors-chunk.mjs';
|
|
9
|
+
import { linkedSignal, untracked, runInInjectionContext, computed, signal, ɵRuntimeError as _RuntimeError, EventEmitter, inject, Injector, effect } from '@angular/core';
|
|
10
|
+
import { AbstractControl, ValueChangeEvent, StatusChangeEvent, TouchedChangeEvent, PristineChangeEvent, FormResetEvent } from '@angular/forms';
|
|
10
11
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
11
12
|
import { ReplaySubject } from 'rxjs';
|
|
12
13
|
import { map, takeUntil } from 'rxjs/operators';
|
|
@@ -152,48 +153,6 @@ class CompatStructure extends FieldNodeStructure {
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
|
|
155
|
-
class CompatValidationError {
|
|
156
|
-
kind = 'compat';
|
|
157
|
-
control;
|
|
158
|
-
fieldTree;
|
|
159
|
-
context;
|
|
160
|
-
message;
|
|
161
|
-
constructor({
|
|
162
|
-
context,
|
|
163
|
-
kind,
|
|
164
|
-
control
|
|
165
|
-
}) {
|
|
166
|
-
this.context = context;
|
|
167
|
-
this.kind = kind;
|
|
168
|
-
this.control = control;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function reactiveErrorsToSignalErrors(errors, control) {
|
|
173
|
-
if (errors === null) {
|
|
174
|
-
return [];
|
|
175
|
-
}
|
|
176
|
-
return Object.entries(errors).map(([kind, context]) => {
|
|
177
|
-
return new CompatValidationError({
|
|
178
|
-
context,
|
|
179
|
-
kind,
|
|
180
|
-
control
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
function extractNestedReactiveErrors(control) {
|
|
185
|
-
const errors = [];
|
|
186
|
-
if (control.errors) {
|
|
187
|
-
errors.push(...reactiveErrorsToSignalErrors(control.errors, control));
|
|
188
|
-
}
|
|
189
|
-
if (control instanceof FormGroup || control instanceof FormArray) {
|
|
190
|
-
for (const c of Object.values(control.controls)) {
|
|
191
|
-
errors.push(...extractNestedReactiveErrors(c));
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return errors;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
156
|
const EMPTY_ARRAY_SIGNAL = computed(() => [], ...(ngDevMode ? [{
|
|
198
157
|
debugName: "EMPTY_ARRAY_SIGNAL"
|
|
199
158
|
}] : []));
|
|
@@ -318,5 +277,385 @@ const NG_STATUS_CLASSES = {
|
|
|
318
277
|
}) => state().pending()
|
|
319
278
|
};
|
|
320
279
|
|
|
321
|
-
|
|
280
|
+
class SignalFormControl extends AbstractControl {
|
|
281
|
+
fieldTree;
|
|
282
|
+
sourceValue;
|
|
283
|
+
fieldState;
|
|
284
|
+
initialValue;
|
|
285
|
+
pendingParentNotifications = 0;
|
|
286
|
+
onChangeCallbacks = [];
|
|
287
|
+
onDisabledChangeCallbacks = [];
|
|
288
|
+
valueChanges = new EventEmitter();
|
|
289
|
+
statusChanges = new EventEmitter();
|
|
290
|
+
constructor(value, schemaOrOptions, options) {
|
|
291
|
+
super(null, null);
|
|
292
|
+
const [model, schema, opts] = normalizeFormArgs([signal(value), schemaOrOptions, options]);
|
|
293
|
+
this.sourceValue = model;
|
|
294
|
+
this.initialValue = value;
|
|
295
|
+
const injector = opts?.injector ?? inject(Injector);
|
|
296
|
+
const rawTree = schema ? compatForm(this.sourceValue, schema, {
|
|
297
|
+
injector
|
|
298
|
+
}) : compatForm(this.sourceValue, {
|
|
299
|
+
injector
|
|
300
|
+
});
|
|
301
|
+
this.fieldTree = wrapFieldTreeForSyncUpdates(rawTree, () => this.parent?.updateValueAndValidity({
|
|
302
|
+
sourceControl: this
|
|
303
|
+
}));
|
|
304
|
+
this.fieldState = this.fieldTree();
|
|
305
|
+
this.defineCompatProperties();
|
|
306
|
+
effect(() => {
|
|
307
|
+
const value = this.sourceValue();
|
|
308
|
+
untracked(() => {
|
|
309
|
+
this.notifyParentUnlessPending();
|
|
310
|
+
this.valueChanges.emit(value);
|
|
311
|
+
this.emitControlEvent(new ValueChangeEvent(value, this));
|
|
312
|
+
});
|
|
313
|
+
}, {
|
|
314
|
+
injector
|
|
315
|
+
});
|
|
316
|
+
effect(() => {
|
|
317
|
+
const status = this.status;
|
|
318
|
+
untracked(() => {
|
|
319
|
+
this.statusChanges.emit(status);
|
|
320
|
+
});
|
|
321
|
+
this.emitControlEvent(new StatusChangeEvent(status, this));
|
|
322
|
+
}, {
|
|
323
|
+
injector
|
|
324
|
+
});
|
|
325
|
+
effect(() => {
|
|
326
|
+
const isDisabled = this.disabled;
|
|
327
|
+
untracked(() => {
|
|
328
|
+
for (const fn of this.onDisabledChangeCallbacks) {
|
|
329
|
+
fn(isDisabled);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}, {
|
|
333
|
+
injector
|
|
334
|
+
});
|
|
335
|
+
effect(() => {
|
|
336
|
+
const isTouched = this.fieldState.touched();
|
|
337
|
+
this.emitControlEvent(new TouchedChangeEvent(isTouched, this));
|
|
338
|
+
const parent = this.parent;
|
|
339
|
+
if (!parent) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (!isTouched) {
|
|
343
|
+
parent.markAsUntouched();
|
|
344
|
+
} else {
|
|
345
|
+
parent.markAsTouched();
|
|
346
|
+
}
|
|
347
|
+
}, {
|
|
348
|
+
injector
|
|
349
|
+
});
|
|
350
|
+
effect(() => {
|
|
351
|
+
const isDirty = this.fieldState.dirty();
|
|
352
|
+
this.emitControlEvent(new PristineChangeEvent(!isDirty, this));
|
|
353
|
+
const parent = this.parent;
|
|
354
|
+
if (!parent) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (isDirty) {
|
|
358
|
+
parent.markAsDirty();
|
|
359
|
+
} else {
|
|
360
|
+
parent.markAsPristine();
|
|
361
|
+
}
|
|
362
|
+
}, {
|
|
363
|
+
injector
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
defineCompatProperties() {
|
|
367
|
+
const valueProp = getClosureSafeProperty({
|
|
368
|
+
value: getClosureSafeProperty
|
|
369
|
+
});
|
|
370
|
+
Object.defineProperty(this, valueProp, {
|
|
371
|
+
get: () => this.sourceValue()
|
|
372
|
+
});
|
|
373
|
+
const errorsProp = getClosureSafeProperty({
|
|
374
|
+
errors: getClosureSafeProperty
|
|
375
|
+
});
|
|
376
|
+
Object.defineProperty(this, errorsProp, {
|
|
377
|
+
get: () => signalErrorsToValidationErrors(this.fieldState.errors())
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
emitControlEvent(event) {
|
|
381
|
+
untracked(() => {
|
|
382
|
+
this._events.next(event);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
setValue(value, options) {
|
|
386
|
+
this.updateValue(value, options);
|
|
387
|
+
}
|
|
388
|
+
patchValue(value, options) {
|
|
389
|
+
this.updateValue(value, options);
|
|
390
|
+
}
|
|
391
|
+
updateValue(value, options) {
|
|
392
|
+
const parent = this.scheduleParentUpdate(options);
|
|
393
|
+
this.sourceValue.set(value);
|
|
394
|
+
if (parent) {
|
|
395
|
+
this.updateParentValueAndValidity(parent, options?.emitEvent);
|
|
396
|
+
}
|
|
397
|
+
if (options?.emitModelToViewChange !== false) {
|
|
398
|
+
for (const fn of this.onChangeCallbacks) {
|
|
399
|
+
fn(value, true);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
registerOnChange(fn) {
|
|
404
|
+
this.onChangeCallbacks.push(fn);
|
|
405
|
+
}
|
|
406
|
+
_unregisterOnChange(fn) {
|
|
407
|
+
removeListItem(this.onChangeCallbacks, fn);
|
|
408
|
+
}
|
|
409
|
+
registerOnDisabledChange(fn) {
|
|
410
|
+
this.onDisabledChangeCallbacks.push(fn);
|
|
411
|
+
}
|
|
412
|
+
_unregisterOnDisabledChange(fn) {
|
|
413
|
+
removeListItem(this.onDisabledChangeCallbacks, fn);
|
|
414
|
+
}
|
|
415
|
+
getRawValue() {
|
|
416
|
+
return this.value;
|
|
417
|
+
}
|
|
418
|
+
reset(value, options) {
|
|
419
|
+
if (isFormControlState(value)) {
|
|
420
|
+
throw unsupportedDisableEnableError();
|
|
421
|
+
}
|
|
422
|
+
const resetValue = value ?? this.initialValue;
|
|
423
|
+
this.fieldState.reset(resetValue);
|
|
424
|
+
if (value !== undefined) {
|
|
425
|
+
this.updateValue(value, options);
|
|
426
|
+
} else if (!options?.onlySelf) {
|
|
427
|
+
const parent = this.parent;
|
|
428
|
+
if (parent) {
|
|
429
|
+
this.updateParentValueAndValidity(parent, options?.emitEvent);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (options?.emitEvent !== false) {
|
|
433
|
+
this.emitControlEvent(new FormResetEvent(this));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
scheduleParentUpdate(options) {
|
|
437
|
+
const parent = options?.onlySelf ? null : this.parent;
|
|
438
|
+
if (options?.onlySelf || parent) {
|
|
439
|
+
this.pendingParentNotifications++;
|
|
440
|
+
}
|
|
441
|
+
return parent;
|
|
442
|
+
}
|
|
443
|
+
notifyParentUnlessPending() {
|
|
444
|
+
if (this.pendingParentNotifications > 0) {
|
|
445
|
+
this.pendingParentNotifications--;
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const parent = this.parent;
|
|
449
|
+
if (parent) {
|
|
450
|
+
this.updateParentValueAndValidity(parent);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
updateParentValueAndValidity(parent, emitEvent) {
|
|
454
|
+
parent.updateValueAndValidity({
|
|
455
|
+
emitEvent,
|
|
456
|
+
sourceControl: this
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
propagateToParent(opts, fn) {
|
|
460
|
+
const parent = this.parent;
|
|
461
|
+
if (parent && !opts?.onlySelf) {
|
|
462
|
+
fn(parent);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
get status() {
|
|
466
|
+
if (this.fieldState.disabled()) {
|
|
467
|
+
return 'DISABLED';
|
|
468
|
+
}
|
|
469
|
+
if (this.fieldState.valid()) {
|
|
470
|
+
return 'VALID';
|
|
471
|
+
}
|
|
472
|
+
if (this.fieldState.invalid()) {
|
|
473
|
+
return 'INVALID';
|
|
474
|
+
}
|
|
475
|
+
return 'PENDING';
|
|
476
|
+
}
|
|
477
|
+
get valid() {
|
|
478
|
+
return this.fieldState.valid();
|
|
479
|
+
}
|
|
480
|
+
get invalid() {
|
|
481
|
+
return this.fieldState.invalid();
|
|
482
|
+
}
|
|
483
|
+
get pending() {
|
|
484
|
+
return this.fieldState.pending();
|
|
485
|
+
}
|
|
486
|
+
get disabled() {
|
|
487
|
+
return this.fieldState.disabled();
|
|
488
|
+
}
|
|
489
|
+
get enabled() {
|
|
490
|
+
return !this.disabled;
|
|
491
|
+
}
|
|
492
|
+
get dirty() {
|
|
493
|
+
return this.fieldState.dirty();
|
|
494
|
+
}
|
|
495
|
+
set dirty(_) {
|
|
496
|
+
throw unsupportedFeatureError(ngDevMode && 'Setting dirty directly is not supported. Instead use markAsDirty().');
|
|
497
|
+
}
|
|
498
|
+
get pristine() {
|
|
499
|
+
return !this.dirty;
|
|
500
|
+
}
|
|
501
|
+
set pristine(_) {
|
|
502
|
+
throw unsupportedFeatureError(ngDevMode && 'Setting pristine directly is not supported. Instead use reset().');
|
|
503
|
+
}
|
|
504
|
+
get touched() {
|
|
505
|
+
return this.fieldState.touched();
|
|
506
|
+
}
|
|
507
|
+
set touched(_) {
|
|
508
|
+
throw unsupportedFeatureError(ngDevMode && 'Setting touched directly is not supported. Instead use markAsTouched() or reset().');
|
|
509
|
+
}
|
|
510
|
+
get untouched() {
|
|
511
|
+
return !this.touched;
|
|
512
|
+
}
|
|
513
|
+
set untouched(_) {
|
|
514
|
+
throw unsupportedFeatureError(ngDevMode && 'Setting untouched directly is not supported. Instead use reset().');
|
|
515
|
+
}
|
|
516
|
+
markAsTouched(opts) {
|
|
517
|
+
this.fieldState.markAsTouched();
|
|
518
|
+
this.propagateToParent(opts, parent => parent.markAsTouched(opts));
|
|
519
|
+
}
|
|
520
|
+
markAsDirty(opts) {
|
|
521
|
+
this.fieldState.markAsDirty();
|
|
522
|
+
this.propagateToParent(opts, parent => parent.markAsDirty(opts));
|
|
523
|
+
}
|
|
524
|
+
markAsPristine(opts) {
|
|
525
|
+
this.fieldState.markAsPristine();
|
|
526
|
+
this.propagateToParent(opts, parent => parent.markAsPristine(opts));
|
|
527
|
+
}
|
|
528
|
+
markAsUntouched(opts) {
|
|
529
|
+
this.fieldState.markAsUntouched();
|
|
530
|
+
this.propagateToParent(opts, parent => parent.markAsUntouched(opts));
|
|
531
|
+
}
|
|
532
|
+
updateValueAndValidity(_opts) {}
|
|
533
|
+
_updateValue() {}
|
|
534
|
+
_forEachChild(_cb) {}
|
|
535
|
+
_anyControls(_condition) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
_allControlsDisabled() {
|
|
539
|
+
return this.disabled;
|
|
540
|
+
}
|
|
541
|
+
_syncPendingControls() {
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
disable(_opts) {
|
|
545
|
+
throw unsupportedDisableEnableError();
|
|
546
|
+
}
|
|
547
|
+
enable(_opts) {
|
|
548
|
+
throw unsupportedDisableEnableError();
|
|
549
|
+
}
|
|
550
|
+
setValidators(_validators) {
|
|
551
|
+
throw unsupportedValidatorsError();
|
|
552
|
+
}
|
|
553
|
+
setAsyncValidators(_validators) {
|
|
554
|
+
throw unsupportedValidatorsError();
|
|
555
|
+
}
|
|
556
|
+
addValidators(_validators) {
|
|
557
|
+
throw unsupportedValidatorsError();
|
|
558
|
+
}
|
|
559
|
+
addAsyncValidators(_validators) {
|
|
560
|
+
throw unsupportedValidatorsError();
|
|
561
|
+
}
|
|
562
|
+
removeValidators(_validators) {
|
|
563
|
+
throw unsupportedValidatorsError();
|
|
564
|
+
}
|
|
565
|
+
removeAsyncValidators(_validators) {
|
|
566
|
+
throw unsupportedValidatorsError();
|
|
567
|
+
}
|
|
568
|
+
clearValidators() {
|
|
569
|
+
throw unsupportedValidatorsError();
|
|
570
|
+
}
|
|
571
|
+
clearAsyncValidators() {
|
|
572
|
+
throw unsupportedValidatorsError();
|
|
573
|
+
}
|
|
574
|
+
setErrors(_errors, _opts) {
|
|
575
|
+
throw unsupportedFeatureError(ngDevMode && 'Imperatively setting errors is not supported in signal forms. Errors are derived from validation rules.');
|
|
576
|
+
}
|
|
577
|
+
markAsPending(_opts) {
|
|
578
|
+
throw unsupportedFeatureError(ngDevMode && 'Imperatively marking as pending is not supported in signal forms. Pending state is derived from async validation status.');
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
class CachingWeakMap {
|
|
582
|
+
map = new WeakMap();
|
|
583
|
+
getOrCreate(key, create) {
|
|
584
|
+
const cached = this.map.get(key);
|
|
585
|
+
if (cached) {
|
|
586
|
+
return cached;
|
|
587
|
+
}
|
|
588
|
+
const value = create();
|
|
589
|
+
this.map.set(key, value);
|
|
590
|
+
return value;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function wrapFieldTreeForSyncUpdates(tree, onUpdate) {
|
|
594
|
+
const treeCache = new CachingWeakMap();
|
|
595
|
+
const stateCache = new CachingWeakMap();
|
|
596
|
+
const wrapState = state => {
|
|
597
|
+
const {
|
|
598
|
+
value
|
|
599
|
+
} = state;
|
|
600
|
+
const wrappedValue = Object.assign((...a) => value(...a), {
|
|
601
|
+
set: v => {
|
|
602
|
+
value.set(v);
|
|
603
|
+
onUpdate();
|
|
604
|
+
},
|
|
605
|
+
update: fn => {
|
|
606
|
+
value.update(fn);
|
|
607
|
+
onUpdate();
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
return Object.create(state, {
|
|
611
|
+
value: {
|
|
612
|
+
get: () => wrappedValue
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
};
|
|
616
|
+
const wrapTree = t => {
|
|
617
|
+
return treeCache.getOrCreate(t, () => {
|
|
618
|
+
return new Proxy(t, {
|
|
619
|
+
get(target, prop, receiver) {
|
|
620
|
+
const val = Reflect.get(target, prop, receiver);
|
|
621
|
+
if (typeof val === 'function' && typeof prop === 'string') {
|
|
622
|
+
return wrapTree(val);
|
|
623
|
+
}
|
|
624
|
+
return val;
|
|
625
|
+
},
|
|
626
|
+
apply(target, _, args) {
|
|
627
|
+
const state = target(...args);
|
|
628
|
+
return stateCache.getOrCreate(state, () => wrapState(state));
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
};
|
|
633
|
+
return wrapTree(tree);
|
|
634
|
+
}
|
|
635
|
+
function isFormControlState(formState) {
|
|
636
|
+
return typeof formState === 'object' && formState !== null && Object.keys(formState).length === 2 && 'value' in formState && 'disabled' in formState;
|
|
637
|
+
}
|
|
638
|
+
function unsupportedFeatureError(message) {
|
|
639
|
+
return new _RuntimeError(1920, message ?? false);
|
|
640
|
+
}
|
|
641
|
+
function unsupportedDisableEnableError() {
|
|
642
|
+
return unsupportedFeatureError(ngDevMode && 'Imperatively changing enabled/disabled status in form control is not supported in signal forms. Instead use a "disabled" rule to derive the disabled status from a signal.');
|
|
643
|
+
}
|
|
644
|
+
function unsupportedValidatorsError() {
|
|
645
|
+
return unsupportedFeatureError(ngDevMode && 'Dynamically adding and removing validators is not supported in signal forms. Instead use the "applyWhen" rule to conditionally apply validators based on a signal.');
|
|
646
|
+
}
|
|
647
|
+
function removeListItem(list, el) {
|
|
648
|
+
const index = list.indexOf(el);
|
|
649
|
+
if (index > -1) list.splice(index, 1);
|
|
650
|
+
}
|
|
651
|
+
function getClosureSafeProperty(objWithPropertyToExtract) {
|
|
652
|
+
for (let key in objWithPropertyToExtract) {
|
|
653
|
+
if (objWithPropertyToExtract[key] === getClosureSafeProperty) {
|
|
654
|
+
return key;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
throw Error(typeof ngDevMode === 'undefined' || ngDevMode ? 'Could not find renamed property on target object.' : '');
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export { NG_STATUS_CLASSES, SignalFormControl, compatForm };
|
|
322
661
|
//# sourceMappingURL=signals-compat.mjs.map
|