@ngx-formly/core 11.0.0 → 11.1.0-rc.0

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
  import * as i0 from "@angular/core";
2
- import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentRef, ContentChildren, DOCUMENT, DestroyRef, Directive, EventEmitter, Inject, Injectable, InjectionToken, Injector, Input, NgModule, Optional, Output, PendingTasks, Pipe, TemplateRef, Type, VERSION, ViewChild, ViewChildren, ViewContainerRef, inject, ɵNoopNgZone } from "@angular/core";
2
+ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ComponentRef, ContentChildren, DOCUMENT, DestroyRef, Directive, EventEmitter, Inject, Injectable, InjectionToken, Injector, Input, NgModule, Optional, Output, PendingTasks, Pipe, TemplateRef, Type, VERSION, ViewChild, ViewChildren, ViewContainerRef, effect, inject, isWritableSignal, untracked, ɵNoopNgZone } from "@angular/core";
3
3
  import { Observable, Subject, Subscription, combineLatest, isObservable, merge, of } from "rxjs";
4
4
  import * as i2$1 from "@angular/forms";
5
5
  import { AbstractControl, FormControl, NgControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup, Validators } from "@angular/forms";
@@ -116,13 +116,16 @@ function isObject(x) {
116
116
  function isPromise(obj) {
117
117
  return !!obj && typeof obj.then === "function";
118
118
  }
119
- function clone(value) {
120
- if (!isObject(value) || isObservable(value) || isPromise(value) || value instanceof TemplateRef || value.changingThisBreaksApplicationSecurity || [
119
+ function isKeptByReference(value) {
120
+ return !isObject(value) || isObservable(value) || isPromise(value) || value instanceof TemplateRef || value.changingThisBreaksApplicationSecurity || [
121
121
  "RegExp",
122
122
  "FileList",
123
123
  "File",
124
124
  "Blob"
125
- ].indexOf(value.constructor?.name) !== -1) return value;
125
+ ].indexOf(value.constructor?.name) !== -1;
126
+ }
127
+ function clone(value) {
128
+ if (isKeptByReference(value)) return value;
126
129
  if (value instanceof Set) return new Set(value);
127
130
  if (value instanceof Map) return new Map(value);
128
131
  if (value instanceof Uint8Array) return new Uint8Array(value);
@@ -142,6 +145,15 @@ function clone(value) {
142
145
  return newVal;
143
146
  }, c);
144
147
  }
148
+ function isEqualValue(a, b) {
149
+ if (Object.is(a, b)) return true;
150
+ if (isKeptByReference(a) || isKeptByReference(b) || Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) return false;
151
+ if (a instanceof Date) return Object.is(a.getTime(), b.getTime());
152
+ if (a instanceof Map) return a.size === b.size && [...a].every(([k, v]) => b.has(k) && isEqualValue(v, b.get(k)));
153
+ if (a instanceof Set) return a.size === b.size && [...a].every((v) => b.has(v));
154
+ const keys = Object.keys(a);
155
+ return keys.length === Object.keys(b).length && keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && isEqualValue(a[k], b[k]));
156
+ }
145
157
  function defineHiddenProp(field, prop, defaultValue) {
146
158
  Object.defineProperty(field, prop, {
147
159
  enumerable: false,
@@ -2378,6 +2390,7 @@ var FormlyFormBuilder = class FormlyFormBuilder {
2378
2390
  subscription?.unsubscribe();
2379
2391
  }
2380
2392
  if (firstBuild && lastEmittedStatus !== field.form.status) field.form.statusChanges.emit(field.form.status);
2393
+ if (!controlsForCheck) options._afterBuild?.forEach((afterBuild) => afterBuild());
2381
2394
  }
2382
2395
  _build(field) {
2383
2396
  if (!field) return;
@@ -2477,6 +2490,12 @@ var FormlyForm = class FormlyForm {
2477
2490
  return this.field.form;
2478
2491
  }
2479
2492
  set model(model) {
2493
+ this.modelSignalEffect?.destroy();
2494
+ this.modelSignal = void 0;
2495
+ if (isWritableSignal(model)) {
2496
+ this.bindModelSignal(model);
2497
+ return;
2498
+ }
2480
2499
  if (this.config.extras.immutable && this._modelChangeValue === model) return;
2481
2500
  this.setField({ model });
2482
2501
  }
@@ -2490,6 +2509,7 @@ var FormlyForm = class FormlyForm {
2490
2509
  return this.field.fieldGroup;
2491
2510
  }
2492
2511
  set options(options) {
2512
+ this.field.options?._afterBuild?.delete(this.afterBuild);
2493
2513
  this.setField({ options });
2494
2514
  }
2495
2515
  get options() {
@@ -2508,19 +2528,19 @@ var FormlyForm = class FormlyForm {
2508
2528
  this.field = { type: "formly-group" };
2509
2529
  this.destroyRef = inject(DestroyRef);
2510
2530
  this.pendingTasks = inject(PendingTasks);
2531
+ this.injector = inject(Injector);
2511
2532
  this._modelChangeValue = {};
2512
2533
  this.valueChangesUnsubscribe = () => {};
2534
+ this.afterBuild = () => {
2535
+ if (this.modelSignal) this.syncModelSignal(this.modelSignal);
2536
+ };
2513
2537
  }
2514
2538
  ngDoCheck() {
2515
2539
  if (this.config.extras.checkExpressionOn === "changeDetectionCheck") this.checkExpressionChange();
2516
2540
  }
2517
2541
  ngOnChanges(changes) {
2518
2542
  if (changes.fields && this.form) clearControl(this.form);
2519
- if (changes.fields || changes.form || changes.model && this._modelChangeValue !== changes.model.currentValue) {
2520
- this.valueChangesUnsubscribe();
2521
- this.builder.build(this.field);
2522
- this.valueChangesUnsubscribe = this.valueChanges();
2523
- }
2543
+ if (changes.fields || changes.form || changes.model && this._modelChangeValue !== changes.model.currentValue) this.build();
2524
2544
  if (changes.fields || changes.form) this.pendingTasks.run(async () => {
2525
2545
  await Promise.resolve();
2526
2546
  if (!this.destroyRef.destroyed) this.fieldChange.emit({
@@ -2533,6 +2553,7 @@ var FormlyForm = class FormlyForm {
2533
2553
  ngOnDestroy() {
2534
2554
  this.valueChangesUnsubscribe();
2535
2555
  this.config.clearRefs();
2556
+ this.field.options?._afterBuild?.delete(this.afterBuild);
2536
2557
  }
2537
2558
  checkExpressionChange() {
2538
2559
  this.field.options.checkExpressions?.(this.field);
@@ -2550,9 +2571,13 @@ var FormlyForm = class FormlyForm {
2550
2571
  });
2551
2572
  }
2552
2573
  const fieldChangesDetection = [observeDeep(this.field.options, ["formState"], () => this.field.options.detectChanges(this.field))];
2553
- const valueChanges = this.field.options.fieldChanges.pipe(filter(({ field, type }) => hasKey(field) && type === "valueChanges"), switchMap(() => isNoopNgZone(this.ngZone) ? of(null) : this.ngZone.onStable.asObservable().pipe(take(1)))).subscribe(() => this.ngZone.runGuarded(() => {
2574
+ const valueChanges = this.field.options.fieldChanges.pipe(filter(({ field, type }) => hasKey(field) && type === "valueChanges"), tap(() => {
2575
+ if (this.modelSignal) this.syncModelSignal(this.modelSignal);
2576
+ }), switchMap(() => isNoopNgZone(this.ngZone) ? of(null) : this.ngZone.onStable.asObservable().pipe(take(1)))).subscribe(() => this.ngZone.runGuarded(() => {
2554
2577
  this.checkExpressionChange();
2555
- this.modelChange.emit(this._modelChangeValue = clone(this.model));
2578
+ this._modelChangeValue = clone(this.model);
2579
+ if (this.modelSignal) this.syncModelSignal(this.modelSignal, this._modelChangeValue);
2580
+ this.modelChange.emit(this._modelChangeValue);
2556
2581
  }));
2557
2582
  return () => {
2558
2583
  fieldChangesDetection.forEach((fnc) => fnc());
@@ -2567,6 +2592,42 @@ var FormlyForm = class FormlyForm {
2567
2592
  };
2568
2593
  else Object.keys(field).forEach((p) => this.field[p] = field[p]);
2569
2594
  }
2595
+ build() {
2596
+ this.valueChangesUnsubscribe();
2597
+ if (this.modelSignal) {
2598
+ const options = this.field.options ??= {};
2599
+ if (!options._afterBuild) defineHiddenProp(options, "_afterBuild", /* @__PURE__ */ new Set());
2600
+ options._afterBuild.add(this.afterBuild);
2601
+ }
2602
+ this.builder.build(this.field);
2603
+ this.valueChangesUnsubscribe = this.valueChanges();
2604
+ }
2605
+ bindModelSignal(model) {
2606
+ this.modelSignal = model;
2607
+ this.modelSignalValue = model();
2608
+ this.useSignalValue(this.modelSignalValue);
2609
+ this.modelSignalEffect = effect(() => {
2610
+ const value = model();
2611
+ if (value === this.modelSignalValue) return;
2612
+ this.modelSignalValue = value;
2613
+ untracked(() => {
2614
+ this.useSignalValue(value);
2615
+ this.build();
2616
+ });
2617
+ }, {
2618
+ ...ngDevMode ? { debugName: "modelSignalEffect" } : /* istanbul ignore next */ {},
2619
+ injector: this.injector
2620
+ });
2621
+ }
2622
+ syncModelSignal(modelSignal, copy) {
2623
+ const current = untracked(modelSignal);
2624
+ if (current !== this.modelSignalValue || isEqualValue(this.model, current ?? {})) return;
2625
+ modelSignal.set(copy ?? clone(this.model));
2626
+ this.modelSignalValue = untracked(modelSignal);
2627
+ }
2628
+ useSignalValue(model) {
2629
+ this.setField({ model: this.config.extras.immutable ? model : clone(model) });
2630
+ }
2570
2631
  static {
2571
2632
  this.ɵfac = i0.ɵɵngDeclareFactory({
2572
2633
  minVersion: "12.0.0",