@ngx-formly/core 6.3.12 → 7.0.0-alpha.1

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.
Files changed (129) hide show
  1. package/README.md +1 -0
  2. package/esm2022/json-schema/formly-json-schema.service.mjs +628 -0
  3. package/esm2022/lib/components/formly.field.mjs +292 -0
  4. package/esm2022/lib/components/formly.form.mjs +164 -0
  5. package/{esm2020 → esm2022}/lib/components/formly.template.mjs +8 -8
  6. package/esm2022/lib/core.config.mjs +36 -0
  7. package/esm2022/lib/core.mjs +20 -0
  8. package/esm2022/lib/core.module.mjs +74 -0
  9. package/esm2022/lib/extensions/core/core.mjs +148 -0
  10. package/esm2022/lib/extensions/field-expression/field-expression.mjs +250 -0
  11. package/esm2022/lib/extensions/field-expression/utils.mjs +17 -0
  12. package/esm2022/lib/extensions/field-form/field-form.mjs +141 -0
  13. package/esm2022/lib/extensions/field-form/utils.mjs +100 -0
  14. package/esm2022/lib/extensions/field-validation/field-validation.mjs +135 -0
  15. package/esm2022/lib/extensions/index.mjs +2 -0
  16. package/esm2022/lib/models/fieldconfig.cache.mjs +2 -0
  17. package/esm2022/lib/models/fieldconfig.mjs +2 -0
  18. package/esm2022/lib/services/formly.builder.mjs +100 -0
  19. package/esm2022/lib/services/formly.config.mjs +229 -0
  20. package/esm2022/lib/templates/field-array.type.mjs +84 -0
  21. package/esm2022/lib/templates/field-template.type.mjs +34 -0
  22. package/esm2022/lib/templates/field.type.mjs +57 -0
  23. package/{esm2020 → esm2022}/lib/templates/field.wrapper.mjs +4 -4
  24. package/esm2022/lib/templates/formly.attributes.mjs +187 -0
  25. package/{esm2020 → esm2022}/lib/templates/formly.group.mjs +8 -8
  26. package/esm2022/lib/templates/formly.validation-message.mjs +69 -0
  27. package/esm2022/lib/utils.mjs +312 -0
  28. package/esm2022/preset/preset-substitution.extension.mjs +30 -0
  29. package/esm2022/preset/preset.config.mjs +11 -0
  30. package/esm2022/preset/preset.module.mjs +18 -0
  31. package/esm2022/select/select-options.pipe.mjs +100 -0
  32. package/{esm2020 → esm2022}/select/select.module.mjs +5 -5
  33. package/esm2022/testing/component-factory.mjs +86 -0
  34. package/{fesm2020 → fesm2022}/ngx-formly-core-json-schema.mjs +16 -16
  35. package/fesm2022/ngx-formly-core-json-schema.mjs.map +1 -0
  36. package/fesm2022/ngx-formly-core-preset.mjs +62 -0
  37. package/fesm2022/ngx-formly-core-preset.mjs.map +1 -0
  38. package/{fesm2020 → fesm2022}/ngx-formly-core-select.mjs +7 -7
  39. package/{fesm2020 → fesm2022}/ngx-formly-core-select.mjs.map +1 -1
  40. package/{fesm2020 → fesm2022}/ngx-formly-core-testing.mjs +5 -4
  41. package/fesm2022/ngx-formly-core-testing.mjs.map +1 -0
  42. package/{fesm2020 → fesm2022}/ngx-formly-core.mjs +1747 -1702
  43. package/fesm2022/ngx-formly-core.mjs.map +1 -0
  44. package/lib/components/formly.field.d.ts +5 -1
  45. package/lib/components/formly.form.d.ts +8 -4
  46. package/lib/components/formly.template.d.ts +1 -1
  47. package/lib/core.config.d.ts +10 -0
  48. package/lib/core.d.ts +4 -3
  49. package/lib/core.module.d.ts +2 -5
  50. package/lib/extensions/field-expression/utils.d.ts +1 -1
  51. package/lib/extensions/index.d.ts +1 -0
  52. package/lib/models/config.d.ts +1 -1
  53. package/lib/models/fieldconfig.cache.d.ts +3 -3
  54. package/lib/models/fieldconfig.d.ts +8 -7
  55. package/lib/services/formly.builder.d.ts +5 -5
  56. package/lib/services/formly.config.d.ts +3 -9
  57. package/lib/templates/field-array.type.d.ts +3 -3
  58. package/lib/templates/field-template.type.d.ts +1 -1
  59. package/lib/templates/field.type.d.ts +5 -5
  60. package/lib/templates/field.wrapper.d.ts +1 -1
  61. package/lib/templates/formly.attributes.d.ts +1 -1
  62. package/lib/templates/formly.group.d.ts +1 -1
  63. package/lib/templates/formly.validation-message.d.ts +1 -1
  64. package/lib/utils.d.ts +5 -5
  65. package/package.json +23 -37
  66. package/preset/preset.config.d.ts +2 -0
  67. package/select/select-options.pipe.d.ts +1 -1
  68. package/testing/component-factory.d.ts +2 -2
  69. package/esm2020/json-schema/formly-json-schema.service.mjs +0 -628
  70. package/esm2020/lib/components/formly.field.mjs +0 -284
  71. package/esm2020/lib/components/formly.form.mjs +0 -147
  72. package/esm2020/lib/core.mjs +0 -19
  73. package/esm2020/lib/core.module.mjs +0 -91
  74. package/esm2020/lib/extensions/core/core.mjs +0 -148
  75. package/esm2020/lib/extensions/field-expression/field-expression.mjs +0 -250
  76. package/esm2020/lib/extensions/field-expression/utils.mjs +0 -17
  77. package/esm2020/lib/extensions/field-form/field-form.mjs +0 -141
  78. package/esm2020/lib/extensions/field-form/utils.mjs +0 -100
  79. package/esm2020/lib/extensions/field-validation/field-validation.mjs +0 -135
  80. package/esm2020/lib/models/fieldconfig.cache.mjs +0 -2
  81. package/esm2020/lib/models/fieldconfig.mjs +0 -2
  82. package/esm2020/lib/services/formly.builder.mjs +0 -93
  83. package/esm2020/lib/services/formly.config.mjs +0 -229
  84. package/esm2020/lib/templates/field-array.type.mjs +0 -84
  85. package/esm2020/lib/templates/field-template.type.mjs +0 -34
  86. package/esm2020/lib/templates/field.type.mjs +0 -57
  87. package/esm2020/lib/templates/formly.attributes.mjs +0 -187
  88. package/esm2020/lib/templates/formly.validation-message.mjs +0 -69
  89. package/esm2020/lib/utils.mjs +0 -312
  90. package/esm2020/preset/preset-substitution.extension.mjs +0 -30
  91. package/esm2020/preset/preset.module.mjs +0 -32
  92. package/esm2020/select/select-options.pipe.mjs +0 -100
  93. package/esm2020/testing/component-factory.mjs +0 -85
  94. package/fesm2015/ngx-formly-core-json-schema.mjs +0 -615
  95. package/fesm2015/ngx-formly-core-json-schema.mjs.map +0 -1
  96. package/fesm2015/ngx-formly-core-preset.mjs +0 -69
  97. package/fesm2015/ngx-formly-core-preset.mjs.map +0 -1
  98. package/fesm2015/ngx-formly-core-select.mjs +0 -120
  99. package/fesm2015/ngx-formly-core-select.mjs.map +0 -1
  100. package/fesm2015/ngx-formly-core-testing.mjs +0 -87
  101. package/fesm2015/ngx-formly-core-testing.mjs.map +0 -1
  102. package/fesm2015/ngx-formly-core.mjs +0 -2417
  103. package/fesm2015/ngx-formly-core.mjs.map +0 -1
  104. package/fesm2020/ngx-formly-core-json-schema.mjs.map +0 -1
  105. package/fesm2020/ngx-formly-core-preset.mjs +0 -67
  106. package/fesm2020/ngx-formly-core-preset.mjs.map +0 -1
  107. package/fesm2020/ngx-formly-core-testing.mjs.map +0 -1
  108. package/fesm2020/ngx-formly-core.mjs.map +0 -1
  109. package/json-schema/package.json +0 -10
  110. package/preset/package.json +0 -10
  111. package/select/package.json +0 -10
  112. package/testing/package.json +0 -10
  113. /package/{esm2020 → esm2022}/json-schema/ngx-formly-core-json-schema.mjs +0 -0
  114. /package/{esm2020 → esm2022}/json-schema/public_api.mjs +0 -0
  115. /package/{esm2020 → esm2022}/lib/models/config.mjs +0 -0
  116. /package/{esm2020 → esm2022}/lib/models/index.mjs +0 -0
  117. /package/{esm2020 → esm2022}/ngx-formly-core.mjs +0 -0
  118. /package/{esm2020 → esm2022}/preset/ngx-formly-core-preset.mjs +0 -0
  119. /package/{esm2020 → esm2022}/preset/public_api.mjs +0 -0
  120. /package/{esm2020 → esm2022}/public_api.mjs +0 -0
  121. /package/{esm2020 → esm2022}/select/ngx-formly-core-select.mjs +0 -0
  122. /package/{esm2020 → esm2022}/select/public_api.mjs +0 -0
  123. /package/{esm2020 → esm2022}/testing/ngx-formly-core-testing.mjs +0 -0
  124. /package/{esm2020 → esm2022}/testing/public_api.mjs +0 -0
  125. /package/{ngx-formly-core.d.ts → index.d.ts} +0 -0
  126. /package/json-schema/{ngx-formly-core-json-schema.d.ts → index.d.ts} +0 -0
  127. /package/preset/{ngx-formly-core-preset.d.ts → index.d.ts} +0 -0
  128. /package/select/{ngx-formly-core-select.d.ts → index.d.ts} +0 -0
  129. /package/testing/{ngx-formly-core-testing.d.ts → index.d.ts} +0 -0
@@ -1,12 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Type, TemplateRef, ComponentRef, ChangeDetectorRef, ɵNoopNgZone, VERSION, InjectionToken, Injectable, Optional, Directive, Input, ViewContainerRef, Component, ViewChild, EventEmitter, ChangeDetectionStrategy, Output, ContentChildren, Inject, ViewChildren, NgModule } from '@angular/core';
2
+ import { Type, TemplateRef, ComponentRef, ChangeDetectorRef, ɵNoopNgZone as _NoopNgZone, VERSION, Input, ViewChildren, Directive, ChangeDetectionStrategy, Component, Injectable, ViewContainerRef, ViewChild, Optional, InjectionToken, Inject, EventEmitter, ContentChildren, Output, NgModule } from '@angular/core';
3
+ import { isObservable, Observable, Subject, of, merge } from 'rxjs';
3
4
  import * as i2 from '@angular/forms';
4
- import { AbstractControl, FormGroup, FormArray, FormControl, NgControl, Validators } from '@angular/forms';
5
- import { isObservable, of, merge, Observable, Subject } from 'rxjs';
6
- import { map, distinctUntilChanged, startWith, debounceTime, filter, switchMap, take, tap } from 'rxjs/operators';
7
- import * as i2$1 from '@angular/common';
8
- import { DOCUMENT, CommonModule } from '@angular/common';
5
+ import { AbstractControl, UntypedFormArray, UntypedFormGroup, FormControl, UntypedFormControl, Validators, NgControl } from '@angular/forms';
6
+ import { tap, map, distinctUntilChanged, startWith, debounceTime, filter, switchMap, take } from 'rxjs/operators';
9
7
  import * as i1 from '@angular/platform-browser';
8
+ import * as i1$1 from '@angular/common';
9
+ import { DOCUMENT, CommonModule } from '@angular/common';
10
10
 
11
11
  function disableTreeValidityCall(form, callback) {
12
12
  const _updateTreeValidity = form._updateTreeValidity.bind(form);
@@ -299,7 +299,7 @@ function markFieldForCheck(field) {
299
299
  });
300
300
  }
301
301
  function isNoopNgZone(ngZone) {
302
- return ngZone instanceof ɵNoopNgZone;
302
+ return ngZone instanceof _NoopNgZone;
303
303
  }
304
304
  function isHiddenField(field) {
305
305
  const isHidden = (f) => f.hide || f.expressions?.hide || f.hideExpression;
@@ -314,392 +314,96 @@ function isHiddenField(field) {
314
314
  return !setDefaultValue;
315
315
  }
316
316
  function isSignalRequired() {
317
- return +VERSION.major > 18 || (+VERSION.major >= 18 && +VERSION.minor >= 1);
317
+ return +VERSION.major >= 18 && +VERSION.minor >= 1;
318
318
  }
319
319
 
320
- /**
321
- * An InjectionToken for registering additional formly config options (types, wrappers ...).
322
- */
323
- const FORMLY_CONFIG = new InjectionToken('FORMLY_CONFIG');
324
- /**
325
- * Maintains list of formly config options. This can be used to register new field type.
326
- */
327
- class FormlyConfig {
328
- constructor() {
329
- this.types = {};
330
- this.validators = {};
331
- this.wrappers = {};
332
- this.messages = {};
333
- this.extras = {
334
- checkExpressionOn: 'modelChange',
335
- lazyRender: true,
336
- resetFieldOnHide: true,
337
- renderFormlyFieldElement: true,
338
- showError(field) {
339
- return (field.formControl?.invalid &&
340
- (field.formControl?.touched || field.options.parentForm?.submitted || !!field.field.validation?.show));
341
- },
342
- };
343
- this.extensions = {};
344
- this.presets = {};
345
- this.extensionsByPriority = {};
320
+ function evalStringExpression(expression, argNames) {
321
+ try {
322
+ return Function(...argNames, `return ${expression};`);
346
323
  }
347
- addConfig(config) {
348
- if (config.types) {
349
- config.types.forEach((type) => this.setType(type));
350
- }
351
- if (config.validators) {
352
- config.validators.forEach((validator) => this.setValidator(validator));
353
- }
354
- if (config.wrappers) {
355
- config.wrappers.forEach((wrapper) => this.setWrapper(wrapper));
356
- }
357
- if (config.validationMessages) {
358
- config.validationMessages.forEach((validation) => this.addValidatorMessage(validation.name, validation.message));
359
- }
360
- if (config.extensions) {
361
- this.setSortedExtensions(config.extensions);
362
- }
363
- if (config.extras) {
364
- this.extras = { ...this.extras, ...config.extras };
365
- }
366
- if (config.presets) {
367
- this.presets = {
368
- ...this.presets,
369
- ...config.presets.reduce((acc, curr) => ({ ...acc, [curr.name]: curr.config }), {}),
370
- };
371
- }
324
+ catch (error) {
325
+ console.error(error);
372
326
  }
373
- /**
374
- * Allows you to specify a custom type which you can use in your field configuration.
375
- * You can pass an object of options, or an array of objects of options.
376
- */
377
- setType(options) {
378
- if (Array.isArray(options)) {
379
- options.forEach((option) => this.setType(option));
380
- }
381
- else {
382
- if (!this.types[options.name]) {
383
- this.types[options.name] = { name: options.name };
384
- }
385
- ['component', 'extends', 'defaultOptions', 'wrappers'].forEach((prop) => {
386
- if (options.hasOwnProperty(prop)) {
387
- this.types[options.name][prop] = options[prop];
388
- }
389
- });
390
- }
327
+ }
328
+ function evalExpression(expression, thisArg, argVal) {
329
+ if (typeof expression === 'function') {
330
+ return expression.apply(thisArg, argVal);
391
331
  }
392
- getType(name, throwIfNotFound = false) {
393
- if (name instanceof Type) {
394
- return { component: name, name: name.prototype.constructor.name };
395
- }
396
- if (!this.types[name]) {
397
- if (throwIfNotFound) {
398
- throw new Error(`[Formly Error] The type "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`);
399
- }
400
- return null;
401
- }
402
- this.mergeExtendedType(name);
403
- return this.types[name];
332
+ else {
333
+ return expression ? true : false;
404
334
  }
405
- /** @ignore */
406
- getMergedField(field = {}) {
407
- const type = this.getType(field.type);
408
- if (!type) {
409
- return;
410
- }
411
- if (type.defaultOptions) {
412
- reverseDeepMerge(field, type.defaultOptions);
413
- }
414
- const extendDefaults = type.extends && this.getType(type.extends).defaultOptions;
415
- if (extendDefaults) {
416
- reverseDeepMerge(field, extendDefaults);
417
- }
418
- if (field?.optionsTypes) {
419
- field.optionsTypes.forEach((option) => {
420
- const defaultOptions = this.getType(option).defaultOptions;
421
- if (defaultOptions) {
422
- reverseDeepMerge(field, defaultOptions);
423
- }
424
- });
425
- }
426
- const componentRef = this.resolveFieldTypeRef(field);
427
- if (componentRef?.instance?.defaultOptions) {
428
- reverseDeepMerge(field, componentRef.instance.defaultOptions);
429
- }
430
- if (!field.wrappers && type.wrappers) {
431
- field.wrappers = [...type.wrappers];
432
- }
335
+ }
336
+
337
+ function unregisterControl(field, emitEvent = false) {
338
+ const control = field.formControl;
339
+ const fieldIndex = control._fields ? control._fields.indexOf(field) : -1;
340
+ if (fieldIndex !== -1) {
341
+ control._fields.splice(fieldIndex, 1);
433
342
  }
434
- /** @ignore @internal */
435
- resolveFieldTypeRef(field = {}) {
436
- const type = this.getType(field.type);
437
- if (!type) {
438
- return null;
439
- }
440
- if (!type.component || type._componentRef) {
441
- return type._componentRef;
442
- }
443
- const { _viewContainerRef, _injector } = field.options;
444
- if (!_viewContainerRef || !_injector) {
445
- return null;
446
- }
447
- const componentRef = _viewContainerRef.createComponent(type.component, { injector: _injector });
448
- defineHiddenProp(type, '_componentRef', componentRef);
449
- try {
450
- componentRef.destroy();
451
- }
452
- catch (e) {
453
- console.error(`An error occurred while destroying the Formly component type "${field.type}"`, e);
454
- }
455
- return type._componentRef;
343
+ const form = control.parent;
344
+ if (!form) {
345
+ return;
456
346
  }
457
- setWrapper(options) {
458
- this.wrappers[options.name] = options;
459
- if (options.types) {
460
- options.types.forEach((type) => {
461
- this.setTypeWrapper(type, options.name);
462
- });
347
+ const opts = { emitEvent };
348
+ if (form instanceof UntypedFormArray) {
349
+ const key = form.controls.findIndex((c) => c === control);
350
+ if (key !== -1) {
351
+ form.removeAt(key, opts);
463
352
  }
464
353
  }
465
- getWrapper(name) {
466
- if (name instanceof Type) {
467
- return { component: name, name: name.prototype.constructor.name };
468
- }
469
- if (!this.wrappers[name]) {
470
- throw new Error(`[Formly Error] The wrapper "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`);
354
+ else if (form instanceof UntypedFormGroup) {
355
+ const paths = getKeyPath(field);
356
+ const key = paths[paths.length - 1];
357
+ if (form.get([key]) === control) {
358
+ form.removeControl(key, opts);
471
359
  }
472
- return this.wrappers[name];
473
360
  }
474
- /** @ignore */
475
- setTypeWrapper(type, name) {
476
- if (!this.types[type]) {
477
- this.types[type] = {};
478
- }
479
- if (!this.types[type].wrappers) {
480
- this.types[type].wrappers = [];
481
- }
482
- if (this.types[type].wrappers.indexOf(name) === -1) {
483
- this.types[type].wrappers.push(name);
484
- }
361
+ control.setParent(null);
362
+ }
363
+ function findControl(field) {
364
+ if (field.formControl) {
365
+ return field.formControl;
485
366
  }
486
- setValidator(options) {
487
- this.validators[options.name] = options;
367
+ if (field.shareFormControl === false) {
368
+ return null;
488
369
  }
489
- getValidator(name) {
490
- if (!this.validators[name]) {
491
- throw new Error(`[Formly Error] The validator "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`);
492
- }
493
- return this.validators[name];
370
+ return field.form?.get(getKeyPath(field));
371
+ }
372
+ function registerControl(field, control, emitEvent = false) {
373
+ control = control || field.formControl;
374
+ if (!control._fields) {
375
+ defineHiddenProp(control, '_fields', []);
494
376
  }
495
- addValidatorMessage(name, message) {
496
- this.messages[name] = message;
497
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
498
- const deprecated = { minlength: 'minLength', maxlength: 'maxLength' };
499
- if (deprecated[name]) {
500
- console.warn(`Formly deprecation: passing validation messages key '${name}' is deprecated since v6.0, use '${deprecated[name]}' instead.`);
501
- this.messages[deprecated[name]] = message;
377
+ if (control._fields.indexOf(field) === -1) {
378
+ control._fields.push(field);
379
+ }
380
+ if (!field.formControl && control) {
381
+ defineHiddenProp(field, 'formControl', control);
382
+ control.setValidators(null);
383
+ control.setAsyncValidators(null);
384
+ field.props.disabled = !!field.props.disabled;
385
+ const disabledObserver = observe(field, ['props', 'disabled'], ({ firstChange, currentValue }) => {
386
+ if (!firstChange) {
387
+ currentValue ? field.formControl.disable() : field.formControl.enable();
502
388
  }
389
+ });
390
+ if (control instanceof FormControl) {
391
+ control.registerOnDisabledChange(disabledObserver.setValue);
503
392
  }
504
393
  }
505
- getValidatorMessage(name) {
506
- return this.messages[name];
394
+ if (!field.form || !hasKey(field)) {
395
+ return;
507
396
  }
508
- setSortedExtensions(extensionOptions) {
509
- // insert new extensions, grouped by priority
510
- extensionOptions.forEach((extensionOption) => {
511
- const priority = extensionOption.priority ?? 1;
512
- this.extensionsByPriority[priority] = {
513
- ...this.extensionsByPriority[priority],
514
- [extensionOption.name]: extensionOption.extension,
515
- };
516
- });
517
- // flatten extensions object with sorted keys
518
- this.extensions = Object.keys(this.extensionsByPriority)
519
- .map(Number)
520
- .sort((a, b) => a - b)
521
- .reduce((acc, prio) => ({
522
- ...acc,
523
- ...this.extensionsByPriority[prio],
524
- }), {});
525
- }
526
- mergeExtendedType(name) {
527
- if (!this.types[name].extends) {
528
- return;
529
- }
530
- const extendedType = this.getType(this.types[name].extends);
531
- if (!this.types[name].component) {
532
- this.types[name].component = extendedType.component;
533
- }
534
- if (!this.types[name].wrappers) {
535
- this.types[name].wrappers = extendedType.wrappers;
536
- }
537
- }
538
- }
539
- FormlyConfig.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
540
- FormlyConfig.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyConfig, providedIn: 'root' });
541
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyConfig, decorators: [{
542
- type: Injectable,
543
- args: [{ providedIn: 'root' }]
544
- }] });
545
-
546
- class FormlyFormBuilder {
547
- constructor(config, injector, viewContainerRef, parentForm) {
548
- this.config = config;
549
- this.injector = injector;
550
- this.viewContainerRef = viewContainerRef;
551
- this.parentForm = parentForm;
552
- }
553
- buildForm(form, fieldGroup = [], model, options) {
554
- this.build({ fieldGroup, model, form, options });
555
- }
556
- build(field) {
557
- if (!this.config.extensions.core) {
558
- throw new Error('NgxFormly: missing `forRoot()` call. use `forRoot()` when registering the `FormlyModule`.');
559
- }
560
- if (!field.parent) {
561
- this._setOptions(field);
562
- }
563
- disableTreeValidityCall(field.form, () => {
564
- this._build(field);
565
- // TODO: add test for https://github.com/ngx-formly/ngx-formly/issues/3910
566
- if (!field.parent || field.fieldArray) {
567
- // detect changes early to avoid reset value by hidden fields
568
- const options = field.options;
569
- if (field.parent && isHiddenField(field)) {
570
- // when hide is used in expression set defaul value will not be set until detect hide changes
571
- // which causes default value not set on new item is added
572
- options._hiddenFieldsForCheck?.push({ field, default: false });
573
- }
574
- options.checkExpressions?.(field, true);
575
- options._detectChanges?.(field);
576
- }
577
- });
578
- }
579
- _build(field) {
580
- if (!field) {
581
- return;
582
- }
583
- const extensions = Object.values(this.config.extensions);
584
- extensions.forEach((extension) => extension.prePopulate?.(field));
585
- extensions.forEach((extension) => extension.onPopulate?.(field));
586
- field.fieldGroup?.forEach((f) => this._build(f));
587
- extensions.forEach((extension) => extension.postPopulate?.(field));
588
- }
589
- _setOptions(field) {
590
- field.form = field.form || new FormGroup({});
591
- field.model = field.model || {};
592
- field.options = field.options || {};
593
- const options = field.options;
594
- if (!options._viewContainerRef) {
595
- defineHiddenProp(options, '_viewContainerRef', this.viewContainerRef);
596
- }
597
- if (!options._injector) {
598
- defineHiddenProp(options, '_injector', this.injector);
599
- }
600
- if (!options.build) {
601
- options._buildForm = () => {
602
- console.warn(`Formly: 'options._buildForm' is deprecated since v6.0, use 'options.build' instead.`);
603
- this.build(field);
604
- };
605
- options.build = (f = field) => {
606
- this.build(f);
607
- return f;
608
- };
609
- }
610
- if (!options.parentForm && this.parentForm) {
611
- defineHiddenProp(options, 'parentForm', this.parentForm);
612
- if (!isSignalRequired()) {
613
- observe(options, ['parentForm', 'submitted'], ({ firstChange }) => {
614
- if (!firstChange) {
615
- options.detectChanges(field);
616
- }
617
- });
618
- }
619
- }
620
- }
621
- }
622
- FormlyFormBuilder.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFormBuilder, deps: [{ token: FormlyConfig }, { token: i0.Injector }, { token: i0.ViewContainerRef, optional: true }, { token: i2.FormGroupDirective, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
623
- FormlyFormBuilder.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFormBuilder, providedIn: 'root' });
624
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFormBuilder, decorators: [{
625
- type: Injectable,
626
- args: [{ providedIn: 'root' }]
627
- }], ctorParameters: function () { return [{ type: FormlyConfig }, { type: i0.Injector }, { type: i0.ViewContainerRef, decorators: [{
628
- type: Optional
629
- }] }, { type: i2.FormGroupDirective, decorators: [{
630
- type: Optional
631
- }] }]; } });
632
-
633
- function unregisterControl(field, emitEvent = false) {
634
- const control = field.formControl;
635
- const fieldIndex = control._fields ? control._fields.indexOf(field) : -1;
636
- if (fieldIndex !== -1) {
637
- control._fields.splice(fieldIndex, 1);
638
- }
639
- const form = control.parent;
640
- if (!form) {
641
- return;
642
- }
643
- const opts = { emitEvent };
644
- if (form instanceof FormArray) {
645
- const key = form.controls.findIndex((c) => c === control);
646
- if (key !== -1) {
647
- form.removeAt(key, opts);
648
- }
649
- }
650
- else if (form instanceof FormGroup) {
651
- const paths = getKeyPath(field);
652
- const key = paths[paths.length - 1];
653
- if (form.get([key]) === control) {
654
- form.removeControl(key, opts);
655
- }
656
- }
657
- control.setParent(null);
658
- }
659
- function findControl(field) {
660
- if (field.formControl) {
661
- return field.formControl;
662
- }
663
- if (field.shareFormControl === false) {
664
- return null;
665
- }
666
- return field.form?.get(getKeyPath(field));
667
- }
668
- function registerControl(field, control, emitEvent = false) {
669
- control = control || field.formControl;
670
- if (!control._fields) {
671
- defineHiddenProp(control, '_fields', []);
672
- }
673
- if (control._fields.indexOf(field) === -1) {
674
- control._fields.push(field);
675
- }
676
- if (!field.formControl && control) {
677
- defineHiddenProp(field, 'formControl', control);
678
- control.setValidators(null);
679
- control.setAsyncValidators(null);
680
- field.props.disabled = !!field.props.disabled;
681
- const disabledObserver = observe(field, ['props', 'disabled'], ({ firstChange, currentValue }) => {
682
- if (!firstChange) {
683
- currentValue ? field.formControl.disable() : field.formControl.enable();
684
- }
685
- });
686
- if (control instanceof FormControl) {
687
- control.registerOnDisabledChange(disabledObserver.setValue);
688
- }
689
- }
690
- if (!field.form || !hasKey(field)) {
691
- return;
692
- }
693
- let form = field.form;
694
- const paths = getKeyPath(field);
695
- const value = getFieldValue(field);
696
- if (!(isNil(control.value) && isNil(value)) && control.value !== value && control instanceof FormControl) {
697
- control.patchValue(value);
397
+ let form = field.form;
398
+ const paths = getKeyPath(field);
399
+ const value = getFieldValue(field);
400
+ if (!(isNil(control.value) && isNil(value)) && control.value !== value && control instanceof FormControl) {
401
+ control.patchValue(value);
698
402
  }
699
403
  for (let i = 0; i < paths.length - 1; i++) {
700
404
  const path = paths[i];
701
405
  if (!form.get([path])) {
702
- form.setControl(path, new FormGroup({}), { emitEvent });
406
+ form.setControl(path, new UntypedFormGroup({}), { emitEvent });
703
407
  }
704
408
  form = form.get([path]);
705
409
  }
@@ -723,630 +427,666 @@ function clearControl(form) {
723
427
  delete form?._fields;
724
428
  form.setValidators(null);
725
429
  form.setAsyncValidators(null);
726
- if (form instanceof FormGroup || form instanceof FormArray) {
430
+ if (form instanceof UntypedFormGroup || form instanceof UntypedFormArray) {
727
431
  Object.values(form.controls).forEach((c) => clearControl(c));
728
432
  }
729
433
  }
730
434
 
731
- class FormlyTemplate {
732
- constructor(ref) {
733
- this.ref = ref;
734
- }
735
- ngOnChanges() {
736
- this.name = this.name || 'formly-group';
737
- }
738
- }
739
- FormlyTemplate.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
740
- FormlyTemplate.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FormlyTemplate, selector: "[formlyTemplate]", inputs: { name: ["formlyTemplate", "name"] }, usesOnChanges: true, ngImport: i0 });
741
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplate, decorators: [{
742
- type: Directive,
743
- args: [{ selector: '[formlyTemplate]' }]
744
- }], ctorParameters: function () { return [{ type: i0.TemplateRef }]; }, propDecorators: { name: [{
745
- type: Input,
746
- args: ['formlyTemplate']
747
- }] } });
748
- // workarround for https://github.com/angular/angular/issues/43227#issuecomment-904173738
749
- class FormlyFieldTemplates {
750
- }
751
- FormlyFieldTemplates.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
752
- FormlyFieldTemplates.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates });
753
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates, decorators: [{
754
- type: Injectable
755
- }] });
756
-
757
- /**
758
- * The `<formly-field>` component is used to render the UI widget (layout + type) of a given `field`.
759
- */
760
- class FormlyField {
761
- constructor(config, renderer, _elementRef, hostContainerRef, form) {
762
- this.config = config;
763
- this.renderer = renderer;
764
- this._elementRef = _elementRef;
765
- this.hostContainerRef = hostContainerRef;
766
- this.form = form;
767
- this.hostObservers = [];
768
- this.componentRefs = [];
769
- this.hooksObservers = [];
770
- this.detectFieldBuild = false;
771
- this.valueChangesUnsubscribe = () => { };
772
- }
773
- get containerRef() {
774
- return this.config.extras.renderFormlyFieldElement ? this.viewContainerRef : this.hostContainerRef;
775
- }
776
- get elementRef() {
777
- if (this.config.extras.renderFormlyFieldElement) {
778
- return this._elementRef;
435
+ class FieldExpressionExtension {
436
+ onPopulate(field) {
437
+ if (field._expressions) {
438
+ return;
779
439
  }
780
- if (this.componentRefs?.[0] instanceof ComponentRef) {
781
- return this.componentRefs[0].location;
440
+ // cache built expression
441
+ defineHiddenProp(field, '_expressions', {});
442
+ observe(field, ['hide'], ({ currentValue, firstChange }) => {
443
+ defineHiddenProp(field, '_hide', !!currentValue);
444
+ if (!firstChange || (firstChange && currentValue === true)) {
445
+ field.props.hidden = currentValue;
446
+ field.options._hiddenFieldsForCheck.push({ field });
447
+ }
448
+ });
449
+ if (field.hideExpression) {
450
+ observe(field, ['hideExpression'], ({ currentValue: expr }) => {
451
+ field._expressions.hide = this.parseExpressions(field, 'hide', typeof expr === 'boolean' ? () => expr : expr);
452
+ });
782
453
  }
783
- return null;
784
- }
785
- ngAfterContentInit() {
786
- this.triggerHook('afterContentInit');
787
- }
788
- ngAfterViewInit() {
789
- this.triggerHook('afterViewInit');
790
- }
791
- ngDoCheck() {
792
- if (this.detectFieldBuild && this.field && this.field.options) {
793
- this.render();
454
+ const evalExpr = (key, expr) => {
455
+ if (typeof expr === 'string' || isFunction(expr)) {
456
+ field._expressions[key] = this.parseExpressions(field, key, expr);
457
+ }
458
+ else if (expr instanceof Observable) {
459
+ field._expressions[key] = {
460
+ value$: expr.pipe(tap((v) => {
461
+ this.evalExpr(field, key, v);
462
+ field.options._detectChanges(field);
463
+ })),
464
+ };
465
+ }
466
+ };
467
+ field.expressions = field.expressions || {};
468
+ for (const key of Object.keys(field.expressions)) {
469
+ observe(field, ['expressions', key], ({ currentValue: expr }) => {
470
+ evalExpr(key, isFunction(expr) ? (...args) => expr(field, args[3]) : expr);
471
+ });
472
+ }
473
+ field.expressionProperties = field.expressionProperties || {};
474
+ for (const key of Object.keys(field.expressionProperties)) {
475
+ observe(field, ['expressionProperties', key], ({ currentValue }) => evalExpr(key, currentValue));
794
476
  }
795
477
  }
796
- ngOnInit() {
797
- this.triggerHook('onInit');
798
- }
799
- ngOnChanges(changes) {
800
- this.triggerHook('onChanges', changes);
801
- }
802
- ngOnDestroy() {
803
- this.resetRefs(this.field);
804
- this.hostObservers.forEach((hostObserver) => hostObserver.unsubscribe());
805
- this.hooksObservers.forEach((unsubscribe) => unsubscribe());
806
- this.valueChangesUnsubscribe();
807
- this.triggerHook('onDestroy');
808
- }
809
- renderField(containerRef, f, wrappers = []) {
810
- if (this.containerRef === containerRef) {
811
- this.resetRefs(this.field);
812
- this.containerRef.clear();
813
- wrappers = this.field?.wrappers;
478
+ postPopulate(field) {
479
+ if (field.parent) {
480
+ return;
814
481
  }
815
- if (wrappers?.length > 0) {
816
- const [wrapper, ...wps] = wrappers;
817
- const { component } = this.config.getWrapper(wrapper);
818
- const ref = containerRef.createComponent(component);
819
- this.attachComponentRef(ref, f);
820
- observe(ref.instance, ['fieldComponent'], ({ currentValue, previousValue, firstChange }) => {
821
- if (currentValue) {
822
- if (previousValue && previousValue._lContainer === currentValue._lContainer) {
823
- return;
824
- }
825
- const viewRef = previousValue ? previousValue.detach() : null;
826
- if (viewRef && !viewRef.destroyed) {
827
- currentValue.insert(viewRef);
828
- }
829
- else {
830
- this.renderField(currentValue, f, wps);
831
- }
832
- !firstChange && ref.changeDetectorRef.detectChanges();
482
+ if (!field.options.checkExpressions) {
483
+ let checkLocked = false;
484
+ field.options.checkExpressions = (f, ignoreCache) => {
485
+ if (checkLocked) {
486
+ return;
833
487
  }
834
- });
835
- }
836
- else if (f?.type) {
837
- const inlineType = this.form?.templates?.find((ref) => ref.name === f.type);
838
- let ref;
839
- if (inlineType) {
840
- ref = containerRef.createEmbeddedView(inlineType.ref, { $implicit: f });
841
- }
842
- else {
843
- const { component } = this.config.getType(f.type, true);
844
- ref = containerRef.createComponent(component);
845
- }
846
- this.attachComponentRef(ref, f);
488
+ checkLocked = true;
489
+ const fieldChanged = this.checkExpressions(f, ignoreCache);
490
+ const options = field.options;
491
+ options._hiddenFieldsForCheck
492
+ .sort((f) => (f.field.hide ? -1 : 1))
493
+ .forEach((f) => this.changeHideState(f.field, f.field.hide ?? f.default, !ignoreCache));
494
+ options._hiddenFieldsForCheck = [];
495
+ if (fieldChanged) {
496
+ this.checkExpressions(field);
497
+ }
498
+ checkLocked = false;
499
+ };
500
+ field.options._checkField = (f, ignoreCache) => {
501
+ console.warn(`Formly: 'options._checkField' is deprecated since v6.0, use 'options.checkExpressions' instead.`);
502
+ field.options.checkExpressions(f, ignoreCache);
503
+ };
847
504
  }
848
505
  }
849
- triggerHook(name, changes) {
850
- if (name === 'onInit' || (name === 'onChanges' && changes.field && !changes.field.firstChange)) {
851
- this.valueChangesUnsubscribe();
852
- this.valueChangesUnsubscribe = this.fieldChanges(this.field);
853
- }
854
- if (this.field?.hooks?.[name]) {
855
- if (!changes || changes.field) {
856
- const r = this.field.hooks[name](this.field);
857
- if (isObservable(r) && ['onInit', 'afterContentInit', 'afterViewInit'].indexOf(name) !== -1) {
858
- const sub = r.subscribe();
859
- this.hooksObservers.push(() => sub.unsubscribe());
506
+ parseExpressions(field, path, expr) {
507
+ let parentExpression;
508
+ if (field.parent && ['hide', 'props.disabled'].includes(path)) {
509
+ const rootValue = (f) => {
510
+ return path === 'hide' ? f.hide : f.props.disabled;
511
+ };
512
+ parentExpression = () => {
513
+ let root = field.parent;
514
+ while (root.parent && !rootValue(root)) {
515
+ root = root.parent;
860
516
  }
861
- }
517
+ return rootValue(root);
518
+ };
862
519
  }
863
- if (name === 'onChanges' && changes.field) {
864
- this.resetRefs(changes.field.previousValue);
865
- this.render();
520
+ expr = expr || (() => false);
521
+ if (typeof expr === 'string') {
522
+ expr = evalStringExpression(expr, ['model', 'formState', 'field']);
866
523
  }
524
+ let currentValue;
525
+ return {
526
+ callback: (ignoreCache) => {
527
+ try {
528
+ const exprValue = evalExpression(parentExpression ? (...args) => parentExpression(field) || expr(...args) : expr, { field }, [field.model, field.options.formState, field, ignoreCache]);
529
+ if (ignoreCache ||
530
+ (currentValue !== exprValue &&
531
+ (!isObject(exprValue) ||
532
+ isObservable(exprValue) ||
533
+ JSON.stringify(exprValue) !== JSON.stringify(currentValue)))) {
534
+ currentValue = exprValue;
535
+ this.evalExpr(field, path, exprValue);
536
+ return true;
537
+ }
538
+ return false;
539
+ }
540
+ catch (error) {
541
+ error.message = `[Formly Error] [Expression "${path}"] ${error.message}`;
542
+ throw error;
543
+ }
544
+ },
545
+ };
867
546
  }
868
- attachComponentRef(ref, field) {
869
- this.componentRefs.push(ref);
870
- field._componentRefs.push(ref);
871
- if (ref instanceof ComponentRef) {
872
- Object.assign(ref.instance, { field });
547
+ checkExpressions(field, ignoreCache = false) {
548
+ if (!field) {
549
+ return false;
873
550
  }
551
+ let fieldChanged = false;
552
+ if (field._expressions) {
553
+ for (const key of Object.keys(field._expressions)) {
554
+ field._expressions[key].callback?.(ignoreCache) && (fieldChanged = true);
555
+ }
556
+ }
557
+ field.fieldGroup?.forEach((f) => this.checkExpressions(f, ignoreCache) && (fieldChanged = true));
558
+ return fieldChanged;
874
559
  }
875
- render() {
876
- if (!this.field) {
877
- return;
560
+ changeDisabledState(field, value) {
561
+ if (field.fieldGroup) {
562
+ field.fieldGroup
563
+ .filter((f) => !f._expressions.hasOwnProperty('props.disabled'))
564
+ .forEach((f) => this.changeDisabledState(f, value));
878
565
  }
879
- // require Formly build
880
- if (!this.field.options) {
881
- this.detectFieldBuild = true;
882
- return;
566
+ if (hasKey(field) && field.props.disabled !== value) {
567
+ field.props.disabled = value;
883
568
  }
884
- this.detectFieldBuild = false;
885
- this.hostObservers.forEach((hostObserver) => hostObserver.unsubscribe());
886
- this.hostObservers = [
887
- observe(this.field, ['hide'], ({ firstChange, currentValue }) => {
888
- const containerRef = this.containerRef;
889
- if (this.config.extras.lazyRender === false) {
890
- firstChange && this.renderField(containerRef, this.field);
891
- if (!firstChange || (firstChange && currentValue)) {
892
- this.elementRef &&
893
- this.renderer.setStyle(this.elementRef.nativeElement, 'display', currentValue ? 'none' : '');
569
+ }
570
+ changeHideState(field, hide, resetOnHide) {
571
+ if (field.fieldGroup) {
572
+ field.fieldGroup
573
+ .filter((f) => f && !f._expressions.hide)
574
+ .forEach((f) => this.changeHideState(f, hide, resetOnHide));
575
+ }
576
+ if (field.formControl && hasKey(field)) {
577
+ defineHiddenProp(field, '_hide', !!(hide || field.hide));
578
+ const c = field.formControl;
579
+ if (c._fields?.length > 1) {
580
+ updateValidity(c);
581
+ }
582
+ if (hide === true && (!c._fields || c._fields.every((f) => !!f._hide))) {
583
+ unregisterControl(field, true);
584
+ if (resetOnHide && field.resetOnHide) {
585
+ assignFieldValue(field, undefined);
586
+ field.formControl.reset({ value: undefined, disabled: field.formControl.disabled });
587
+ field.options.fieldChanges.next({ value: undefined, field, type: 'valueChanges' });
588
+ if (field.fieldGroup && field.formControl instanceof UntypedFormArray) {
589
+ field.fieldGroup.length = 0;
894
590
  }
895
591
  }
896
- else {
897
- if (currentValue) {
898
- containerRef.clear();
899
- if (this.field.className) {
900
- this.renderer.removeAttribute(this.elementRef.nativeElement, 'class');
901
- }
902
- }
903
- else {
904
- this.renderField(containerRef, this.field);
905
- if (this.field.className) {
906
- this.renderer.setAttribute(this.elementRef.nativeElement, 'class', this.field.className);
907
- }
908
- }
592
+ }
593
+ else if (hide === false) {
594
+ if (field.resetOnHide && !isUndefined(field.defaultValue) && isUndefined(getFieldValue(field))) {
595
+ assignFieldValue(field, field.defaultValue);
909
596
  }
910
- !firstChange && this.field.options.detectChanges(this.field);
911
- }),
912
- observe(this.field, ['className'], ({ firstChange, currentValue }) => {
913
- if ((!firstChange || (firstChange && currentValue)) &&
914
- (!this.config.extras.lazyRender || this.field.hide !== true)) {
915
- this.elementRef && this.renderer.setAttribute(this.elementRef.nativeElement, 'class', currentValue);
597
+ registerControl(field, undefined, true);
598
+ if (field.resetOnHide && field.fieldArray && field.fieldGroup?.length !== field.model?.length) {
599
+ field.options.build(field);
916
600
  }
917
- }),
918
- ];
919
- if (!isSignalRequired()) {
920
- ['touched', 'pristine', 'status'].forEach((prop) => this.hostObservers.push(observe(this.field, ['formControl', prop], ({ firstChange }) => !firstChange && markFieldForCheck(this.field))));
601
+ }
921
602
  }
922
- else if (this.field.formControl) {
923
- const events = this.field.formControl.events.subscribe(() => markFieldForCheck(this.field));
924
- this.hostObservers.push(events);
603
+ if (field.options.fieldChanges) {
604
+ field.options.fieldChanges.next({ field, type: 'hidden', value: hide });
925
605
  }
926
606
  }
927
- resetRefs(field) {
928
- if (field) {
929
- if (field._localFields) {
930
- field._localFields = [];
607
+ evalExpr(field, prop, value) {
608
+ if (prop.indexOf('model.') === 0) {
609
+ const key = prop.replace(/^model\./, ''), parent = field.fieldGroup ? field : field.parent;
610
+ let control = field?.key === key ? field.formControl : field.form.get(key);
611
+ if (!control && field.get(key)) {
612
+ control = field.get(key).formControl;
931
613
  }
932
- else {
933
- defineHiddenProp(this.field, '_localFields', []);
614
+ assignFieldValue({ key, parent, model: field.model }, value);
615
+ if (control && !(isNil(control.value) && isNil(value)) && control.value !== value) {
616
+ control.patchValue(value);
934
617
  }
935
- if (field._componentRefs) {
936
- field._componentRefs = field._componentRefs.filter((ref) => this.componentRefs.indexOf(ref) === -1);
618
+ }
619
+ else {
620
+ try {
621
+ let target = field;
622
+ const paths = this._evalExpressionPath(field, prop);
623
+ const lastIndex = paths.length - 1;
624
+ for (let i = 0; i < lastIndex; i++) {
625
+ target = target[paths[i]];
626
+ }
627
+ target[paths[lastIndex]] = value;
937
628
  }
938
- else {
939
- defineHiddenProp(this.field, '_componentRefs', []);
629
+ catch (error) {
630
+ error.message = `[Formly Error] [Expression "${prop}"] ${error.message}`;
631
+ throw error;
632
+ }
633
+ if (['templateOptions.disabled', 'props.disabled'].includes(prop) && hasKey(field)) {
634
+ this.changeDisabledState(field, value);
940
635
  }
941
636
  }
942
- this.componentRefs = [];
637
+ this.emitExpressionChanges(field, prop, value);
943
638
  }
944
- fieldChanges(field) {
945
- if (!field) {
946
- return () => { };
639
+ emitExpressionChanges(field, property, value) {
640
+ if (!field.options.fieldChanges) {
641
+ return;
947
642
  }
948
- const propsObserver = observeDeep(field, ['props'], () => field.options.detectChanges(field));
949
- let subscribes = [
950
- () => {
951
- propsObserver();
952
- },
953
- ];
954
- for (const key of Object.keys(field._expressions || {})) {
955
- const expressionObserver = observe(field, ['_expressions', key], ({ currentValue, previousValue }) => {
956
- if (previousValue?.subscription) {
957
- previousValue.subscription.unsubscribe();
958
- previousValue.subscription = null;
959
- }
960
- if (isObservable(currentValue.value$)) {
961
- currentValue.subscription = currentValue.value$.subscribe();
962
- }
963
- });
964
- subscribes.push(() => {
965
- if (field._expressions[key]?.subscription) {
966
- field._expressions[key].subscription.unsubscribe();
967
- }
968
- expressionObserver.unsubscribe();
969
- });
643
+ field.options.fieldChanges.next({
644
+ field,
645
+ type: 'expressionChanges',
646
+ property,
647
+ value,
648
+ });
649
+ }
650
+ _evalExpressionPath(field, prop) {
651
+ if (field._expressions[prop] && field._expressions[prop].paths) {
652
+ return field._expressions[prop].paths;
970
653
  }
971
- for (const path of [['focus'], ['template'], ['fieldGroupClassName'], ['validation', 'show']]) {
972
- const fieldObserver = observe(field, path, ({ firstChange }) => !firstChange && field.options.detectChanges(field));
973
- subscribes.push(() => fieldObserver.unsubscribe());
654
+ let paths = [];
655
+ if (prop.indexOf('[') === -1) {
656
+ paths = prop.split('.');
974
657
  }
975
- if (field.formControl && !field.fieldGroup) {
976
- const control = field.formControl;
977
- let valueChanges = control.valueChanges.pipe(map((value) => {
978
- field.parsers?.map((parserFn) => (value = parserFn(value, field)));
979
- if (!Object.is(value, field.formControl.value)) {
980
- field.formControl.setValue(value);
981
- }
982
- return value;
983
- }), distinctUntilChanged((x, y) => {
984
- if (x !== y || Array.isArray(x) || isObject(x)) {
985
- return false;
986
- }
987
- return true;
988
- }));
989
- if (control.value !== getFieldValue(field)) {
990
- valueChanges = valueChanges.pipe(startWith(control.value));
991
- }
992
- const { updateOn, debounce } = field.modelOptions;
993
- if ((!updateOn || updateOn === 'change') && debounce?.default > 0) {
994
- valueChanges = control.valueChanges.pipe(debounceTime(debounce.default));
995
- }
996
- const sub = valueChanges.subscribe((value) => {
997
- // workaround for https://github.com/angular/angular/issues/13792
998
- if (control._fields?.length > 1 && control instanceof FormControl) {
999
- control.patchValue(value, { emitEvent: false, onlySelf: true });
658
+ else {
659
+ prop
660
+ .split(/[[\]]{1,2}/) // https://stackoverflow.com/a/20198206
661
+ .filter((p) => p)
662
+ .forEach((path) => {
663
+ const arrayPath = path.match(/['|"](.*?)['|"]/);
664
+ if (arrayPath) {
665
+ paths.push(arrayPath[1]);
1000
666
  }
1001
- if (hasKey(field)) {
1002
- assignFieldValue(field, value);
667
+ else {
668
+ paths.push(...path.split('.').filter((p) => p));
1003
669
  }
1004
- field.options.fieldChanges.next({ value, field, type: 'valueChanges' });
1005
670
  });
1006
- subscribes.push(() => sub.unsubscribe());
1007
671
  }
1008
- let templateFieldsSubs = [];
1009
- observe(field, ['_localFields'], ({ currentValue }) => {
1010
- templateFieldsSubs.forEach((unsubscribe) => unsubscribe());
1011
- templateFieldsSubs = (currentValue || []).map((f) => this.fieldChanges(f));
1012
- });
1013
- return () => {
1014
- subscribes.forEach((unsubscribe) => unsubscribe());
1015
- templateFieldsSubs.forEach((unsubscribe) => unsubscribe());
1016
- };
672
+ if (field._expressions[prop]) {
673
+ field._expressions[prop].paths = paths;
674
+ }
675
+ return paths;
1017
676
  }
1018
677
  }
1019
- FormlyField.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyField, deps: [{ token: FormlyConfig }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i0.ViewContainerRef }, { token: FormlyFieldTemplates, optional: true }], target: i0.ɵɵFactoryTarget.Component });
1020
- FormlyField.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: FormlyField, selector: "formly-field", inputs: { field: "field" }, viewQueries: [{ propertyName: "viewContainerRef", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: '<ng-template #container></ng-template>', isInline: true, styles: [":host:empty{display:none}\n"] });
1021
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyField, decorators: [{
1022
- type: Component,
1023
- args: [{ selector: 'formly-field', template: '<ng-template #container></ng-template>', styles: [":host:empty{display:none}\n"] }]
1024
- }], ctorParameters: function () { return [{ type: FormlyConfig }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.ViewContainerRef }, { type: FormlyFieldTemplates, decorators: [{
1025
- type: Optional
1026
- }] }]; }, propDecorators: { field: [{
1027
- type: Input
1028
- }], viewContainerRef: [{
1029
- type: ViewChild,
1030
- args: ['container', { read: ViewContainerRef, static: true }]
1031
- }] } });
1032
678
 
1033
- /**
1034
- * The `<form-form>` component is the main container of the form,
1035
- * which takes care of managing the form state
1036
- * and delegates the rendering of each field to `<formly-field>` component.
1037
- */
1038
- class FormlyForm {
1039
- constructor(builder, config, ngZone, fieldTemplates) {
1040
- this.builder = builder;
679
+ class CoreExtension {
680
+ constructor(config) {
1041
681
  this.config = config;
1042
- this.ngZone = ngZone;
1043
- this.fieldTemplates = fieldTemplates;
1044
- /** Event that is emitted when the model value is changed */
1045
- this.modelChange = new EventEmitter();
1046
- this.field = { type: 'formly-group' };
1047
- this._modelChangeValue = {};
1048
- this.valueChangesUnsubscribe = () => { };
1049
- }
1050
- /** The form instance which allow to track model value and validation status. */
1051
- set form(form) {
1052
- this.field.form = form;
1053
- }
1054
- get form() {
1055
- return this.field.form;
682
+ this.formId = 0;
1056
683
  }
1057
- /** The model to be represented by the form. */
1058
- set model(model) {
1059
- if (this.config.extras.immutable && this._modelChangeValue === model) {
1060
- return;
684
+ prePopulate(field) {
685
+ const root = field.parent;
686
+ this.initRootOptions(field);
687
+ this.initFieldProps(field);
688
+ if (root) {
689
+ Object.defineProperty(field, 'options', { get: () => root.options, configurable: true });
690
+ Object.defineProperty(field, 'model', {
691
+ get: () => (hasKey(field) && field.fieldGroup ? getFieldValue(field) : root.model),
692
+ configurable: true,
693
+ });
1061
694
  }
1062
- this.setField({ model });
1063
- }
1064
- get model() {
1065
- return this.field.model;
1066
- }
1067
- /** The field configurations for building the form. */
1068
- set fields(fieldGroup) {
1069
- this.setField({ fieldGroup });
1070
- }
1071
- get fields() {
1072
- return this.field.fieldGroup;
695
+ Object.defineProperty(field, 'get', {
696
+ value: (key) => getField(field, key),
697
+ configurable: true,
698
+ });
699
+ this.getFieldComponentInstance(field).prePopulate?.(field);
1073
700
  }
1074
- /** Options for the form. */
1075
- set options(options) {
1076
- this.setField({ options });
701
+ onPopulate(field) {
702
+ this.initFieldOptions(field);
703
+ this.getFieldComponentInstance(field).onPopulate?.(field);
704
+ if (field.fieldGroup) {
705
+ field.fieldGroup.forEach((f, index) => {
706
+ if (f) {
707
+ Object.defineProperty(f, 'parent', { get: () => field, configurable: true });
708
+ Object.defineProperty(f, 'index', { get: () => index, configurable: true });
709
+ }
710
+ this.formId++;
711
+ });
712
+ }
1077
713
  }
1078
- get options() {
1079
- return this.field.options;
714
+ postPopulate(field) {
715
+ this.getFieldComponentInstance(field).postPopulate?.(field);
1080
716
  }
1081
- set templates(templates) {
1082
- this.fieldTemplates.templates = templates;
717
+ initFieldProps(field) {
718
+ field.props ??= field.templateOptions;
719
+ Object.defineProperty(field, 'templateOptions', {
720
+ get: () => field.props,
721
+ set: (props) => (field.props = props),
722
+ configurable: true,
723
+ });
1083
724
  }
1084
- ngDoCheck() {
1085
- if (this.config.extras.checkExpressionOn === 'changeDetectionCheck') {
1086
- this.checkExpressionChange();
725
+ initRootOptions(field) {
726
+ if (field.parent) {
727
+ return;
1087
728
  }
1088
- }
1089
- ngOnChanges(changes) {
1090
- if (changes.fields && this.form) {
1091
- clearControl(this.form);
729
+ const options = field.options;
730
+ field.options.formState = field.options.formState || {};
731
+ if (!options.showError) {
732
+ options.showError = this.config.extras.showError;
1092
733
  }
1093
- if (changes.fields || changes.form || (changes.model && this._modelChangeValue !== changes.model.currentValue)) {
1094
- this.valueChangesUnsubscribe();
1095
- this.builder.build(this.field);
1096
- this.valueChangesUnsubscribe = this.valueChanges();
734
+ if (!options.fieldChanges) {
735
+ defineHiddenProp(options, 'fieldChanges', new Subject());
1097
736
  }
1098
- }
1099
- ngOnDestroy() {
1100
- this.valueChangesUnsubscribe();
1101
- }
1102
- checkExpressionChange() {
1103
- this.field.options.checkExpressions?.(this.field);
1104
- }
1105
- valueChanges() {
1106
- this.valueChangesUnsubscribe();
1107
- let formEvents = null;
1108
- if (isSignalRequired()) {
1109
- let submitted = this.options?.parentForm?.submitted;
1110
- formEvents = this.form.events.subscribe(() => {
1111
- if (submitted !== this.options?.parentForm?.submitted) {
1112
- this.options.detectChanges(this.field);
1113
- submitted = this.options?.parentForm?.submitted;
1114
- }
1115
- });
737
+ if (!options._hiddenFieldsForCheck) {
738
+ options._hiddenFieldsForCheck = [];
1116
739
  }
1117
- let fieldChangesDetection = [
1118
- observeDeep(this.field.options, ['formState'], () => this.field.options.detectChanges(this.field)),
1119
- ];
1120
- const valueChanges = this.field.options.fieldChanges
1121
- .pipe(filter(({ field, type }) => hasKey(field) && type === 'valueChanges'), switchMap(() => (isNoopNgZone(this.ngZone) ? of(null) : this.ngZone.onStable.asObservable().pipe(take(1)))))
1122
- .subscribe(() => this.ngZone.runGuarded(() => {
1123
- // runGuarded is used to keep in sync the expression changes
1124
- // https://github.com/ngx-formly/ngx-formly/issues/2095
1125
- this.checkExpressionChange();
1126
- this.modelChange.emit((this._modelChangeValue = clone(this.model)));
1127
- }));
1128
- return () => {
1129
- fieldChangesDetection.forEach((fnc) => fnc());
1130
- formEvents?.unsubscribe();
1131
- valueChanges.unsubscribe();
740
+ options._markForCheck = (f) => {
741
+ console.warn(`Formly: 'options._markForCheck' is deprecated since v6.0, use 'options.detectChanges' instead.`);
742
+ options.detectChanges(f);
1132
743
  };
1133
- }
1134
- setField(field) {
1135
- if (this.config.extras.immutable) {
1136
- this.field = { ...this.field, ...clone(field) };
1137
- }
1138
- else {
1139
- Object.keys(field).forEach((p) => (this.field[p] = field[p]));
1140
- }
1141
- }
1142
- }
1143
- FormlyForm.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyForm, deps: [{ token: FormlyFormBuilder }, { token: FormlyConfig }, { token: i0.NgZone }, { token: FormlyFieldTemplates }], target: i0.ɵɵFactoryTarget.Component });
1144
- FormlyForm.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: FormlyForm, selector: "formly-form", inputs: { form: "form", model: "model", fields: "fields", options: "options" }, outputs: { modelChange: "modelChange" }, providers: [FormlyFormBuilder, FormlyFieldTemplates], queries: [{ propertyName: "templates", predicate: FormlyTemplate }], usesOnChanges: true, ngImport: i0, template: '<formly-field [field]="field"></formly-field>', isInline: true, components: [{ type: FormlyField, selector: "formly-field", inputs: ["field"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1145
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyForm, decorators: [{
1146
- type: Component,
1147
- args: [{
1148
- selector: 'formly-form',
1149
- template: '<formly-field [field]="field"></formly-field>',
1150
- providers: [FormlyFormBuilder, FormlyFieldTemplates],
1151
- changeDetection: ChangeDetectionStrategy.OnPush,
1152
- }]
1153
- }], ctorParameters: function () { return [{ type: FormlyFormBuilder }, { type: FormlyConfig }, { type: i0.NgZone }, { type: FormlyFieldTemplates }]; }, propDecorators: { form: [{
1154
- type: Input
1155
- }], model: [{
1156
- type: Input
1157
- }], fields: [{
1158
- type: Input
1159
- }], options: [{
1160
- type: Input
1161
- }], modelChange: [{
1162
- type: Output
1163
- }], templates: [{
1164
- type: ContentChildren,
1165
- args: [FormlyTemplate]
1166
- }] } });
1167
-
1168
- /**
1169
- * Allow to link the `field` HTML attributes (`id`, `name` ...) and Event attributes (`focus`, `blur` ...) to an element in the DOM.
1170
- */
1171
- class FormlyAttributes {
1172
- constructor(renderer, elementRef, _document) {
1173
- this.renderer = renderer;
1174
- this.elementRef = elementRef;
1175
- this.uiAttributesCache = {};
1176
- /**
1177
- * HostBinding doesn't register listeners conditionally which may produce some perf issues.
1178
- *
1179
- * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1991
1180
- */
1181
- this.uiEvents = {
1182
- listeners: [],
1183
- events: ['click', 'keyup', 'keydown', 'keypress', 'focus', 'blur', 'change'],
1184
- callback: (eventName, $event) => {
1185
- switch (eventName) {
1186
- case 'focus':
1187
- return this.onFocus($event);
1188
- case 'blur':
1189
- return this.onBlur($event);
1190
- case 'change':
1191
- return this.onChange($event);
1192
- default:
1193
- return this.props[eventName](this.field, $event);
1194
- }
1195
- },
744
+ options._detectChanges = (f) => {
745
+ if (f._componentRefs) {
746
+ markFieldForCheck(f);
747
+ }
748
+ f.fieldGroup?.forEach((f) => f && options._detectChanges(f));
1196
749
  };
1197
- this.document = _document;
1198
- }
1199
- get props() {
1200
- return this.field.props || {};
1201
- }
1202
- get fieldAttrElements() {
1203
- return this.field?.['_elementRefs'] || [];
1204
- }
1205
- ngOnChanges(changes) {
1206
- if (changes.field) {
1207
- this.field.name && this.setAttribute('name', this.field.name);
1208
- this.uiEvents.listeners.forEach((listener) => listener());
1209
- this.uiEvents.events.forEach((eventName) => {
1210
- if (this.props?.[eventName] || ['focus', 'blur', 'change'].indexOf(eventName) !== -1) {
1211
- this.uiEvents.listeners.push(this.renderer.listen(this.elementRef.nativeElement, eventName, (e) => this.uiEvents.callback(eventName, e)));
1212
- }
1213
- });
1214
- if (this.props?.attributes) {
1215
- observe(this.field, ['props', 'attributes'], ({ currentValue, previousValue }) => {
1216
- if (previousValue) {
1217
- Object.keys(previousValue).forEach((attr) => this.removeAttribute(attr));
1218
- }
1219
- if (currentValue) {
1220
- Object.keys(currentValue).forEach((attr) => {
1221
- if (currentValue[attr] != null) {
1222
- this.setAttribute(attr, currentValue[attr]);
1223
- }
1224
- });
1225
- }
1226
- });
750
+ options.detectChanges = (f) => {
751
+ f.options.checkExpressions?.(f);
752
+ options._detectChanges(f);
753
+ };
754
+ options.resetModel = (model) => {
755
+ model = clone(model ?? options._initialModel);
756
+ if (field.model) {
757
+ Object.keys(field.model).forEach((k) => delete field.model[k]);
758
+ Object.assign(field.model, model || {});
1227
759
  }
1228
- this.detachElementRef(changes.field.previousValue);
1229
- this.attachElementRef(changes.field.currentValue);
1230
- if (this.fieldAttrElements.length === 1) {
1231
- !this.id && this.field.id && this.setAttribute('id', this.field.id);
1232
- this.focusObserver = observe(this.field, ['focus'], ({ currentValue }) => {
1233
- this.toggleFocus(currentValue);
1234
- });
760
+ if (!isSignalRequired()) {
761
+ observe(options, ['parentForm', 'submitted']).setValue(false, false);
1235
762
  }
763
+ options.build(field);
764
+ field.form.reset(field.model);
765
+ };
766
+ options.updateInitialValue = (model) => (options._initialModel = clone(model ?? field.model));
767
+ field.options.updateInitialValue();
768
+ }
769
+ initFieldOptions(field) {
770
+ reverseDeepMerge(field, {
771
+ id: getFieldId(`formly_${this.formId}`, field, field.index),
772
+ hooks: {},
773
+ modelOptions: {},
774
+ validation: { messages: {} },
775
+ props: !field.type || !hasKey(field)
776
+ ? {}
777
+ : {
778
+ label: '',
779
+ placeholder: '',
780
+ disabled: false,
781
+ },
782
+ });
783
+ if (this.config.extras.resetFieldOnHide && field.resetOnHide !== false) {
784
+ field.resetOnHide = true;
1236
785
  }
1237
- if (changes.id) {
1238
- this.setAttribute('id', this.id);
786
+ if (field.type !== 'formly-template' &&
787
+ (field.template || field.expressions?.template || field.expressionProperties?.template)) {
788
+ field.type = 'formly-template';
1239
789
  }
1240
- }
1241
- /**
1242
- * We need to re-evaluate all the attributes on every change detection cycle, because
1243
- * by using a HostBinding we run into certain edge cases. This means that whatever logic
1244
- * is in here has to be super lean or we risk seriously damaging or destroying the performance.
1245
- *
1246
- * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1317
1247
- * Material issue: https://github.com/angular/components/issues/14024
1248
- */
1249
- ngDoCheck() {
1250
- if (!this.uiAttributes) {
1251
- const element = this.elementRef.nativeElement;
1252
- this.uiAttributes = [...FORMLY_VALIDATORS, 'tabindex', 'placeholder', 'readonly', 'disabled', 'step'].filter((attr) => !element.hasAttribute || !element.hasAttribute(attr));
790
+ if (!field.type && field.fieldGroup) {
791
+ field.type = 'formly-group';
1253
792
  }
1254
- for (let i = 0; i < this.uiAttributes.length; i++) {
1255
- const attr = this.uiAttributes[i];
1256
- const value = this.props[attr];
1257
- if (this.uiAttributesCache[attr] !== value &&
1258
- (!this.props.attributes || !this.props.attributes.hasOwnProperty(attr.toLowerCase()))) {
1259
- this.uiAttributesCache[attr] = value;
1260
- if (value || value === 0) {
1261
- this.setAttribute(attr, value === true ? attr : `${value}`);
1262
- }
1263
- else {
1264
- this.removeAttribute(attr);
1265
- }
1266
- }
793
+ if (field.type) {
794
+ this.config.getMergedField(field);
1267
795
  }
796
+ if (hasKey(field) &&
797
+ !isUndefined(field.defaultValue) &&
798
+ isUndefined(getFieldValue(field)) &&
799
+ !isHiddenField(field)) {
800
+ assignFieldValue(field, field.defaultValue);
801
+ }
802
+ field.wrappers = field.wrappers || [];
1268
803
  }
1269
- ngOnDestroy() {
1270
- this.uiEvents.listeners.forEach((listener) => listener());
1271
- this.detachElementRef(this.field);
1272
- this.focusObserver?.unsubscribe();
1273
- }
1274
- toggleFocus(value) {
1275
- const element = this.fieldAttrElements ? this.fieldAttrElements[0] : null;
1276
- if (!element || !element.nativeElement.focus) {
1277
- return;
804
+ getFieldComponentInstance(field) {
805
+ const componentRefInstance = () => {
806
+ let componentRef = this.config.resolveFieldTypeRef(field);
807
+ const fieldComponentRef = field._componentRefs?.slice(-1)[0];
808
+ if (fieldComponentRef instanceof ComponentRef &&
809
+ fieldComponentRef?.componentType === componentRef?.componentType) {
810
+ componentRef = fieldComponentRef;
811
+ }
812
+ return componentRef?.instance;
813
+ };
814
+ if (!field._proxyInstance) {
815
+ defineHiddenProp(field, '_proxyInstance', new Proxy({}, {
816
+ get: (_, prop) => componentRefInstance()?.[prop],
817
+ set: (_, prop, value) => (componentRefInstance()[prop] = value),
818
+ }));
1278
819
  }
1279
- const isFocused = !!this.document.activeElement &&
1280
- this.fieldAttrElements.some(({ nativeElement }) => this.document.activeElement === nativeElement || nativeElement.contains(this.document.activeElement));
1281
- if (value && !isFocused) {
1282
- Promise.resolve().then(() => element.nativeElement.focus());
820
+ return field._proxyInstance;
821
+ }
822
+ }
823
+
824
+ class FieldFormExtension {
825
+ prePopulate(field) {
826
+ if (!this.root) {
827
+ this.root = field;
1283
828
  }
1284
- else if (!value && isFocused) {
1285
- Promise.resolve().then(() => element.nativeElement.blur());
829
+ if (field.parent) {
830
+ Object.defineProperty(field, 'form', {
831
+ get: () => field.parent.formControl,
832
+ configurable: true,
833
+ });
1286
834
  }
1287
835
  }
1288
- onFocus($event) {
1289
- this.focusObserver?.setValue(true);
1290
- this.props.focus?.(this.field, $event);
1291
- }
1292
- onBlur($event) {
1293
- this.focusObserver?.setValue(false);
1294
- this.props.blur?.(this.field, $event);
836
+ onPopulate(field) {
837
+ if (field.hasOwnProperty('fieldGroup') && !hasKey(field)) {
838
+ defineHiddenProp(field, 'formControl', field.form);
839
+ }
840
+ else {
841
+ this.addFormControl(field);
842
+ }
1295
843
  }
1296
- // handle custom `change` event, for regular ones rely on DOM listener
1297
- onHostChange($event) {
1298
- if ($event instanceof Event) {
844
+ postPopulate(field) {
845
+ if (this.root !== field) {
1299
846
  return;
1300
847
  }
1301
- this.onChange($event);
1302
- }
1303
- onChange($event) {
1304
- this.props.change?.(this.field, $event);
1305
- this.field.formControl?.markAsDirty();
848
+ this.root = null;
849
+ const markForCheck = this.setValidators(field);
850
+ if (markForCheck && field.parent) {
851
+ let parent = field.parent;
852
+ while (parent) {
853
+ if (hasKey(parent) || !parent.parent) {
854
+ updateValidity(parent.formControl, true);
855
+ }
856
+ parent = parent.parent;
857
+ }
858
+ }
1306
859
  }
1307
- attachElementRef(f) {
1308
- if (!f) {
860
+ addFormControl(field) {
861
+ let control = findControl(field);
862
+ if (field.fieldArray) {
1309
863
  return;
1310
864
  }
1311
- if (f['_elementRefs']?.indexOf(this.elementRef) === -1) {
1312
- f['_elementRefs'].push(this.elementRef);
865
+ if (!control) {
866
+ const controlOptions = { updateOn: field.modelOptions.updateOn };
867
+ if (field.fieldGroup) {
868
+ control = new UntypedFormGroup({}, controlOptions);
869
+ }
870
+ else {
871
+ const value = hasKey(field) ? getFieldValue(field) : field.defaultValue;
872
+ control = new UntypedFormControl({ value, disabled: !!field.props.disabled }, { ...controlOptions, initialValueIsDefault: true });
873
+ }
1313
874
  }
1314
875
  else {
1315
- defineHiddenProp(f, '_elementRefs', [this.elementRef]);
876
+ if (control instanceof FormControl) {
877
+ const value = hasKey(field) ? getFieldValue(field) : field.defaultValue;
878
+ control.defaultValue = value;
879
+ }
1316
880
  }
881
+ registerControl(field, control);
1317
882
  }
1318
- detachElementRef(f) {
1319
- const index = f?.['_elementRefs'] ? this.fieldAttrElements.indexOf(this.elementRef) : -1;
1320
- if (index !== -1) {
1321
- f['_elementRefs'].splice(index, 1);
883
+ setValidators(field, disabled = false) {
884
+ if (disabled === false && hasKey(field) && field.props?.disabled) {
885
+ disabled = true;
886
+ }
887
+ let markForCheck = false;
888
+ field.fieldGroup?.forEach((f) => f && this.setValidators(f, disabled) && (markForCheck = true));
889
+ if (hasKey(field) || !field.parent || (!hasKey(field) && !field.fieldGroup)) {
890
+ const { formControl: c } = field;
891
+ if (c) {
892
+ if (hasKey(field) && c instanceof FormControl) {
893
+ if (disabled && c.enabled) {
894
+ c.disable({ emitEvent: false, onlySelf: true });
895
+ markForCheck = true;
896
+ }
897
+ if (!disabled && c.disabled) {
898
+ c.enable({ emitEvent: false, onlySelf: true });
899
+ markForCheck = true;
900
+ }
901
+ }
902
+ if (null === c.validator && this.hasValidators(field, '_validators')) {
903
+ c.setValidators(() => {
904
+ const v = Validators.compose(this.mergeValidators(field, '_validators'));
905
+ return v ? v(c) : null;
906
+ });
907
+ markForCheck = true;
908
+ }
909
+ if (null === c.asyncValidator && this.hasValidators(field, '_asyncValidators')) {
910
+ c.setAsyncValidators(() => {
911
+ const v = Validators.composeAsync(this.mergeValidators(field, '_asyncValidators'));
912
+ return v ? v(c) : of(null);
913
+ });
914
+ markForCheck = true;
915
+ }
916
+ if (markForCheck) {
917
+ updateValidity(c, true);
918
+ // update validity of `FormGroup` instance created by field with nested key.
919
+ let parent = c.parent;
920
+ for (let i = 1; i < getKeyPath(field).length; i++) {
921
+ if (parent) {
922
+ updateValidity(parent, true);
923
+ parent = parent.parent;
924
+ }
925
+ }
926
+ }
927
+ }
1322
928
  }
929
+ return markForCheck;
1323
930
  }
1324
- setAttribute(attr, value) {
1325
- this.renderer.setAttribute(this.elementRef.nativeElement, attr, value);
931
+ hasValidators(field, type) {
932
+ const c = field.formControl;
933
+ if (c?._fields?.length > 1 && c._fields.some((f) => f[type].length > 0)) {
934
+ return true;
935
+ }
936
+ else if (field[type].length > 0) {
937
+ return true;
938
+ }
939
+ return field.fieldGroup?.some((f) => f?.fieldGroup && !hasKey(f) && this.hasValidators(f, type));
1326
940
  }
1327
- removeAttribute(attr) {
1328
- this.renderer.removeAttribute(this.elementRef.nativeElement, attr);
941
+ mergeValidators(field, type) {
942
+ const validators = [];
943
+ const c = field.formControl;
944
+ if (c?._fields?.length > 1) {
945
+ c._fields
946
+ .filter((f) => !f._hide)
947
+ .forEach((f) => validators.push(...f[type]));
948
+ }
949
+ else if (field[type]) {
950
+ validators.push(...field[type]);
951
+ }
952
+ if (field.fieldGroup) {
953
+ field.fieldGroup
954
+ .filter((f) => f?.fieldGroup && !hasKey(f))
955
+ .forEach((f) => validators.push(...this.mergeValidators(f, type)));
956
+ }
957
+ return validators;
958
+ }
959
+ }
960
+
961
+ class FieldValidationExtension {
962
+ constructor(config) {
963
+ this.config = config;
964
+ }
965
+ onPopulate(field) {
966
+ this.initFieldValidation(field, 'validators');
967
+ this.initFieldValidation(field, 'asyncValidators');
968
+ }
969
+ initFieldValidation(field, type) {
970
+ const validators = [];
971
+ if (type === 'validators' && !(field.hasOwnProperty('fieldGroup') && !hasKey(field))) {
972
+ validators.push(this.getPredefinedFieldValidation(field));
973
+ }
974
+ if (field[type]) {
975
+ for (const validatorName of Object.keys(field[type])) {
976
+ validatorName === 'validation'
977
+ ? validators.push(...field[type].validation.map((v) => this.wrapNgValidatorFn(field, v)))
978
+ : validators.push(this.wrapNgValidatorFn(field, field[type][validatorName], validatorName));
979
+ }
980
+ }
981
+ defineHiddenProp(field, '_' + type, validators);
982
+ }
983
+ getPredefinedFieldValidation(field) {
984
+ let VALIDATORS = [];
985
+ FORMLY_VALIDATORS.forEach((opt) => observe(field, ['props', opt], ({ currentValue, firstChange }) => {
986
+ VALIDATORS = VALIDATORS.filter((o) => o !== opt);
987
+ if (opt === 'required' && currentValue != null && typeof currentValue !== 'boolean') {
988
+ console.warn(`Formly: Invalid prop 'required' of type '${typeof currentValue}', expected 'boolean' (Field:${field.key}).`);
989
+ }
990
+ if (currentValue != null && currentValue !== false) {
991
+ VALIDATORS.push(opt);
992
+ }
993
+ if (!firstChange && field.formControl) {
994
+ updateValidity(field.formControl);
995
+ }
996
+ }));
997
+ return (control) => {
998
+ if (VALIDATORS.length === 0) {
999
+ return null;
1000
+ }
1001
+ return Validators.compose(VALIDATORS.map((opt) => () => {
1002
+ const value = field.props[opt];
1003
+ switch (opt) {
1004
+ case 'required':
1005
+ return Validators.required(control);
1006
+ case 'pattern':
1007
+ return Validators.pattern(value)(control);
1008
+ case 'minLength':
1009
+ const minLengthResult = Validators.minLength(value)(control);
1010
+ const minLengthKey = this.config.getValidatorMessage('minlength') || field.validation?.messages?.minlength
1011
+ ? 'minlength'
1012
+ : 'minLength';
1013
+ return minLengthResult ? { [minLengthKey]: minLengthResult.minlength } : null;
1014
+ case 'maxLength':
1015
+ const maxLengthResult = Validators.maxLength(value)(control);
1016
+ const maxLengthKey = this.config.getValidatorMessage('maxlength') || field.validation?.messages?.maxlength
1017
+ ? 'maxlength'
1018
+ : 'maxLength';
1019
+ return maxLengthResult ? { [maxLengthKey]: maxLengthResult.maxlength } : null;
1020
+ case 'min':
1021
+ return Validators.min(value)(control);
1022
+ case 'max':
1023
+ return Validators.max(value)(control);
1024
+ default:
1025
+ return null;
1026
+ }
1027
+ }))(control);
1028
+ };
1029
+ }
1030
+ wrapNgValidatorFn(field, validator, validatorName) {
1031
+ let validatorOption;
1032
+ if (typeof validator === 'string') {
1033
+ validatorOption = clone(this.config.getValidator(validator));
1034
+ }
1035
+ if (typeof validator === 'object' && validator.name) {
1036
+ validatorOption = clone(this.config.getValidator(validator.name));
1037
+ if (validator.options) {
1038
+ validatorOption.options = validator.options;
1039
+ }
1040
+ }
1041
+ if (typeof validator === 'object' && validator.expression) {
1042
+ const { expression, ...options } = validator;
1043
+ validatorOption = {
1044
+ name: validatorName,
1045
+ validation: expression,
1046
+ options: Object.keys(options).length > 0 ? options : null,
1047
+ };
1048
+ }
1049
+ if (typeof validator === 'function') {
1050
+ validatorOption = {
1051
+ name: validatorName,
1052
+ validation: validator,
1053
+ };
1054
+ }
1055
+ return (control) => {
1056
+ const errors = validatorOption.validation(control, field, validatorOption.options);
1057
+ if (isPromise(errors)) {
1058
+ return errors.then((v) => this.handleResult(field, validatorName ? !!v : v, validatorOption));
1059
+ }
1060
+ if (isObservable(errors)) {
1061
+ return errors.pipe(map((v) => this.handleResult(field, validatorName ? !!v : v, validatorOption)));
1062
+ }
1063
+ return this.handleResult(field, validatorName ? !!errors : errors, validatorOption);
1064
+ };
1065
+ }
1066
+ handleResult(field, errors, { name, options }) {
1067
+ if (typeof errors === 'boolean') {
1068
+ errors = errors ? null : { [name]: options ? options : true };
1069
+ }
1070
+ const ctrl = field.formControl;
1071
+ ctrl?._childrenErrors?.[name]?.();
1072
+ if (isObject(errors)) {
1073
+ Object.keys(errors).forEach((name) => {
1074
+ const errorPath = errors[name].errorPath ? errors[name].errorPath : options?.errorPath;
1075
+ const childCtrl = errorPath ? field.formControl.get(errorPath) : null;
1076
+ if (childCtrl) {
1077
+ const { errorPath: _errorPath, ...opts } = errors[name];
1078
+ childCtrl.setErrors({ ...(childCtrl.errors || {}), [name]: opts });
1079
+ !ctrl._childrenErrors && defineHiddenProp(ctrl, '_childrenErrors', {});
1080
+ ctrl._childrenErrors[name] = () => {
1081
+ const { [name]: _toDelete, ...childErrors } = childCtrl.errors || {};
1082
+ childCtrl.setErrors(Object.keys(childErrors).length === 0 ? null : childErrors);
1083
+ };
1084
+ }
1085
+ });
1086
+ }
1087
+ return errors;
1329
1088
  }
1330
1089
  }
1331
- FormlyAttributes.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyAttributes, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Directive });
1332
- FormlyAttributes.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FormlyAttributes, selector: "[formlyAttributes]", inputs: { field: ["formlyAttributes", "field"], id: "id" }, host: { listeners: { "change": "onHostChange($event)" } }, usesOnChanges: true, ngImport: i0 });
1333
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyAttributes, decorators: [{
1334
- type: Directive,
1335
- args: [{
1336
- selector: '[formlyAttributes]',
1337
- host: {
1338
- '(change)': 'onHostChange($event)',
1339
- },
1340
- }]
1341
- }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: undefined, decorators: [{
1342
- type: Inject,
1343
- args: [DOCUMENT]
1344
- }] }]; }, propDecorators: { field: [{
1345
- type: Input,
1346
- args: ['formlyAttributes']
1347
- }], id: [{
1348
- type: Input
1349
- }] } });
1350
1090
 
1351
1091
  class FieldType {
1352
1092
  constructor() {
@@ -1390,10 +1130,10 @@ class FieldType {
1390
1130
  get formState() {
1391
1131
  return this.options?.formState || {};
1392
1132
  }
1133
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FieldType, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1134
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: FieldType, inputs: { field: "field" }, viewQueries: [{ propertyName: "_formlyControls", predicate: NgControl, descendants: true }], ngImport: i0 }); }
1393
1135
  }
1394
- FieldType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldType, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1395
- FieldType.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FieldType, inputs: { field: "field" }, viewQueries: [{ propertyName: "_formlyControls", predicate: NgControl, descendants: true }], ngImport: i0 });
1396
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldType, decorators: [{
1136
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FieldType, decorators: [{
1397
1137
  type: Directive
1398
1138
  }], propDecorators: { _formlyControls: [{
1399
1139
  type: ViewChildren,
@@ -1403,914 +1143,1212 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1403
1143
  }] } });
1404
1144
 
1405
1145
  /** @ignore */
1406
- class FormlyGroup extends FieldType {
1146
+ class FormlyTemplateType extends FieldType {
1147
+ get template() {
1148
+ if (this.field && this.field.template !== this.innerHtml.template) {
1149
+ this.innerHtml = {
1150
+ template: this.field.template,
1151
+ content: this.props.safeHtml
1152
+ ? this.sanitizer.bypassSecurityTrustHtml(this.field.template)
1153
+ : this.field.template,
1154
+ };
1155
+ }
1156
+ return this.innerHtml.content;
1157
+ }
1158
+ constructor(sanitizer) {
1159
+ super();
1160
+ this.sanitizer = sanitizer;
1161
+ this.innerHtml = {};
1162
+ }
1163
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyTemplateType, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
1164
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FormlyTemplateType, selector: "formly-template", usesInheritance: true, ngImport: i0, template: `<div [innerHtml]="template"></div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1407
1165
  }
1408
- FormlyGroup.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyGroup, deps: null, target: i0.ɵɵFactoryTarget.Component });
1409
- FormlyGroup.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: FormlyGroup, selector: "formly-group", host: { properties: { "class": "field.fieldGroupClassName || \"\"" } }, usesInheritance: true, ngImport: i0, template: `
1410
- <formly-field *ngFor="let f of field.fieldGroup" [field]="f"></formly-field>
1411
- <ng-content></ng-content>
1412
- `, isInline: true, components: [{ type: FormlyField, selector: "formly-field", inputs: ["field"] }], directives: [{ type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1413
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyGroup, decorators: [{
1166
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyTemplateType, decorators: [{
1414
1167
  type: Component,
1415
1168
  args: [{
1416
- selector: 'formly-group',
1417
- template: `
1418
- <formly-field *ngFor="let f of field.fieldGroup" [field]="f"></formly-field>
1419
- <ng-content></ng-content>
1420
- `,
1421
- host: {
1422
- '[class]': 'field.fieldGroupClassName || ""',
1423
- },
1169
+ selector: 'formly-template',
1170
+ template: `<div [innerHtml]="template"></div>`,
1424
1171
  changeDetection: ChangeDetectionStrategy.OnPush,
1425
1172
  }]
1426
- }] });
1173
+ }], ctorParameters: () => [{ type: i1.DomSanitizer }] });
1427
1174
 
1428
1175
  /**
1429
- * The `<formly-validation-message>` component renders the error message of a given `field`.
1176
+ * Maintains list of formly config options. This can be used to register new field type.
1430
1177
  */
1431
- class FormlyValidationMessage {
1432
- constructor(config) {
1433
- this.config = config;
1178
+ class FormlyConfig {
1179
+ constructor() {
1180
+ this.types = {};
1181
+ this.validators = {};
1182
+ this.wrappers = {};
1183
+ this.messages = {};
1184
+ this.extras = {
1185
+ checkExpressionOn: 'modelChange',
1186
+ lazyRender: true,
1187
+ resetFieldOnHide: true,
1188
+ renderFormlyFieldElement: true,
1189
+ showError(field) {
1190
+ return (field.formControl?.invalid &&
1191
+ (field.formControl?.touched || field.options.parentForm?.submitted || !!field.field.validation?.show));
1192
+ },
1193
+ };
1194
+ this.extensions = {};
1195
+ this.presets = {};
1196
+ this.extensionsByPriority = {};
1434
1197
  }
1435
- ngOnChanges() {
1436
- const EXPR_VALIDATORS = FORMLY_VALIDATORS.map((v) => `templateOptions.${v}`);
1437
- this.errorMessage$ = merge(this.field.formControl.statusChanges, !this.field.options
1438
- ? of(null)
1439
- : this.field.options.fieldChanges.pipe(filter(({ field, type, property }) => {
1440
- return (field === this.field &&
1441
- type === 'expressionChanges' &&
1442
- (property.indexOf('validation') !== -1 || EXPR_VALIDATORS.indexOf(property) !== -1));
1443
- }))).pipe(startWith(null), switchMap(() => (isObservable(this.errorMessage) ? this.errorMessage : of(this.errorMessage))));
1198
+ addConfig(config) {
1199
+ if (Array.isArray(config)) {
1200
+ config.forEach((c) => this.addConfig(c));
1201
+ return;
1202
+ }
1203
+ if (config.types) {
1204
+ config.types.forEach((type) => this.setType(type));
1205
+ }
1206
+ if (config.validators) {
1207
+ config.validators.forEach((validator) => this.setValidator(validator));
1208
+ }
1209
+ if (config.wrappers) {
1210
+ config.wrappers.forEach((wrapper) => this.setWrapper(wrapper));
1211
+ }
1212
+ if (config.validationMessages) {
1213
+ config.validationMessages.forEach((validation) => this.addValidatorMessage(validation.name, validation.message));
1214
+ }
1215
+ if (config.extensions) {
1216
+ this.setSortedExtensions(config.extensions);
1217
+ }
1218
+ if (config.extras) {
1219
+ this.extras = { ...this.extras, ...config.extras };
1220
+ }
1221
+ if (config.presets) {
1222
+ this.presets = {
1223
+ ...this.presets,
1224
+ ...config.presets.reduce((acc, curr) => ({ ...acc, [curr.name]: curr.config }), {}),
1225
+ };
1226
+ }
1444
1227
  }
1445
- get errorMessage() {
1446
- const fieldForm = this.field.formControl;
1447
- for (const error in fieldForm.errors) {
1448
- if (fieldForm.errors.hasOwnProperty(error)) {
1449
- let message = this.config.getValidatorMessage(error);
1450
- if (isObject(fieldForm.errors[error])) {
1451
- if (fieldForm.errors[error].errorPath) {
1452
- return undefined;
1453
- }
1454
- if (fieldForm.errors[error].message) {
1455
- message = fieldForm.errors[error].message;
1456
- }
1457
- }
1458
- if (this.field.validation?.messages?.[error]) {
1459
- message = this.field.validation.messages[error];
1460
- }
1461
- if (this.field.validators?.[error]?.message) {
1462
- message = this.field.validators[error].message;
1463
- }
1464
- if (this.field.asyncValidators?.[error]?.message) {
1465
- message = this.field.asyncValidators[error].message;
1466
- }
1467
- if (typeof message === 'function') {
1468
- return message(fieldForm.errors[error], this.field);
1469
- }
1470
- return message;
1228
+ /**
1229
+ * Allows you to specify a custom type which you can use in your field configuration.
1230
+ * You can pass an object of options, or an array of objects of options.
1231
+ */
1232
+ setType(options) {
1233
+ if (Array.isArray(options)) {
1234
+ options.forEach((option) => this.setType(option));
1235
+ }
1236
+ else {
1237
+ if (!this.types[options.name]) {
1238
+ this.types[options.name] = { name: options.name };
1471
1239
  }
1240
+ ['component', 'extends', 'defaultOptions', 'wrappers'].forEach((prop) => {
1241
+ if (options.hasOwnProperty(prop)) {
1242
+ this.types[options.name][prop] = options[prop];
1243
+ }
1244
+ });
1472
1245
  }
1473
- return undefined;
1474
1246
  }
1475
- }
1476
- FormlyValidationMessage.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyValidationMessage, deps: [{ token: FormlyConfig }], target: i0.ɵɵFactoryTarget.Component });
1477
- FormlyValidationMessage.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: FormlyValidationMessage, selector: "formly-validation-message", inputs: { field: "field" }, usesOnChanges: true, ngImport: i0, template: '{{ errorMessage$ | async }}', isInline: true, pipes: { "async": i2$1.AsyncPipe }, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1478
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyValidationMessage, decorators: [{
1479
- type: Component,
1480
- args: [{
1481
- selector: 'formly-validation-message',
1482
- template: '{{ errorMessage$ | async }}',
1483
- changeDetection: ChangeDetectionStrategy.OnPush,
1484
- }]
1485
- }], ctorParameters: function () { return [{ type: FormlyConfig }]; }, propDecorators: { field: [{
1486
- type: Input
1487
- }] } });
1488
-
1489
- class FieldArrayType extends FieldType {
1490
- onPopulate(field) {
1491
- if (hasKey(field)) {
1492
- const control = findControl(field);
1493
- registerControl(field, control ? control : new FormArray([], { updateOn: field.modelOptions.updateOn }));
1247
+ getType(name, throwIfNotFound = false) {
1248
+ if (name instanceof Type) {
1249
+ return { component: name, name: name.prototype.constructor.name };
1494
1250
  }
1495
- field.fieldGroup = field.fieldGroup || [];
1496
- const length = Array.isArray(field.model) ? field.model.length : 0;
1497
- if (field.fieldGroup.length > length) {
1498
- for (let i = field.fieldGroup.length - 1; i >= length; --i) {
1499
- unregisterControl(field.fieldGroup[i], true);
1500
- field.fieldGroup.splice(i, 1);
1251
+ if (!this.types[name]) {
1252
+ if (throwIfNotFound) {
1253
+ throw new Error(`[Formly Error] The type "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`);
1501
1254
  }
1255
+ return null;
1502
1256
  }
1503
- for (let i = field.fieldGroup.length; i < length; i++) {
1504
- const f = { ...clone(typeof field.fieldArray === 'function' ? field.fieldArray(field) : field.fieldArray) };
1505
- if (f.key !== null) {
1506
- f.key = `${i}`;
1507
- }
1508
- field.fieldGroup.push(f);
1257
+ this.mergeExtendedType(name);
1258
+ return this.types[name];
1259
+ }
1260
+ /** @ignore */
1261
+ getMergedField(field = {}) {
1262
+ const type = this.getType(field.type);
1263
+ if (!type) {
1264
+ return;
1265
+ }
1266
+ if (type.defaultOptions) {
1267
+ reverseDeepMerge(field, type.defaultOptions);
1268
+ }
1269
+ const extendDefaults = type.extends && this.getType(type.extends).defaultOptions;
1270
+ if (extendDefaults) {
1271
+ reverseDeepMerge(field, extendDefaults);
1272
+ }
1273
+ if (field?.optionsTypes) {
1274
+ field.optionsTypes.forEach((option) => {
1275
+ const defaultOptions = this.getType(option).defaultOptions;
1276
+ if (defaultOptions) {
1277
+ reverseDeepMerge(field, defaultOptions);
1278
+ }
1279
+ });
1280
+ }
1281
+ const componentRef = this.resolveFieldTypeRef(field);
1282
+ if (componentRef?.instance?.defaultOptions) {
1283
+ reverseDeepMerge(field, componentRef.instance.defaultOptions);
1284
+ }
1285
+ if (!field.wrappers && type.wrappers) {
1286
+ field.wrappers = [...type.wrappers];
1509
1287
  }
1510
1288
  }
1511
- add(i, initialModel, { markAsDirty } = { markAsDirty: true }) {
1512
- markAsDirty && this.formControl.markAsDirty();
1513
- i = i == null ? this.field.fieldGroup.length : i;
1514
- if (!this.model) {
1515
- assignFieldValue(this.field, []);
1289
+ /** @ignore @internal */
1290
+ resolveFieldTypeRef(field = {}) {
1291
+ const type = this.getType(field.type);
1292
+ if (!type) {
1293
+ return null;
1516
1294
  }
1517
- this.model.splice(i, 0, initialModel ? clone(initialModel) : undefined);
1518
- this.markFieldForCheck(this.field.fieldGroup[i]);
1519
- this._build();
1295
+ if (!type.component || type._componentRef) {
1296
+ return type._componentRef;
1297
+ }
1298
+ const { _viewContainerRef, _injector } = field.options;
1299
+ if (!_viewContainerRef || !_injector) {
1300
+ return null;
1301
+ }
1302
+ const componentRef = _viewContainerRef.createComponent(type.component, { injector: _injector });
1303
+ defineHiddenProp(type, '_componentRef', componentRef);
1304
+ try {
1305
+ componentRef.destroy();
1306
+ }
1307
+ catch (e) {
1308
+ console.error(`An error occurred while destroying the Formly component type "${field.type}"`, e);
1309
+ }
1310
+ return type._componentRef;
1520
1311
  }
1521
- remove(i, { markAsDirty } = { markAsDirty: true }) {
1522
- markAsDirty && this.formControl.markAsDirty();
1523
- this.model.splice(i, 1);
1524
- const field = this.field.fieldGroup[i];
1525
- this.field.fieldGroup.splice(i, 1);
1526
- this.field.fieldGroup.forEach((f, key) => this.updateArrayElementKey(f, `${key}`));
1527
- unregisterControl(field, true);
1528
- this._build();
1312
+ setWrapper(options) {
1313
+ this.wrappers[options.name] = options;
1314
+ if (options.types) {
1315
+ options.types.forEach((type) => {
1316
+ this.setTypeWrapper(type, options.name);
1317
+ });
1318
+ }
1529
1319
  }
1530
- _build() {
1531
- const fields = this.field.formControl._fields ?? [this.field];
1532
- fields.forEach((f) => this.options.build(f));
1533
- this.options.fieldChanges.next({
1534
- field: this.field,
1535
- value: getFieldValue(this.field),
1536
- type: 'valueChanges',
1537
- });
1320
+ getWrapper(name) {
1321
+ if (name instanceof Type) {
1322
+ return { component: name, name: name.prototype.constructor.name };
1323
+ }
1324
+ if (!this.wrappers[name]) {
1325
+ throw new Error(`[Formly Error] The wrapper "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`);
1326
+ }
1327
+ return this.wrappers[name];
1538
1328
  }
1539
- updateArrayElementKey(f, newKey) {
1540
- if (hasKey(f)) {
1541
- f.key = newKey;
1542
- return;
1329
+ /** @ignore */
1330
+ setTypeWrapper(type, name) {
1331
+ if (!this.types[type]) {
1332
+ this.types[type] = {};
1543
1333
  }
1544
- if (!f.fieldGroup?.length) {
1545
- return;
1334
+ if (!this.types[type].wrappers) {
1335
+ this.types[type].wrappers = [];
1546
1336
  }
1547
- for (let i = 0; i < f.fieldGroup.length; i++) {
1548
- this.updateArrayElementKey(f.fieldGroup[i], newKey);
1337
+ if (this.types[type].wrappers.indexOf(name) === -1) {
1338
+ this.types[type].wrappers.push(name);
1549
1339
  }
1550
1340
  }
1551
- markFieldForCheck(f) {
1552
- if (!f) {
1553
- return;
1554
- }
1555
- f.fieldGroup?.forEach((c) => this.markFieldForCheck(c));
1556
- if (f.hide === false) {
1557
- this.options._hiddenFieldsForCheck.push({ field: f });
1341
+ setValidator(options) {
1342
+ this.validators[options.name] = options;
1343
+ }
1344
+ getValidator(name) {
1345
+ if (!this.validators[name]) {
1346
+ throw new Error(`[Formly Error] The validator "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`);
1558
1347
  }
1348
+ return this.validators[name];
1559
1349
  }
1560
- }
1561
- FieldArrayType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldArrayType, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1562
- FieldArrayType.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FieldArrayType, usesInheritance: true, ngImport: i0 });
1563
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldArrayType, decorators: [{
1564
- type: Directive
1350
+ addValidatorMessage(name, message) {
1351
+ this.messages[name] = message;
1352
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
1353
+ const deprecated = { minlength: 'minLength', maxlength: 'maxLength' };
1354
+ if (deprecated[name]) {
1355
+ console.warn(`Formly deprecation: passing validation messages key '${name}' is deprecated since v6.0, use '${deprecated[name]}' instead.`);
1356
+ this.messages[deprecated[name]] = message;
1357
+ }
1358
+ }
1359
+ }
1360
+ getValidatorMessage(name) {
1361
+ return this.messages[name];
1362
+ }
1363
+ setSortedExtensions(extensionOptions) {
1364
+ // insert new extensions, grouped by priority
1365
+ extensionOptions.forEach((extensionOption) => {
1366
+ const priority = extensionOption.priority ?? 1;
1367
+ this.extensionsByPriority[priority] = {
1368
+ ...this.extensionsByPriority[priority],
1369
+ [extensionOption.name]: extensionOption.extension,
1370
+ };
1371
+ });
1372
+ // flatten extensions object with sorted keys
1373
+ this.extensions = Object.keys(this.extensionsByPriority)
1374
+ .map(Number)
1375
+ .sort((a, b) => a - b)
1376
+ .reduce((acc, prio) => ({
1377
+ ...acc,
1378
+ ...this.extensionsByPriority[prio],
1379
+ }), {});
1380
+ }
1381
+ mergeExtendedType(name) {
1382
+ if (!this.types[name].extends) {
1383
+ return;
1384
+ }
1385
+ const extendedType = this.getType(this.types[name].extends);
1386
+ if (!this.types[name].component) {
1387
+ this.types[name].component = extendedType.component;
1388
+ }
1389
+ if (!this.types[name].wrappers) {
1390
+ this.types[name].wrappers = extendedType.wrappers;
1391
+ }
1392
+ }
1393
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1394
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyConfig, providedIn: 'root' }); }
1395
+ }
1396
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyConfig, decorators: [{
1397
+ type: Injectable,
1398
+ args: [{ providedIn: 'root' }]
1565
1399
  }] });
1566
1400
 
1567
- class FieldWrapper extends FieldType {
1568
- set _formlyControls(_) { }
1569
- set _staticContent(content) {
1570
- this.fieldComponent = content;
1401
+ class FormlyTemplate {
1402
+ constructor(ref) {
1403
+ this.ref = ref;
1404
+ }
1405
+ ngOnChanges() {
1406
+ this.name = this.name || 'formly-group';
1571
1407
  }
1408
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
1409
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: FormlyTemplate, selector: "[formlyTemplate]", inputs: { name: ["formlyTemplate", "name"] }, usesOnChanges: true, ngImport: i0 }); }
1572
1410
  }
1573
- FieldWrapper.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldWrapper, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1574
- FieldWrapper.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FieldWrapper, viewQueries: [{ propertyName: "fieldComponent", first: true, predicate: ["fieldComponent"], descendants: true, read: ViewContainerRef }, { propertyName: "_staticContent", first: true, predicate: ["fieldComponent"], descendants: true, read: ViewContainerRef, static: true }], usesInheritance: true, ngImport: i0 });
1575
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldWrapper, decorators: [{
1576
- type: Directive
1577
- }], propDecorators: { fieldComponent: [{
1578
- type: ViewChild,
1579
- args: ['fieldComponent', { read: ViewContainerRef }]
1580
- }], _staticContent: [{
1581
- type: ViewChild,
1582
- args: ['fieldComponent', { read: ViewContainerRef, static: true }]
1411
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyTemplate, decorators: [{
1412
+ type: Directive,
1413
+ args: [{ selector: '[formlyTemplate]' }]
1414
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { name: [{
1415
+ type: Input,
1416
+ args: ['formlyTemplate']
1583
1417
  }] } });
1418
+ // workarround for https://github.com/angular/angular/issues/43227#issuecomment-904173738
1419
+ class FormlyFieldTemplates {
1420
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyFieldTemplates, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1421
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyFieldTemplates }); }
1422
+ }
1423
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyFieldTemplates, decorators: [{
1424
+ type: Injectable
1425
+ }] });
1584
1426
 
1585
- /** @ignore */
1586
- class FormlyTemplateType extends FieldType {
1587
- constructor(sanitizer) {
1588
- super();
1589
- this.sanitizer = sanitizer;
1590
- this.innerHtml = {};
1427
+ /**
1428
+ * The `<formly-field>` component is used to render the UI widget (layout + type) of a given `field`.
1429
+ */
1430
+ class FormlyField {
1431
+ get containerRef() {
1432
+ return this.config.extras.renderFormlyFieldElement ? this.viewContainerRef : this.hostContainerRef;
1591
1433
  }
1592
- get template() {
1593
- if (this.field && this.field.template !== this.innerHtml.template) {
1594
- this.innerHtml = {
1595
- template: this.field.template,
1596
- content: this.props.safeHtml
1597
- ? this.sanitizer.bypassSecurityTrustHtml(this.field.template)
1598
- : this.field.template,
1599
- };
1434
+ get elementRef() {
1435
+ if (this.config.extras.renderFormlyFieldElement) {
1436
+ return this._elementRef;
1600
1437
  }
1601
- return this.innerHtml.content;
1438
+ if (this.componentRefs?.[0] instanceof ComponentRef) {
1439
+ return this.componentRefs[0].location;
1440
+ }
1441
+ return null;
1602
1442
  }
1603
- }
1604
- FormlyTemplateType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplateType, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
1605
- FormlyTemplateType.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: FormlyTemplateType, selector: "formly-template", usesInheritance: true, ngImport: i0, template: `<div [innerHtml]="template"></div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1606
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplateType, decorators: [{
1607
- type: Component,
1608
- args: [{
1609
- selector: 'formly-template',
1610
- template: `<div [innerHtml]="template"></div>`,
1611
- changeDetection: ChangeDetectionStrategy.OnPush,
1612
- }]
1613
- }], ctorParameters: function () { return [{ type: i1.DomSanitizer }]; } });
1614
-
1615
- function evalStringExpression(expression, argNames) {
1616
- try {
1617
- return Function(...argNames, `return ${expression};`);
1443
+ constructor(config, renderer, _elementRef, hostContainerRef, form) {
1444
+ this.config = config;
1445
+ this.renderer = renderer;
1446
+ this._elementRef = _elementRef;
1447
+ this.hostContainerRef = hostContainerRef;
1448
+ this.form = form;
1449
+ this.hostObservers = [];
1450
+ this.componentRefs = [];
1451
+ this.hooksObservers = [];
1452
+ this.detectFieldBuild = false;
1453
+ this.valueChangesUnsubscribe = () => { };
1618
1454
  }
1619
- catch (error) {
1620
- console.error(error);
1455
+ ngAfterContentInit() {
1456
+ this.triggerHook('afterContentInit');
1621
1457
  }
1622
- }
1623
- function evalExpression(expression, thisArg, argVal) {
1624
- if (typeof expression === 'function') {
1625
- return expression.apply(thisArg, argVal);
1458
+ ngAfterViewInit() {
1459
+ this.triggerHook('afterViewInit');
1626
1460
  }
1627
- else {
1628
- return expression ? true : false;
1461
+ ngDoCheck() {
1462
+ if (this.detectFieldBuild && this.field && this.field.options) {
1463
+ this.render();
1464
+ }
1629
1465
  }
1630
- }
1631
-
1632
- class FieldExpressionExtension {
1633
- onPopulate(field) {
1634
- if (field._expressions) {
1635
- return;
1466
+ ngOnInit() {
1467
+ this.triggerHook('onInit');
1468
+ }
1469
+ ngOnChanges(changes) {
1470
+ this.triggerHook('onChanges', changes);
1471
+ }
1472
+ ngOnDestroy() {
1473
+ this.resetRefs(this.field);
1474
+ this.hostObservers.forEach((hostObserver) => hostObserver.unsubscribe());
1475
+ this.hooksObservers.forEach((unsubscribe) => unsubscribe());
1476
+ this.valueChangesUnsubscribe();
1477
+ this.triggerHook('onDestroy');
1478
+ }
1479
+ renderField(containerRef, f, wrappers = []) {
1480
+ if (this.containerRef === containerRef) {
1481
+ this.resetRefs(this.field);
1482
+ this.containerRef.clear();
1483
+ wrappers = this.field?.wrappers;
1636
1484
  }
1637
- // cache built expression
1638
- defineHiddenProp(field, '_expressions', {});
1639
- observe(field, ['hide'], ({ currentValue, firstChange }) => {
1640
- defineHiddenProp(field, '_hide', !!currentValue);
1641
- if (!firstChange || (firstChange && currentValue === true)) {
1642
- field.props.hidden = currentValue;
1643
- field.options._hiddenFieldsForCheck.push({ field });
1644
- }
1645
- });
1646
- if (field.hideExpression) {
1647
- observe(field, ['hideExpression'], ({ currentValue: expr }) => {
1648
- field._expressions.hide = this.parseExpressions(field, 'hide', typeof expr === 'boolean' ? () => expr : expr);
1485
+ if (wrappers?.length > 0) {
1486
+ const [wrapper, ...wps] = wrappers;
1487
+ const { component } = this.config.getWrapper(wrapper);
1488
+ const ref = containerRef.createComponent(component);
1489
+ this.attachComponentRef(ref, f);
1490
+ observe(ref.instance, ['fieldComponent'], ({ currentValue, previousValue, firstChange }) => {
1491
+ if (currentValue) {
1492
+ if (previousValue && previousValue._lContainer === currentValue._lContainer) {
1493
+ return;
1494
+ }
1495
+ const viewRef = previousValue ? previousValue.detach() : null;
1496
+ if (viewRef && !viewRef.destroyed) {
1497
+ currentValue.insert(viewRef);
1498
+ }
1499
+ else {
1500
+ this.renderField(currentValue, f, wps);
1501
+ }
1502
+ !firstChange && ref.changeDetectorRef.detectChanges();
1503
+ }
1649
1504
  });
1650
1505
  }
1651
- const evalExpr = (key, expr) => {
1652
- if (typeof expr === 'string' || isFunction(expr)) {
1653
- field._expressions[key] = this.parseExpressions(field, key, expr);
1506
+ else if (f?.type) {
1507
+ const inlineType = this.form?.templates?.find((ref) => ref.name === f.type);
1508
+ let ref;
1509
+ if (inlineType) {
1510
+ ref = containerRef.createEmbeddedView(inlineType.ref, { $implicit: f });
1654
1511
  }
1655
- else if (expr instanceof Observable) {
1656
- field._expressions[key] = {
1657
- value$: expr.pipe(tap((v) => {
1658
- this.evalExpr(field, key, v);
1659
- field.options._detectChanges(field);
1660
- })),
1661
- };
1512
+ else {
1513
+ const { component } = this.config.getType(f.type, true);
1514
+ ref = containerRef.createComponent(component);
1662
1515
  }
1663
- };
1664
- field.expressions = field.expressions || {};
1665
- for (const key of Object.keys(field.expressions)) {
1666
- observe(field, ['expressions', key], ({ currentValue: expr }) => {
1667
- evalExpr(key, isFunction(expr) ? (...args) => expr(field, args[3]) : expr);
1668
- });
1516
+ this.attachComponentRef(ref, f);
1669
1517
  }
1670
- field.expressionProperties = field.expressionProperties || {};
1671
- for (const key of Object.keys(field.expressionProperties)) {
1672
- observe(field, ['expressionProperties', key], ({ currentValue }) => evalExpr(key, currentValue));
1518
+ }
1519
+ triggerHook(name, changes) {
1520
+ if (name === 'onInit' || (name === 'onChanges' && changes.field && !changes.field.firstChange)) {
1521
+ this.valueChangesUnsubscribe();
1522
+ this.valueChangesUnsubscribe = this.fieldChanges(this.field);
1523
+ }
1524
+ if (this.field?.hooks?.[name]) {
1525
+ if (!changes || changes.field) {
1526
+ const r = this.field.hooks[name](this.field);
1527
+ if (isObservable(r) && ['onInit', 'afterContentInit', 'afterViewInit'].indexOf(name) !== -1) {
1528
+ const sub = r.subscribe();
1529
+ this.hooksObservers.push(() => sub.unsubscribe());
1530
+ }
1531
+ }
1532
+ }
1533
+ if (name === 'onChanges' && changes.field) {
1534
+ this.resetRefs(changes.field.previousValue);
1535
+ this.render();
1673
1536
  }
1674
1537
  }
1675
- postPopulate(field) {
1676
- if (field.parent) {
1538
+ attachComponentRef(ref, field) {
1539
+ this.componentRefs.push(ref);
1540
+ field._componentRefs.push(ref);
1541
+ if (ref instanceof ComponentRef) {
1542
+ Object.assign(ref.instance, { field });
1543
+ }
1544
+ }
1545
+ render() {
1546
+ if (!this.field) {
1677
1547
  return;
1678
1548
  }
1679
- if (!field.options.checkExpressions) {
1680
- let checkLocked = false;
1681
- field.options.checkExpressions = (f, ignoreCache) => {
1682
- if (checkLocked) {
1683
- return;
1684
- }
1685
- checkLocked = true;
1686
- const fieldChanged = this.checkExpressions(f, ignoreCache);
1687
- const options = field.options;
1688
- options._hiddenFieldsForCheck
1689
- .sort((f) => (f.field.hide ? -1 : 1))
1690
- .forEach((f) => this.changeHideState(f.field, f.field.hide ?? f.default, !ignoreCache));
1691
- options._hiddenFieldsForCheck = [];
1692
- if (fieldChanged) {
1693
- this.checkExpressions(field);
1694
- }
1695
- checkLocked = false;
1696
- };
1697
- field.options._checkField = (f, ignoreCache) => {
1698
- console.warn(`Formly: 'options._checkField' is deprecated since v6.0, use 'options.checkExpressions' instead.`);
1699
- field.options.checkExpressions(f, ignoreCache);
1700
- };
1549
+ // require Formly build
1550
+ if (!this.field.options) {
1551
+ this.detectFieldBuild = true;
1552
+ return;
1701
1553
  }
1702
- }
1703
- parseExpressions(field, path, expr) {
1704
- let parentExpression;
1705
- if (field.parent && ['hide', 'props.disabled'].includes(path)) {
1706
- const rootValue = (f) => {
1707
- return path === 'hide' ? f.hide : f.props.disabled;
1708
- };
1709
- parentExpression = () => {
1710
- let root = field.parent;
1711
- while (root.parent && !rootValue(root)) {
1712
- root = root.parent;
1554
+ this.detectFieldBuild = false;
1555
+ this.hostObservers.forEach((hostObserver) => hostObserver.unsubscribe());
1556
+ this.hostObservers = [
1557
+ observe(this.field, ['hide'], ({ firstChange, currentValue }) => {
1558
+ const containerRef = this.containerRef;
1559
+ if (this.config.extras.lazyRender === false) {
1560
+ firstChange && this.renderField(containerRef, this.field);
1561
+ if (!firstChange || (firstChange && currentValue)) {
1562
+ this.elementRef &&
1563
+ this.renderer.setStyle(this.elementRef.nativeElement, 'display', currentValue ? 'none' : '');
1564
+ }
1713
1565
  }
1714
- return rootValue(root);
1715
- };
1716
- }
1717
- expr = expr || (() => false);
1718
- if (typeof expr === 'string') {
1719
- expr = evalStringExpression(expr, ['model', 'formState', 'field']);
1720
- }
1721
- let currentValue;
1722
- return {
1723
- callback: (ignoreCache) => {
1724
- try {
1725
- const exprValue = evalExpression(parentExpression ? (...args) => parentExpression(field) || expr(...args) : expr, { field }, [field.model, field.options.formState, field, ignoreCache]);
1726
- if (ignoreCache ||
1727
- (currentValue !== exprValue &&
1728
- (!isObject(exprValue) ||
1729
- isObservable(exprValue) ||
1730
- JSON.stringify(exprValue) !== JSON.stringify(currentValue)))) {
1731
- currentValue = exprValue;
1732
- this.evalExpr(field, path, exprValue);
1733
- return true;
1566
+ else {
1567
+ if (currentValue) {
1568
+ containerRef.clear();
1569
+ if (this.field.className) {
1570
+ this.renderer.removeAttribute(this.elementRef.nativeElement, 'class');
1571
+ }
1572
+ }
1573
+ else {
1574
+ this.renderField(containerRef, this.field);
1575
+ if (this.field.className) {
1576
+ this.renderer.setAttribute(this.elementRef.nativeElement, 'class', this.field.className);
1577
+ }
1734
1578
  }
1735
- return false;
1736
1579
  }
1737
- catch (error) {
1738
- error.message = `[Formly Error] [Expression "${path}"] ${error.message}`;
1739
- throw error;
1580
+ !firstChange && this.field.options.detectChanges(this.field);
1581
+ }),
1582
+ observe(this.field, ['className'], ({ firstChange, currentValue }) => {
1583
+ if ((!firstChange || (firstChange && currentValue)) &&
1584
+ (!this.config.extras.lazyRender || this.field.hide !== true)) {
1585
+ this.elementRef && this.renderer.setAttribute(this.elementRef.nativeElement, 'class', currentValue);
1740
1586
  }
1741
- },
1742
- };
1743
- }
1744
- checkExpressions(field, ignoreCache = false) {
1745
- if (!field) {
1746
- return false;
1587
+ }),
1588
+ ];
1589
+ if (!isSignalRequired()) {
1590
+ ['touched', 'pristine', 'status'].forEach((prop) => this.hostObservers.push(observe(this.field, ['formControl', prop], ({ firstChange }) => !firstChange && markFieldForCheck(this.field))));
1747
1591
  }
1748
- let fieldChanged = false;
1749
- if (field._expressions) {
1750
- for (const key of Object.keys(field._expressions)) {
1751
- field._expressions[key].callback?.(ignoreCache) && (fieldChanged = true);
1752
- }
1592
+ else if (this.field.formControl) {
1593
+ const events = this.field.formControl.events.subscribe(() => markFieldForCheck(this.field));
1594
+ this.hostObservers.push(events);
1753
1595
  }
1754
- field.fieldGroup?.forEach((f) => this.checkExpressions(f, ignoreCache) && (fieldChanged = true));
1755
- return fieldChanged;
1756
1596
  }
1757
- changeDisabledState(field, value) {
1758
- if (field.fieldGroup) {
1759
- field.fieldGroup
1760
- .filter((f) => !f._expressions.hasOwnProperty('props.disabled'))
1761
- .forEach((f) => this.changeDisabledState(f, value));
1762
- }
1763
- if (hasKey(field) && field.props.disabled !== value) {
1764
- field.props.disabled = value;
1597
+ resetRefs(field) {
1598
+ if (field) {
1599
+ if (field._localFields) {
1600
+ field._localFields = [];
1601
+ }
1602
+ else {
1603
+ defineHiddenProp(this.field, '_localFields', []);
1604
+ }
1605
+ if (field._componentRefs) {
1606
+ field._componentRefs = field._componentRefs.filter((ref) => this.componentRefs.indexOf(ref) === -1);
1607
+ }
1608
+ else {
1609
+ defineHiddenProp(this.field, '_componentRefs', []);
1610
+ }
1765
1611
  }
1612
+ this.componentRefs = [];
1766
1613
  }
1767
- changeHideState(field, hide, resetOnHide) {
1768
- if (field.fieldGroup) {
1769
- field.fieldGroup
1770
- .filter((f) => f && !f._expressions.hide)
1771
- .forEach((f) => this.changeHideState(f, hide, resetOnHide));
1614
+ fieldChanges(field) {
1615
+ if (!field) {
1616
+ return () => { };
1772
1617
  }
1773
- if (field.formControl && hasKey(field)) {
1774
- defineHiddenProp(field, '_hide', !!(hide || field.hide));
1775
- const c = field.formControl;
1776
- if (c._fields?.length > 1) {
1777
- updateValidity(c);
1778
- }
1779
- if (hide === true && (!c._fields || c._fields.every((f) => !!f._hide))) {
1780
- unregisterControl(field, true);
1781
- if (resetOnHide && field.resetOnHide) {
1782
- assignFieldValue(field, undefined);
1783
- field.formControl.reset({ value: undefined, disabled: field.formControl.disabled });
1784
- field.options.fieldChanges.next({ value: undefined, field, type: 'valueChanges' });
1785
- if (field.fieldGroup && field.formControl instanceof FormArray) {
1786
- field.fieldGroup.length = 0;
1787
- }
1618
+ const propsObserver = observeDeep(field, ['props'], () => field.options.detectChanges(field));
1619
+ const subscribes = [
1620
+ () => {
1621
+ propsObserver();
1622
+ },
1623
+ ];
1624
+ for (const key of Object.keys(field._expressions || {})) {
1625
+ const expressionObserver = observe(field, ['_expressions', key], ({ currentValue, previousValue }) => {
1626
+ if (previousValue?.subscription) {
1627
+ previousValue.subscription.unsubscribe();
1628
+ previousValue.subscription = null;
1788
1629
  }
1789
- }
1790
- else if (hide === false) {
1791
- if (field.resetOnHide && !isUndefined(field.defaultValue) && isUndefined(getFieldValue(field))) {
1792
- assignFieldValue(field, field.defaultValue);
1630
+ if (isObservable(currentValue.value$)) {
1631
+ currentValue.subscription = currentValue.value$.subscribe();
1793
1632
  }
1794
- registerControl(field, undefined, true);
1795
- if (field.resetOnHide && field.fieldArray && field.fieldGroup?.length !== field.model?.length) {
1796
- field.options.build(field);
1633
+ });
1634
+ subscribes.push(() => {
1635
+ if (field._expressions[key]?.subscription) {
1636
+ field._expressions[key].subscription.unsubscribe();
1797
1637
  }
1798
- }
1799
- }
1800
- if (field.options.fieldChanges) {
1801
- field.options.fieldChanges.next({ field, type: 'hidden', value: hide });
1638
+ expressionObserver.unsubscribe();
1639
+ });
1802
1640
  }
1803
- }
1804
- evalExpr(field, prop, value) {
1805
- if (prop.indexOf('model.') === 0) {
1806
- const key = prop.replace(/^model\./, ''), parent = field.fieldGroup ? field : field.parent;
1807
- let control = field?.key === key ? field.formControl : field.form.get(key);
1808
- if (!control && field.get(key)) {
1809
- control = field.get(key).formControl;
1810
- }
1811
- assignFieldValue({ key, parent, model: field.model }, value);
1812
- if (control && !(isNil(control.value) && isNil(value)) && control.value !== value) {
1813
- control.patchValue(value);
1814
- }
1641
+ for (const path of [['focus'], ['template'], ['fieldGroupClassName'], ['validation', 'show']]) {
1642
+ const fieldObserver = observe(field, path, ({ firstChange }) => !firstChange && field.options.detectChanges(field));
1643
+ subscribes.push(() => fieldObserver.unsubscribe());
1815
1644
  }
1816
- else {
1817
- try {
1818
- let target = field;
1819
- const paths = this._evalExpressionPath(field, prop);
1820
- const lastIndex = paths.length - 1;
1821
- for (let i = 0; i < lastIndex; i++) {
1822
- target = target[paths[i]];
1645
+ if (field.formControl && !field.fieldGroup) {
1646
+ const control = field.formControl;
1647
+ let valueChanges = control.valueChanges.pipe(map((value) => {
1648
+ field.parsers?.map((parserFn) => (value = parserFn(value, field)));
1649
+ if (!Object.is(value, field.formControl.value)) {
1650
+ field.formControl.setValue(value);
1823
1651
  }
1824
- target[paths[lastIndex]] = value;
1825
- }
1826
- catch (error) {
1827
- error.message = `[Formly Error] [Expression "${prop}"] ${error.message}`;
1828
- throw error;
1652
+ return value;
1653
+ }), distinctUntilChanged((x, y) => {
1654
+ if (x !== y || Array.isArray(x) || isObject(x)) {
1655
+ return false;
1656
+ }
1657
+ return true;
1658
+ }));
1659
+ if (control.value !== getFieldValue(field)) {
1660
+ valueChanges = valueChanges.pipe(startWith(control.value));
1829
1661
  }
1830
- if (['templateOptions.disabled', 'props.disabled'].includes(prop) && hasKey(field)) {
1831
- this.changeDisabledState(field, value);
1662
+ const { updateOn, debounce } = field.modelOptions;
1663
+ if ((!updateOn || updateOn === 'change') && debounce?.default > 0) {
1664
+ valueChanges = control.valueChanges.pipe(debounceTime(debounce.default));
1832
1665
  }
1666
+ const sub = valueChanges.subscribe((value) => {
1667
+ // workaround for https://github.com/angular/angular/issues/13792
1668
+ if (control._fields?.length > 1 && control instanceof FormControl) {
1669
+ control.patchValue(value, { emitEvent: false, onlySelf: true });
1670
+ }
1671
+ if (hasKey(field)) {
1672
+ assignFieldValue(field, value);
1673
+ }
1674
+ field.options.fieldChanges.next({ value, field, type: 'valueChanges' });
1675
+ });
1676
+ subscribes.push(() => sub.unsubscribe());
1833
1677
  }
1834
- this.emitExpressionChanges(field, prop, value);
1835
- }
1836
- emitExpressionChanges(field, property, value) {
1837
- if (!field.options.fieldChanges) {
1838
- return;
1839
- }
1840
- field.options.fieldChanges.next({
1841
- field,
1842
- type: 'expressionChanges',
1843
- property,
1844
- value,
1678
+ let templateFieldsSubs = [];
1679
+ observe(field, ['_localFields'], ({ currentValue }) => {
1680
+ templateFieldsSubs.forEach((unsubscribe) => unsubscribe());
1681
+ templateFieldsSubs = (currentValue || []).map((f) => this.fieldChanges(f));
1845
1682
  });
1683
+ return () => {
1684
+ subscribes.forEach((unsubscribe) => unsubscribe());
1685
+ templateFieldsSubs.forEach((unsubscribe) => unsubscribe());
1686
+ };
1846
1687
  }
1847
- _evalExpressionPath(field, prop) {
1848
- if (field._expressions[prop] && field._expressions[prop].paths) {
1849
- return field._expressions[prop].paths;
1688
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyField, deps: [{ token: FormlyConfig }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i0.ViewContainerRef }, { token: FormlyFieldTemplates, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
1689
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FormlyField, isStandalone: true, selector: "formly-field", inputs: { field: "field" }, viewQueries: [{ propertyName: "viewContainerRef", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: '<ng-template #container></ng-template>', isInline: true, styles: [":host:empty{display:none}\n"] }); }
1690
+ }
1691
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyField, decorators: [{
1692
+ type: Component,
1693
+ args: [{ selector: 'formly-field', template: '<ng-template #container></ng-template>', standalone: true, styles: [":host:empty{display:none}\n"] }]
1694
+ }], ctorParameters: () => [{ type: FormlyConfig }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.ViewContainerRef }, { type: FormlyFieldTemplates, decorators: [{
1695
+ type: Optional
1696
+ }] }], propDecorators: { field: [{
1697
+ type: Input
1698
+ }], viewContainerRef: [{
1699
+ type: ViewChild,
1700
+ args: ['container', { read: ViewContainerRef, static: true }]
1701
+ }] } });
1702
+ class LegacyFormlyField extends FormlyField {
1703
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LegacyFormlyField, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1704
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: LegacyFormlyField, selector: "formly-field", usesInheritance: true, ngImport: i0, template: '<ng-template #container></ng-template>', isInline: true, styles: [":host:empty{display:none}\n"] }); }
1705
+ }
1706
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LegacyFormlyField, decorators: [{
1707
+ type: Component,
1708
+ args: [{ selector: 'formly-field', template: '<ng-template #container></ng-template>', standalone: false, styles: [":host:empty{display:none}\n"] }]
1709
+ }] });
1710
+
1711
+ /** @ignore */
1712
+ class FormlyGroup extends FieldType {
1713
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyGroup, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1714
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FormlyGroup, selector: "formly-group", host: { properties: { "class": "field.fieldGroupClassName || \"\"" } }, usesInheritance: true, ngImport: i0, template: `
1715
+ <formly-field *ngFor="let f of field.fieldGroup" [field]="f"></formly-field>
1716
+ <ng-content></ng-content>
1717
+ `, isInline: true, dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: LegacyFormlyField, selector: "formly-field" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1718
+ }
1719
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyGroup, decorators: [{
1720
+ type: Component,
1721
+ args: [{
1722
+ selector: 'formly-group',
1723
+ template: `
1724
+ <formly-field *ngFor="let f of field.fieldGroup" [field]="f"></formly-field>
1725
+ <ng-content></ng-content>
1726
+ `,
1727
+ host: {
1728
+ '[class]': 'field.fieldGroupClassName || ""',
1729
+ },
1730
+ changeDetection: ChangeDetectionStrategy.OnPush,
1731
+ }]
1732
+ }] });
1733
+
1734
+ /**
1735
+ * An InjectionToken for registering additional formly config options (types, wrappers ...).
1736
+ */
1737
+ const FORMLY_CONFIG = new InjectionToken('FORMLY_CONFIG');
1738
+ function withDefaultConfig(config) {
1739
+ return {
1740
+ types: [
1741
+ { name: 'formly-group', component: FormlyGroup },
1742
+ { name: 'formly-template', component: FormlyTemplateType },
1743
+ ],
1744
+ extensions: [
1745
+ { name: 'core', extension: new CoreExtension(config), priority: -250 },
1746
+ { name: 'field-validation', extension: new FieldValidationExtension(config), priority: -200 },
1747
+ { name: 'field-form', extension: new FieldFormExtension(), priority: -150 },
1748
+ { name: 'field-expression', extension: new FieldExpressionExtension(), priority: -100 },
1749
+ ],
1750
+ };
1751
+ }
1752
+ const provideFormlyCore = (configs = []) => {
1753
+ return [
1754
+ { provide: FORMLY_CONFIG, multi: true, useFactory: withDefaultConfig, deps: [FormlyConfig] },
1755
+ provideFormlyConfig(configs),
1756
+ ];
1757
+ };
1758
+ const provideFormlyConfig = (configs = []) => {
1759
+ return [{ provide: FORMLY_CONFIG, multi: true, useValue: configs }];
1760
+ };
1761
+
1762
+ class FormlyFormBuilder {
1763
+ constructor(config, injector, viewContainerRef, parentForm, configs = []) {
1764
+ this.config = config;
1765
+ this.injector = injector;
1766
+ this.viewContainerRef = viewContainerRef;
1767
+ this.parentForm = parentForm;
1768
+ configs.forEach((c) => config.addConfig(c));
1769
+ }
1770
+ buildForm(form, fieldGroup = [], model, options) {
1771
+ this.build({ fieldGroup, model, form, options });
1772
+ }
1773
+ build(field) {
1774
+ if (!this.config.extensions.core) {
1775
+ throw new Error('NgxFormly: missing `forRoot()` call. use `forRoot()` when registering the `FormlyModule`.');
1850
1776
  }
1851
- let paths = [];
1852
- if (prop.indexOf('[') === -1) {
1853
- paths = prop.split('.');
1777
+ if (!field.parent) {
1778
+ this._setOptions(field);
1854
1779
  }
1855
- else {
1856
- prop
1857
- .split(/[[\]]{1,2}/) // https://stackoverflow.com/a/20198206
1858
- .filter((p) => p)
1859
- .forEach((path) => {
1860
- const arrayPath = path.match(/['|"](.*?)['|"]/);
1861
- if (arrayPath) {
1862
- paths.push(arrayPath[1]);
1863
- }
1864
- else {
1865
- paths.push(...path.split('.').filter((p) => p));
1780
+ disableTreeValidityCall(field.form, () => {
1781
+ this._build(field);
1782
+ // TODO: add test for https://github.com/ngx-formly/ngx-formly/issues/3910
1783
+ if (!field.parent || field.fieldArray) {
1784
+ // detect changes early to avoid reset value by hidden fields
1785
+ const options = field.options;
1786
+ if (field.parent && isHiddenField(field)) {
1787
+ // when hide is used in expression set defaul value will not be set until detect hide changes
1788
+ // which causes default value not set on new item is added
1789
+ options._hiddenFieldsForCheck?.push({ field, default: false });
1866
1790
  }
1867
- });
1791
+ options.checkExpressions?.(field, true);
1792
+ options._detectChanges?.(field);
1793
+ }
1794
+ });
1795
+ }
1796
+ _build(field) {
1797
+ if (!field) {
1798
+ return;
1868
1799
  }
1869
- if (field._expressions[prop]) {
1870
- field._expressions[prop].paths = paths;
1800
+ const extensions = Object.values(this.config.extensions);
1801
+ extensions.forEach((extension) => extension.prePopulate?.(field));
1802
+ extensions.forEach((extension) => extension.onPopulate?.(field));
1803
+ field.fieldGroup?.forEach((f) => this._build(f));
1804
+ extensions.forEach((extension) => extension.postPopulate?.(field));
1805
+ }
1806
+ _setOptions(field) {
1807
+ field.form = field.form || new UntypedFormGroup({});
1808
+ field.model = field.model || {};
1809
+ field.options = field.options || {};
1810
+ const options = field.options;
1811
+ if (!options._viewContainerRef) {
1812
+ defineHiddenProp(options, '_viewContainerRef', this.viewContainerRef);
1813
+ }
1814
+ if (!options._injector) {
1815
+ defineHiddenProp(options, '_injector', this.injector);
1816
+ }
1817
+ if (!options.build) {
1818
+ options._buildForm = () => {
1819
+ console.warn(`Formly: 'options._buildForm' is deprecated since v6.0, use 'options.build' instead.`);
1820
+ this.build(field);
1821
+ };
1822
+ options.build = (f = field) => {
1823
+ this.build(f);
1824
+ return f;
1825
+ };
1826
+ }
1827
+ if (!options.parentForm && this.parentForm) {
1828
+ defineHiddenProp(options, 'parentForm', this.parentForm);
1829
+ if (!isSignalRequired()) {
1830
+ observe(options, ['parentForm', 'submitted'], ({ firstChange }) => {
1831
+ if (!firstChange) {
1832
+ options.detectChanges(field);
1833
+ }
1834
+ });
1835
+ }
1871
1836
  }
1872
- return paths;
1873
1837
  }
1838
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyFormBuilder, deps: [{ token: FormlyConfig }, { token: i0.Injector }, { token: i0.ViewContainerRef, optional: true }, { token: i2.FormGroupDirective, optional: true }, { token: FORMLY_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
1839
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyFormBuilder, providedIn: 'root' }); }
1874
1840
  }
1841
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyFormBuilder, decorators: [{
1842
+ type: Injectable,
1843
+ args: [{ providedIn: 'root' }]
1844
+ }], ctorParameters: () => [{ type: FormlyConfig }, { type: i0.Injector }, { type: i0.ViewContainerRef, decorators: [{
1845
+ type: Optional
1846
+ }] }, { type: i2.FormGroupDirective, decorators: [{
1847
+ type: Optional
1848
+ }] }, { type: undefined, decorators: [{
1849
+ type: Optional
1850
+ }, {
1851
+ type: Inject,
1852
+ args: [FORMLY_CONFIG]
1853
+ }] }] });
1875
1854
 
1876
- class FieldValidationExtension {
1877
- constructor(config) {
1855
+ /**
1856
+ * The `<form-form>` component is the main container of the form,
1857
+ * which takes care of managing the form state
1858
+ * and delegates the rendering of each field to `<formly-field>` component.
1859
+ */
1860
+ class FormlyForm {
1861
+ /** The form instance which allow to track model value and validation status. */
1862
+ set form(form) {
1863
+ this.field.form = form;
1864
+ }
1865
+ get form() {
1866
+ return this.field.form;
1867
+ }
1868
+ /** The model to be represented by the form. */
1869
+ set model(model) {
1870
+ if (this.config.extras.immutable && this._modelChangeValue === model) {
1871
+ return;
1872
+ }
1873
+ this.setField({ model });
1874
+ }
1875
+ get model() {
1876
+ return this.field.model;
1877
+ }
1878
+ /** The field configurations for building the form. */
1879
+ set fields(fieldGroup) {
1880
+ this.setField({ fieldGroup });
1881
+ }
1882
+ get fields() {
1883
+ return this.field.fieldGroup;
1884
+ }
1885
+ /** Options for the form. */
1886
+ set options(options) {
1887
+ this.setField({ options });
1888
+ }
1889
+ get options() {
1890
+ return this.field.options;
1891
+ }
1892
+ set templates(templates) {
1893
+ this.fieldTemplates.templates = templates;
1894
+ }
1895
+ constructor(builder, config, ngZone, fieldTemplates) {
1896
+ this.builder = builder;
1878
1897
  this.config = config;
1898
+ this.ngZone = ngZone;
1899
+ this.fieldTemplates = fieldTemplates;
1900
+ /** Event that is emitted when the model value is changed */
1901
+ this.modelChange = new EventEmitter();
1902
+ this.field = { type: 'formly-group' };
1903
+ this._modelChangeValue = {};
1904
+ this.valueChangesUnsubscribe = () => { };
1879
1905
  }
1880
- onPopulate(field) {
1881
- this.initFieldValidation(field, 'validators');
1882
- this.initFieldValidation(field, 'asyncValidators');
1906
+ ngDoCheck() {
1907
+ if (this.config.extras.checkExpressionOn === 'changeDetectionCheck') {
1908
+ this.checkExpressionChange();
1909
+ }
1883
1910
  }
1884
- initFieldValidation(field, type) {
1885
- const validators = [];
1886
- if (type === 'validators' && !(field.hasOwnProperty('fieldGroup') && !hasKey(field))) {
1887
- validators.push(this.getPredefinedFieldValidation(field));
1911
+ ngOnChanges(changes) {
1912
+ if (changes.fields && this.form) {
1913
+ clearControl(this.form);
1888
1914
  }
1889
- if (field[type]) {
1890
- for (const validatorName of Object.keys(field[type])) {
1891
- validatorName === 'validation'
1892
- ? validators.push(...field[type].validation.map((v) => this.wrapNgValidatorFn(field, v)))
1893
- : validators.push(this.wrapNgValidatorFn(field, field[type][validatorName], validatorName));
1894
- }
1915
+ if (changes.fields || changes.form || (changes.model && this._modelChangeValue !== changes.model.currentValue)) {
1916
+ this.valueChangesUnsubscribe();
1917
+ this.builder.build(this.field);
1918
+ this.valueChangesUnsubscribe = this.valueChanges();
1895
1919
  }
1896
- defineHiddenProp(field, '_' + type, validators);
1897
1920
  }
1898
- getPredefinedFieldValidation(field) {
1899
- let VALIDATORS = [];
1900
- FORMLY_VALIDATORS.forEach((opt) => observe(field, ['props', opt], ({ currentValue, firstChange }) => {
1901
- VALIDATORS = VALIDATORS.filter((o) => o !== opt);
1902
- if (opt === 'required' && currentValue != null && typeof currentValue !== 'boolean') {
1903
- console.warn(`Formly: Invalid prop 'required' of type '${typeof currentValue}', expected 'boolean' (Field:${field.key}).`);
1904
- }
1905
- if (currentValue != null && currentValue !== false) {
1906
- VALIDATORS.push(opt);
1907
- }
1908
- if (!firstChange && field.formControl) {
1909
- updateValidity(field.formControl);
1910
- }
1911
- }));
1912
- return (control) => {
1913
- if (VALIDATORS.length === 0) {
1914
- return null;
1915
- }
1916
- return Validators.compose(VALIDATORS.map((opt) => () => {
1917
- const value = field.props[opt];
1918
- switch (opt) {
1919
- case 'required':
1920
- return Validators.required(control);
1921
- case 'pattern':
1922
- return Validators.pattern(value)(control);
1923
- case 'minLength':
1924
- const minLengthResult = Validators.minLength(value)(control);
1925
- const minLengthKey = this.config.getValidatorMessage('minlength') || field.validation?.messages?.minlength
1926
- ? 'minlength'
1927
- : 'minLength';
1928
- return minLengthResult ? { [minLengthKey]: minLengthResult.minlength } : null;
1929
- case 'maxLength':
1930
- const maxLengthResult = Validators.maxLength(value)(control);
1931
- const maxLengthKey = this.config.getValidatorMessage('maxlength') || field.validation?.messages?.maxlength
1932
- ? 'maxlength'
1933
- : 'maxLength';
1934
- return maxLengthResult ? { [maxLengthKey]: maxLengthResult.maxlength } : null;
1935
- case 'min':
1936
- return Validators.min(value)(control);
1937
- case 'max':
1938
- return Validators.max(value)(control);
1939
- default:
1940
- return null;
1921
+ ngOnDestroy() {
1922
+ this.valueChangesUnsubscribe();
1923
+ }
1924
+ checkExpressionChange() {
1925
+ this.field.options.checkExpressions?.(this.field);
1926
+ }
1927
+ valueChanges() {
1928
+ this.valueChangesUnsubscribe();
1929
+ let formEvents = null;
1930
+ if (isSignalRequired()) {
1931
+ let submitted = this.options?.parentForm?.submitted;
1932
+ formEvents = this.form.events.subscribe(() => {
1933
+ if (submitted !== this.options?.parentForm?.submitted) {
1934
+ this.options.detectChanges(this.field);
1935
+ submitted = this.options?.parentForm?.submitted;
1941
1936
  }
1942
- }))(control);
1937
+ });
1938
+ }
1939
+ const fieldChangesDetection = [
1940
+ observeDeep(this.field.options, ['formState'], () => this.field.options.detectChanges(this.field)),
1941
+ ];
1942
+ const valueChanges = this.field.options.fieldChanges
1943
+ .pipe(filter(({ field, type }) => hasKey(field) && type === 'valueChanges'), switchMap(() => (isNoopNgZone(this.ngZone) ? of(null) : this.ngZone.onStable.asObservable().pipe(take(1)))))
1944
+ .subscribe(() => this.ngZone.runGuarded(() => {
1945
+ // runGuarded is used to keep in sync the expression changes
1946
+ // https://github.com/ngx-formly/ngx-formly/issues/2095
1947
+ this.checkExpressionChange();
1948
+ this.modelChange.emit((this._modelChangeValue = clone(this.model)));
1949
+ }));
1950
+ return () => {
1951
+ fieldChangesDetection.forEach((fnc) => fnc());
1952
+ formEvents?.unsubscribe();
1953
+ valueChanges.unsubscribe();
1943
1954
  };
1944
1955
  }
1945
- wrapNgValidatorFn(field, validator, validatorName) {
1946
- let validatorOption;
1947
- if (typeof validator === 'string') {
1948
- validatorOption = clone(this.config.getValidator(validator));
1949
- }
1950
- if (typeof validator === 'object' && validator.name) {
1951
- validatorOption = clone(this.config.getValidator(validator.name));
1952
- if (validator.options) {
1953
- validatorOption.options = validator.options;
1954
- }
1955
- }
1956
- if (typeof validator === 'object' && validator.expression) {
1957
- const { expression, ...options } = validator;
1958
- validatorOption = {
1959
- name: validatorName,
1960
- validation: expression,
1961
- options: Object.keys(options).length > 0 ? options : null,
1962
- };
1956
+ setField(field) {
1957
+ if (this.config.extras.immutable) {
1958
+ this.field = { ...this.field, ...clone(field) };
1963
1959
  }
1964
- if (typeof validator === 'function') {
1965
- validatorOption = {
1966
- name: validatorName,
1967
- validation: validator,
1968
- };
1960
+ else {
1961
+ Object.keys(field).forEach((p) => (this.field[p] = field[p]));
1969
1962
  }
1970
- return (control) => {
1971
- const errors = validatorOption.validation(control, field, validatorOption.options);
1972
- if (isPromise(errors)) {
1973
- return errors.then((v) => this.handleResult(field, validatorName ? !!v : v, validatorOption));
1963
+ }
1964
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyForm, deps: [{ token: FormlyFormBuilder }, { token: FormlyConfig }, { token: i0.NgZone }, { token: FormlyFieldTemplates }], target: i0.ɵɵFactoryTarget.Component }); }
1965
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FormlyForm, isStandalone: true, selector: "formly-form", inputs: { form: "form", model: "model", fields: "fields", options: "options" }, outputs: { modelChange: "modelChange" }, providers: [FormlyFormBuilder, FormlyFieldTemplates], queries: [{ propertyName: "templates", predicate: FormlyTemplate }], usesOnChanges: true, ngImport: i0, template: '<formly-field [field]="field"></formly-field>', isInline: true, dependencies: [{ kind: "component", type: FormlyField, selector: "formly-field", inputs: ["field"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1966
+ }
1967
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyForm, decorators: [{
1968
+ type: Component,
1969
+ args: [{
1970
+ selector: 'formly-form',
1971
+ template: '<formly-field [field]="field"></formly-field>',
1972
+ providers: [FormlyFormBuilder, FormlyFieldTemplates],
1973
+ imports: [FormlyField],
1974
+ changeDetection: ChangeDetectionStrategy.OnPush,
1975
+ standalone: true,
1976
+ }]
1977
+ }], ctorParameters: () => [{ type: FormlyFormBuilder }, { type: FormlyConfig }, { type: i0.NgZone }, { type: FormlyFieldTemplates }], propDecorators: { form: [{
1978
+ type: Input
1979
+ }], model: [{
1980
+ type: Input
1981
+ }], fields: [{
1982
+ type: Input
1983
+ }], options: [{
1984
+ type: Input
1985
+ }], modelChange: [{
1986
+ type: Output
1987
+ }], templates: [{
1988
+ type: ContentChildren,
1989
+ args: [FormlyTemplate]
1990
+ }] } });
1991
+ class LegacyFormlyForm extends FormlyForm {
1992
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LegacyFormlyForm, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1993
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: LegacyFormlyForm, selector: "formly-form", providers: [FormlyFormBuilder, FormlyFieldTemplates], usesInheritance: true, ngImport: i0, template: '<formly-field [field]="field"></formly-field>', isInline: true, dependencies: [{ kind: "component", type: LegacyFormlyField, selector: "formly-field" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1994
+ }
1995
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: LegacyFormlyForm, decorators: [{
1996
+ type: Component,
1997
+ args: [{
1998
+ selector: 'formly-form',
1999
+ template: '<formly-field [field]="field"></formly-field>',
2000
+ providers: [FormlyFormBuilder, FormlyFieldTemplates],
2001
+ changeDetection: ChangeDetectionStrategy.OnPush,
2002
+ standalone: false,
2003
+ }]
2004
+ }] });
2005
+
2006
+ /**
2007
+ * Allow to link the `field` HTML attributes (`id`, `name` ...) and Event attributes (`focus`, `blur` ...) to an element in the DOM.
2008
+ */
2009
+ class FormlyAttributes {
2010
+ get props() {
2011
+ return this.field.props || {};
2012
+ }
2013
+ get fieldAttrElements() {
2014
+ return this.field?.['_elementRefs'] || [];
2015
+ }
2016
+ constructor(renderer, elementRef, _document) {
2017
+ this.renderer = renderer;
2018
+ this.elementRef = elementRef;
2019
+ this.uiAttributesCache = {};
2020
+ /**
2021
+ * HostBinding doesn't register listeners conditionally which may produce some perf issues.
2022
+ *
2023
+ * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1991
2024
+ */
2025
+ this.uiEvents = {
2026
+ listeners: [],
2027
+ events: ['click', 'keyup', 'keydown', 'keypress', 'focus', 'blur', 'change', 'wheel'],
2028
+ callback: (eventName, $event) => {
2029
+ switch (eventName) {
2030
+ case 'focus':
2031
+ return this.onFocus($event);
2032
+ case 'blur':
2033
+ return this.onBlur($event);
2034
+ case 'change':
2035
+ return this.onChange($event);
2036
+ default:
2037
+ return this.props[eventName](this.field, $event);
2038
+ }
2039
+ },
2040
+ };
2041
+ this.document = _document;
2042
+ }
2043
+ ngOnChanges(changes) {
2044
+ if (changes.field) {
2045
+ this.field.name && this.setAttribute('name', this.field.name);
2046
+ this.uiEvents.listeners.forEach((listener) => listener());
2047
+ this.uiEvents.events.forEach((eventName) => {
2048
+ if (this.props?.[eventName] || ['focus', 'blur', 'change'].indexOf(eventName) !== -1) {
2049
+ this.uiEvents.listeners.push(this.renderer.listen(this.elementRef.nativeElement, eventName, (e) => this.uiEvents.callback(eventName, e)));
2050
+ }
2051
+ });
2052
+ if (this.props?.attributes) {
2053
+ observe(this.field, ['props', 'attributes'], ({ currentValue, previousValue }) => {
2054
+ if (previousValue) {
2055
+ Object.keys(previousValue).forEach((attr) => this.removeAttribute(attr));
2056
+ }
2057
+ if (currentValue) {
2058
+ Object.keys(currentValue).forEach((attr) => {
2059
+ if (currentValue[attr] != null) {
2060
+ this.setAttribute(attr, currentValue[attr]);
2061
+ }
2062
+ });
2063
+ }
2064
+ });
1974
2065
  }
1975
- if (isObservable(errors)) {
1976
- return errors.pipe(map((v) => this.handleResult(field, validatorName ? !!v : v, validatorOption)));
2066
+ this.detachElementRef(changes.field.previousValue);
2067
+ this.attachElementRef(changes.field.currentValue);
2068
+ if (this.fieldAttrElements.length === 1) {
2069
+ !this.id && this.field.id && this.setAttribute('id', this.field.id);
2070
+ this.focusObserver = observe(this.field, ['focus'], ({ currentValue }) => {
2071
+ this.toggleFocus(currentValue);
2072
+ });
1977
2073
  }
1978
- return this.handleResult(field, validatorName ? !!errors : errors, validatorOption);
1979
- };
1980
- }
1981
- handleResult(field, errors, { name, options }) {
1982
- if (typeof errors === 'boolean') {
1983
- errors = errors ? null : { [name]: options ? options : true };
1984
2074
  }
1985
- const ctrl = field.formControl;
1986
- ctrl?._childrenErrors?.[name]?.();
1987
- if (isObject(errors)) {
1988
- Object.keys(errors).forEach((name) => {
1989
- const errorPath = errors[name].errorPath ? errors[name].errorPath : options?.errorPath;
1990
- const childCtrl = errorPath ? field.formControl.get(errorPath) : null;
1991
- if (childCtrl) {
1992
- const { errorPath: _errorPath, ...opts } = errors[name];
1993
- childCtrl.setErrors({ ...(childCtrl.errors || {}), [name]: opts });
1994
- !ctrl._childrenErrors && defineHiddenProp(ctrl, '_childrenErrors', {});
1995
- ctrl._childrenErrors[name] = () => {
1996
- const { [name]: _toDelete, ...childErrors } = childCtrl.errors || {};
1997
- childCtrl.setErrors(Object.keys(childErrors).length === 0 ? null : childErrors);
1998
- };
1999
- }
2000
- });
2075
+ if (changes.id) {
2076
+ this.setAttribute('id', this.id);
2001
2077
  }
2002
- return errors;
2003
2078
  }
2004
- }
2005
-
2006
- class FieldFormExtension {
2007
- prePopulate(field) {
2008
- if (!this.root) {
2009
- this.root = field;
2079
+ /**
2080
+ * We need to re-evaluate all the attributes on every change detection cycle, because
2081
+ * by using a HostBinding we run into certain edge cases. This means that whatever logic
2082
+ * is in here has to be super lean or we risk seriously damaging or destroying the performance.
2083
+ *
2084
+ * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1317
2085
+ * Material issue: https://github.com/angular/components/issues/14024
2086
+ */
2087
+ ngDoCheck() {
2088
+ if (!this.uiAttributes) {
2089
+ const element = this.elementRef.nativeElement;
2090
+ this.uiAttributes = [...FORMLY_VALIDATORS, 'tabindex', 'placeholder', 'readonly', 'disabled', 'step'].filter((attr) => !element.hasAttribute || !element.hasAttribute(attr));
2010
2091
  }
2011
- if (field.parent) {
2012
- Object.defineProperty(field, 'form', {
2013
- get: () => field.parent.formControl,
2014
- configurable: true,
2015
- });
2092
+ for (let i = 0; i < this.uiAttributes.length; i++) {
2093
+ const attr = this.uiAttributes[i];
2094
+ const value = this.props[attr];
2095
+ if (this.uiAttributesCache[attr] !== value &&
2096
+ (!this.props.attributes || !this.props.attributes.hasOwnProperty(attr.toLowerCase()))) {
2097
+ this.uiAttributesCache[attr] = value;
2098
+ if (value || value === 0) {
2099
+ this.setAttribute(attr, value === true ? attr : `${value}`);
2100
+ }
2101
+ else {
2102
+ this.removeAttribute(attr);
2103
+ }
2104
+ }
2016
2105
  }
2017
2106
  }
2018
- onPopulate(field) {
2019
- if (field.hasOwnProperty('fieldGroup') && !hasKey(field)) {
2020
- defineHiddenProp(field, 'formControl', field.form);
2107
+ ngOnDestroy() {
2108
+ this.uiEvents.listeners.forEach((listener) => listener());
2109
+ this.detachElementRef(this.field);
2110
+ this.focusObserver?.unsubscribe();
2111
+ }
2112
+ toggleFocus(value) {
2113
+ const element = this.fieldAttrElements ? this.fieldAttrElements[0] : null;
2114
+ if (!element || !element.nativeElement.focus) {
2115
+ return;
2021
2116
  }
2022
- else {
2023
- this.addFormControl(field);
2117
+ const isFocused = !!this.document.activeElement &&
2118
+ this.fieldAttrElements.some(({ nativeElement }) => this.document.activeElement === nativeElement || nativeElement.contains(this.document.activeElement));
2119
+ if (value && !isFocused) {
2120
+ Promise.resolve().then(() => element.nativeElement.focus());
2121
+ }
2122
+ else if (!value && isFocused) {
2123
+ Promise.resolve().then(() => element.nativeElement.blur());
2024
2124
  }
2025
2125
  }
2026
- postPopulate(field) {
2027
- if (this.root !== field) {
2126
+ onFocus($event) {
2127
+ this.focusObserver?.setValue(true);
2128
+ this.props.focus?.(this.field, $event);
2129
+ }
2130
+ onBlur($event) {
2131
+ this.focusObserver?.setValue(false);
2132
+ this.props.blur?.(this.field, $event);
2133
+ }
2134
+ // handle custom `change` event, for regular ones rely on DOM listener
2135
+ onHostChange($event) {
2136
+ if ($event instanceof Event) {
2028
2137
  return;
2029
2138
  }
2030
- this.root = null;
2031
- const markForCheck = this.setValidators(field);
2032
- if (markForCheck && field.parent) {
2033
- let parent = field.parent;
2034
- while (parent) {
2035
- if (hasKey(parent) || !parent.parent) {
2036
- updateValidity(parent.formControl, true);
2037
- }
2038
- parent = parent.parent;
2039
- }
2040
- }
2139
+ this.onChange($event);
2041
2140
  }
2042
- addFormControl(field) {
2043
- let control = findControl(field);
2044
- if (field.fieldArray) {
2141
+ onChange($event) {
2142
+ this.props.change?.(this.field, $event);
2143
+ this.field.formControl?.markAsDirty();
2144
+ }
2145
+ attachElementRef(f) {
2146
+ if (!f) {
2045
2147
  return;
2046
2148
  }
2047
- if (!control) {
2048
- const controlOptions = { updateOn: field.modelOptions.updateOn };
2049
- if (field.fieldGroup) {
2050
- control = new FormGroup({}, controlOptions);
2051
- }
2052
- else {
2053
- const value = hasKey(field) ? getFieldValue(field) : field.defaultValue;
2054
- control = new FormControl({ value, disabled: !!field.props.disabled }, { ...controlOptions, initialValueIsDefault: true });
2055
- }
2149
+ if (f['_elementRefs']?.indexOf(this.elementRef) === -1) {
2150
+ f['_elementRefs'].push(this.elementRef);
2056
2151
  }
2057
2152
  else {
2058
- if (control instanceof FormControl) {
2059
- const value = hasKey(field) ? getFieldValue(field) : field.defaultValue;
2060
- control.defaultValue = value;
2061
- }
2153
+ defineHiddenProp(f, '_elementRefs', [this.elementRef]);
2062
2154
  }
2063
- registerControl(field, control);
2064
2155
  }
2065
- setValidators(field, disabled = false) {
2066
- if (disabled === false && hasKey(field) && field.props?.disabled) {
2067
- disabled = true;
2068
- }
2069
- let markForCheck = false;
2070
- field.fieldGroup?.forEach((f) => f && this.setValidators(f, disabled) && (markForCheck = true));
2071
- if (hasKey(field) || !field.parent || (!hasKey(field) && !field.fieldGroup)) {
2072
- const { formControl: c } = field;
2073
- if (c) {
2074
- if (hasKey(field) && c instanceof FormControl) {
2075
- if (disabled && c.enabled) {
2076
- c.disable({ emitEvent: false, onlySelf: true });
2077
- markForCheck = true;
2078
- }
2079
- if (!disabled && c.disabled) {
2080
- c.enable({ emitEvent: false, onlySelf: true });
2081
- markForCheck = true;
2082
- }
2083
- }
2084
- if (null === c.validator && this.hasValidators(field, '_validators')) {
2085
- c.setValidators(() => {
2086
- const v = Validators.compose(this.mergeValidators(field, '_validators'));
2087
- return v ? v(c) : null;
2088
- });
2089
- markForCheck = true;
2090
- }
2091
- if (null === c.asyncValidator && this.hasValidators(field, '_asyncValidators')) {
2092
- c.setAsyncValidators(() => {
2093
- const v = Validators.composeAsync(this.mergeValidators(field, '_asyncValidators'));
2094
- return v ? v(c) : of(null);
2095
- });
2096
- markForCheck = true;
2097
- }
2098
- if (markForCheck) {
2099
- updateValidity(c, true);
2100
- // update validity of `FormGroup` instance created by field with nested key.
2101
- let parent = c.parent;
2102
- for (let i = 1; i < getKeyPath(field).length; i++) {
2103
- if (parent) {
2104
- updateValidity(parent, true);
2105
- parent = parent.parent;
2106
- }
2107
- }
2108
- }
2109
- }
2156
+ detachElementRef(f) {
2157
+ const index = f?.['_elementRefs'] ? this.fieldAttrElements.indexOf(this.elementRef) : -1;
2158
+ if (index !== -1) {
2159
+ f['_elementRefs'].splice(index, 1);
2110
2160
  }
2111
- return markForCheck;
2112
2161
  }
2113
- hasValidators(field, type) {
2114
- const c = field.formControl;
2115
- if (c?._fields?.length > 1 && c._fields.some((f) => f[type].length > 0)) {
2116
- return true;
2117
- }
2118
- else if (field[type].length > 0) {
2119
- return true;
2120
- }
2121
- return field.fieldGroup?.some((f) => f?.fieldGroup && !hasKey(f) && this.hasValidators(f, type));
2162
+ setAttribute(attr, value) {
2163
+ this.renderer.setAttribute(this.elementRef.nativeElement, attr, value);
2122
2164
  }
2123
- mergeValidators(field, type) {
2124
- const validators = [];
2125
- const c = field.formControl;
2126
- if (c?._fields?.length > 1) {
2127
- c._fields
2128
- .filter((f) => !f._hide)
2129
- .forEach((f) => validators.push(...f[type]));
2130
- }
2131
- else if (field[type]) {
2132
- validators.push(...field[type]);
2133
- }
2134
- if (field.fieldGroup) {
2135
- field.fieldGroup
2136
- .filter((f) => f?.fieldGroup && !hasKey(f))
2137
- .forEach((f) => validators.push(...this.mergeValidators(f, type)));
2165
+ removeAttribute(attr) {
2166
+ this.renderer.removeAttribute(this.elementRef.nativeElement, attr);
2167
+ }
2168
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyAttributes, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Directive }); }
2169
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: FormlyAttributes, selector: "[formlyAttributes]", inputs: { field: ["formlyAttributes", "field"], id: "id" }, host: { listeners: { "change": "onHostChange($event)" } }, usesOnChanges: true, ngImport: i0 }); }
2170
+ }
2171
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyAttributes, decorators: [{
2172
+ type: Directive,
2173
+ args: [{
2174
+ selector: '[formlyAttributes]',
2175
+ host: {
2176
+ '(change)': 'onHostChange($event)',
2177
+ },
2178
+ }]
2179
+ }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: undefined, decorators: [{
2180
+ type: Inject,
2181
+ args: [DOCUMENT]
2182
+ }] }], propDecorators: { field: [{
2183
+ type: Input,
2184
+ args: ['formlyAttributes']
2185
+ }], id: [{
2186
+ type: Input
2187
+ }] } });
2188
+
2189
+ /**
2190
+ * The `<formly-validation-message>` component renders the error message of a given `field`.
2191
+ */
2192
+ class FormlyValidationMessage {
2193
+ constructor(config) {
2194
+ this.config = config;
2195
+ }
2196
+ ngOnChanges() {
2197
+ const EXPR_VALIDATORS = FORMLY_VALIDATORS.map((v) => `templateOptions.${v}`);
2198
+ this.errorMessage$ = merge(this.field.formControl.statusChanges, !this.field.options
2199
+ ? of(null)
2200
+ : this.field.options.fieldChanges.pipe(filter(({ field, type, property }) => {
2201
+ return (field === this.field &&
2202
+ type === 'expressionChanges' &&
2203
+ (property.indexOf('validation') !== -1 || EXPR_VALIDATORS.indexOf(property) !== -1));
2204
+ }))).pipe(startWith(null), switchMap(() => (isObservable(this.errorMessage) ? this.errorMessage : of(this.errorMessage))));
2205
+ }
2206
+ get errorMessage() {
2207
+ const fieldForm = this.field.formControl;
2208
+ for (const error in fieldForm.errors) {
2209
+ if (fieldForm.errors.hasOwnProperty(error)) {
2210
+ let message = this.config.getValidatorMessage(error);
2211
+ if (isObject(fieldForm.errors[error])) {
2212
+ if (fieldForm.errors[error].errorPath) {
2213
+ return undefined;
2214
+ }
2215
+ if (fieldForm.errors[error].message) {
2216
+ message = fieldForm.errors[error].message;
2217
+ }
2218
+ }
2219
+ if (this.field.validation?.messages?.[error]) {
2220
+ message = this.field.validation.messages[error];
2221
+ }
2222
+ if (this.field.validators?.[error]?.message) {
2223
+ message = this.field.validators[error].message;
2224
+ }
2225
+ if (this.field.asyncValidators?.[error]?.message) {
2226
+ message = this.field.asyncValidators[error].message;
2227
+ }
2228
+ if (typeof message === 'function') {
2229
+ return message(fieldForm.errors[error], this.field);
2230
+ }
2231
+ return message;
2232
+ }
2138
2233
  }
2139
- return validators;
2234
+ return undefined;
2140
2235
  }
2236
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyValidationMessage, deps: [{ token: FormlyConfig }], target: i0.ɵɵFactoryTarget.Component }); }
2237
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FormlyValidationMessage, selector: "formly-validation-message", inputs: { field: "field" }, usesOnChanges: true, ngImport: i0, template: '{{ errorMessage$ | async }}', isInline: true, dependencies: [{ kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2141
2238
  }
2239
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyValidationMessage, decorators: [{
2240
+ type: Component,
2241
+ args: [{
2242
+ selector: 'formly-validation-message',
2243
+ template: '{{ errorMessage$ | async }}',
2244
+ changeDetection: ChangeDetectionStrategy.OnPush,
2245
+ }]
2246
+ }], ctorParameters: () => [{ type: FormlyConfig }], propDecorators: { field: [{
2247
+ type: Input
2248
+ }] } });
2142
2249
 
2143
- class CoreExtension {
2144
- constructor(config) {
2145
- this.config = config;
2146
- this.formId = 0;
2147
- }
2148
- prePopulate(field) {
2149
- const root = field.parent;
2150
- this.initRootOptions(field);
2151
- this.initFieldProps(field);
2152
- if (root) {
2153
- Object.defineProperty(field, 'options', { get: () => root.options, configurable: true });
2154
- Object.defineProperty(field, 'model', {
2155
- get: () => (hasKey(field) && field.fieldGroup ? getFieldValue(field) : root.model),
2156
- configurable: true,
2157
- });
2250
+ class FieldArrayType extends FieldType {
2251
+ onPopulate(field) {
2252
+ if (hasKey(field)) {
2253
+ const control = findControl(field);
2254
+ registerControl(field, control ? control : new UntypedFormArray([], { updateOn: field.modelOptions.updateOn }));
2255
+ }
2256
+ field.fieldGroup = field.fieldGroup || [];
2257
+ const length = Array.isArray(field.model) ? field.model.length : 0;
2258
+ if (field.fieldGroup.length > length) {
2259
+ for (let i = field.fieldGroup.length - 1; i >= length; --i) {
2260
+ unregisterControl(field.fieldGroup[i], true);
2261
+ field.fieldGroup.splice(i, 1);
2262
+ }
2263
+ }
2264
+ for (let i = field.fieldGroup.length; i < length; i++) {
2265
+ const f = { ...clone(typeof field.fieldArray === 'function' ? field.fieldArray(field) : field.fieldArray) };
2266
+ if (f.key !== null) {
2267
+ f.key = `${i}`;
2268
+ }
2269
+ field.fieldGroup.push(f);
2158
2270
  }
2159
- Object.defineProperty(field, 'get', {
2160
- value: (key) => getField(field, key),
2161
- configurable: true,
2162
- });
2163
- this.getFieldComponentInstance(field).prePopulate?.(field);
2164
2271
  }
2165
- onPopulate(field) {
2166
- this.initFieldOptions(field);
2167
- this.getFieldComponentInstance(field).onPopulate?.(field);
2168
- if (field.fieldGroup) {
2169
- field.fieldGroup.forEach((f, index) => {
2170
- if (f) {
2171
- Object.defineProperty(f, 'parent', { get: () => field, configurable: true });
2172
- Object.defineProperty(f, 'index', { get: () => index, configurable: true });
2173
- }
2174
- this.formId++;
2175
- });
2272
+ add(i, initialModel, { markAsDirty } = { markAsDirty: true }) {
2273
+ markAsDirty && this.formControl.markAsDirty();
2274
+ i = i == null ? this.field.fieldGroup.length : i;
2275
+ if (!this.model) {
2276
+ assignFieldValue(this.field, []);
2176
2277
  }
2278
+ this.model.splice(i, 0, initialModel ? clone(initialModel) : undefined);
2279
+ this.markFieldForCheck(this.field.fieldGroup[i]);
2280
+ this._build();
2177
2281
  }
2178
- postPopulate(field) {
2179
- this.getFieldComponentInstance(field).postPopulate?.(field);
2282
+ remove(i, { markAsDirty } = { markAsDirty: true }) {
2283
+ markAsDirty && this.formControl.markAsDirty();
2284
+ this.model.splice(i, 1);
2285
+ const field = this.field.fieldGroup[i];
2286
+ this.field.fieldGroup.splice(i, 1);
2287
+ this.field.fieldGroup.forEach((f, key) => this.updateArrayElementKey(f, `${key}`));
2288
+ unregisterControl(field, true);
2289
+ this._build();
2180
2290
  }
2181
- initFieldProps(field) {
2182
- field.props ?? (field.props = field.templateOptions);
2183
- Object.defineProperty(field, 'templateOptions', {
2184
- get: () => field.props,
2185
- set: (props) => (field.props = props),
2186
- configurable: true,
2291
+ _build() {
2292
+ const fields = this.field.formControl._fields ?? [this.field];
2293
+ fields.forEach((f) => this.options.build(f));
2294
+ this.options.fieldChanges.next({
2295
+ field: this.field,
2296
+ value: getFieldValue(this.field),
2297
+ type: 'valueChanges',
2187
2298
  });
2188
2299
  }
2189
- initRootOptions(field) {
2190
- if (field.parent) {
2300
+ updateArrayElementKey(f, newKey) {
2301
+ if (hasKey(f)) {
2302
+ f.key = newKey;
2191
2303
  return;
2192
2304
  }
2193
- const options = field.options;
2194
- field.options.formState = field.options.formState || {};
2195
- if (!options.showError) {
2196
- options.showError = this.config.extras.showError;
2197
- }
2198
- if (!options.fieldChanges) {
2199
- defineHiddenProp(options, 'fieldChanges', new Subject());
2305
+ if (!f.fieldGroup?.length) {
2306
+ return;
2200
2307
  }
2201
- if (!options._hiddenFieldsForCheck) {
2202
- options._hiddenFieldsForCheck = [];
2308
+ for (let i = 0; i < f.fieldGroup.length; i++) {
2309
+ this.updateArrayElementKey(f.fieldGroup[i], newKey);
2203
2310
  }
2204
- options._markForCheck = (f) => {
2205
- console.warn(`Formly: 'options._markForCheck' is deprecated since v6.0, use 'options.detectChanges' instead.`);
2206
- options.detectChanges(f);
2207
- };
2208
- options._detectChanges = (f) => {
2209
- if (f._componentRefs) {
2210
- markFieldForCheck(f);
2211
- }
2212
- f.fieldGroup?.forEach((f) => f && options._detectChanges(f));
2213
- };
2214
- options.detectChanges = (f) => {
2215
- f.options.checkExpressions?.(f);
2216
- options._detectChanges(f);
2217
- };
2218
- options.resetModel = (model) => {
2219
- model = clone(model ?? options._initialModel);
2220
- if (field.model) {
2221
- Object.keys(field.model).forEach((k) => delete field.model[k]);
2222
- Object.assign(field.model, model || {});
2223
- }
2224
- if (!isSignalRequired()) {
2225
- observe(options, ['parentForm', 'submitted']).setValue(false, false);
2226
- }
2227
- options.build(field);
2228
- field.form.reset(field.model);
2229
- };
2230
- options.updateInitialValue = (model) => (options._initialModel = clone(model ?? field.model));
2231
- field.options.updateInitialValue();
2232
2311
  }
2233
- initFieldOptions(field) {
2234
- reverseDeepMerge(field, {
2235
- id: getFieldId(`formly_${this.formId}`, field, field.index),
2236
- hooks: {},
2237
- modelOptions: {},
2238
- validation: { messages: {} },
2239
- props: !field.type || !hasKey(field)
2240
- ? {}
2241
- : {
2242
- label: '',
2243
- placeholder: '',
2244
- disabled: false,
2245
- },
2246
- });
2247
- if (this.config.extras.resetFieldOnHide && field.resetOnHide !== false) {
2248
- field.resetOnHide = true;
2249
- }
2250
- if (field.type !== 'formly-template' &&
2251
- (field.template || field.expressions?.template || field.expressionProperties?.template)) {
2252
- field.type = 'formly-template';
2253
- }
2254
- if (!field.type && field.fieldGroup) {
2255
- field.type = 'formly-group';
2256
- }
2257
- if (field.type) {
2258
- this.config.getMergedField(field);
2259
- }
2260
- if (hasKey(field) &&
2261
- !isUndefined(field.defaultValue) &&
2262
- isUndefined(getFieldValue(field)) &&
2263
- !isHiddenField(field)) {
2264
- assignFieldValue(field, field.defaultValue);
2312
+ markFieldForCheck(f) {
2313
+ if (!f) {
2314
+ return;
2265
2315
  }
2266
- field.wrappers = field.wrappers || [];
2267
- }
2268
- getFieldComponentInstance(field) {
2269
- const componentRefInstance = () => {
2270
- let componentRef = this.config.resolveFieldTypeRef(field);
2271
- const fieldComponentRef = field._componentRefs?.slice(-1)[0];
2272
- if (fieldComponentRef instanceof ComponentRef &&
2273
- fieldComponentRef?.componentType === componentRef?.componentType) {
2274
- componentRef = fieldComponentRef;
2275
- }
2276
- return componentRef?.instance;
2277
- };
2278
- if (!field._proxyInstance) {
2279
- defineHiddenProp(field, '_proxyInstance', new Proxy({}, {
2280
- get: (_, prop) => componentRefInstance()?.[prop],
2281
- set: (_, prop, value) => (componentRefInstance()[prop] = value),
2282
- }));
2316
+ f.fieldGroup?.forEach((c) => this.markFieldForCheck(c));
2317
+ if (f.hide === false) {
2318
+ this.options._hiddenFieldsForCheck.push({ field: f });
2283
2319
  }
2284
- return field._proxyInstance;
2285
2320
  }
2321
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FieldArrayType, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
2322
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: FieldArrayType, usesInheritance: true, ngImport: i0 }); }
2286
2323
  }
2324
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FieldArrayType, decorators: [{
2325
+ type: Directive
2326
+ }] });
2287
2327
 
2288
- function defaultFormlyConfig(config) {
2289
- return {
2290
- types: [
2291
- { name: 'formly-group', component: FormlyGroup },
2292
- { name: 'formly-template', component: FormlyTemplateType },
2293
- ],
2294
- extensions: [
2295
- { name: 'core', extension: new CoreExtension(config), priority: -250 },
2296
- { name: 'field-validation', extension: new FieldValidationExtension(config), priority: -200 },
2297
- { name: 'field-form', extension: new FieldFormExtension(), priority: -150 },
2298
- { name: 'field-expression', extension: new FieldExpressionExtension(), priority: -100 },
2299
- ],
2300
- };
2328
+ class FieldWrapper extends FieldType {
2329
+ set _formlyControls(_) { }
2330
+ set _staticContent(content) {
2331
+ this.fieldComponent = content;
2332
+ }
2333
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FieldWrapper, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
2334
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: FieldWrapper, viewQueries: [{ propertyName: "fieldComponent", first: true, predicate: ["fieldComponent"], descendants: true, read: ViewContainerRef }, { propertyName: "_staticContent", first: true, predicate: ["fieldComponent"], descendants: true, read: ViewContainerRef, static: true }], usesInheritance: true, ngImport: i0 }); }
2301
2335
  }
2336
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FieldWrapper, decorators: [{
2337
+ type: Directive
2338
+ }], propDecorators: { fieldComponent: [{
2339
+ type: ViewChild,
2340
+ args: ['fieldComponent', { read: ViewContainerRef }]
2341
+ }], _staticContent: [{
2342
+ type: ViewChild,
2343
+ args: ['fieldComponent', { read: ViewContainerRef, static: true }]
2344
+ }] } });
2345
+
2302
2346
  class FormlyModule {
2303
- constructor(configService, configs = []) {
2304
- if (!configs) {
2305
- return;
2306
- }
2307
- configs.forEach((config) => configService.addConfig(config));
2308
- }
2309
2347
  static forRoot(config = {}) {
2310
2348
  return {
2311
2349
  ngModule: FormlyModule,
2312
2350
  providers: [
2313
- { provide: FORMLY_CONFIG, multi: true, useFactory: defaultFormlyConfig, deps: [FormlyConfig] },
2351
+ { provide: FORMLY_CONFIG, multi: true, useFactory: withDefaultConfig, deps: [FormlyConfig] },
2314
2352
  { provide: FORMLY_CONFIG, useValue: config, multi: true },
2315
2353
  FormlyConfig,
2316
2354
  FormlyFormBuilder,
@@ -2321,43 +2359,50 @@ class FormlyModule {
2321
2359
  return {
2322
2360
  ngModule: FormlyModule,
2323
2361
  providers: [
2324
- { provide: FORMLY_CONFIG, multi: true, useFactory: defaultFormlyConfig, deps: [FormlyConfig] },
2362
+ { provide: FORMLY_CONFIG, multi: true, useFactory: withDefaultConfig, deps: [FormlyConfig] },
2325
2363
  { provide: FORMLY_CONFIG, useValue: config, multi: true },
2326
2364
  FormlyFormBuilder,
2327
2365
  ],
2328
2366
  };
2329
2367
  }
2368
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
2369
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: FormlyModule, declarations: [FormlyTemplate,
2370
+ LegacyFormlyForm,
2371
+ LegacyFormlyField,
2372
+ FormlyAttributes,
2373
+ FormlyGroup,
2374
+ FormlyValidationMessage,
2375
+ FormlyTemplateType], imports: [CommonModule], exports: [FormlyTemplate,
2376
+ LegacyFormlyForm,
2377
+ LegacyFormlyField,
2378
+ FormlyAttributes,
2379
+ FormlyGroup,
2380
+ FormlyValidationMessage] }); }
2381
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyModule, imports: [CommonModule] }); }
2330
2382
  }
2331
- FormlyModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyModule, deps: [{ token: FormlyConfig }, { token: FORMLY_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.NgModule });
2332
- FormlyModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyModule, declarations: [FormlyTemplate,
2333
- FormlyForm,
2334
- FormlyField,
2335
- FormlyAttributes,
2336
- FormlyGroup,
2337
- FormlyValidationMessage,
2338
- FormlyTemplateType], imports: [CommonModule], exports: [FormlyTemplate, FormlyForm, FormlyField, FormlyAttributes, FormlyGroup, FormlyValidationMessage] });
2339
- FormlyModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyModule, imports: [[CommonModule]] });
2340
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyModule, decorators: [{
2383
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FormlyModule, decorators: [{
2341
2384
  type: NgModule,
2342
2385
  args: [{
2343
2386
  declarations: [
2344
2387
  FormlyTemplate,
2345
- FormlyForm,
2346
- FormlyField,
2388
+ LegacyFormlyForm,
2389
+ LegacyFormlyField,
2347
2390
  FormlyAttributes,
2348
2391
  FormlyGroup,
2349
2392
  FormlyValidationMessage,
2350
2393
  FormlyTemplateType,
2351
2394
  ],
2352
- exports: [FormlyTemplate, FormlyForm, FormlyField, FormlyAttributes, FormlyGroup, FormlyValidationMessage],
2395
+ exports: [
2396
+ FormlyTemplate,
2397
+ LegacyFormlyForm,
2398
+ LegacyFormlyField,
2399
+ FormlyAttributes,
2400
+ FormlyGroup,
2401
+ FormlyValidationMessage,
2402
+ ],
2353
2403
  imports: [CommonModule],
2354
2404
  }]
2355
- }], ctorParameters: function () { return [{ type: FormlyConfig }, { type: undefined, decorators: [{
2356
- type: Optional
2357
- }, {
2358
- type: Inject,
2359
- args: [FORMLY_CONFIG]
2360
- }] }]; } });
2405
+ }] });
2361
2406
 
2362
2407
  /*
2363
2408
  * Public API Surface of core
@@ -2367,5 +2412,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
2367
2412
  * Generated bundle index. Do not edit.
2368
2413
  */
2369
2414
 
2370
- export { FORMLY_CONFIG, FieldArrayType, FieldType, FieldWrapper, FormlyConfig, FormlyField, FormlyForm, FormlyFormBuilder, FormlyModule, FormlyAttributes as ɵFormlyAttributes, FormlyGroup as ɵFormlyGroup, FormlyTemplate as ɵFormlyTemplate, FormlyValidationMessage as ɵFormlyValidationMessage, clone as ɵclone, defineHiddenProp as ɵdefineHiddenProp, getFieldValue as ɵgetFieldValue, hasKey as ɵhasKey, observe as ɵobserve, reverseDeepMerge as ɵreverseDeepMerge };
2415
+ export { FORMLY_CONFIG, FieldArrayType, FieldType, FieldWrapper, FormlyConfig, FormlyField, FormlyForm, FormlyFormBuilder, FormlyModule, LegacyFormlyField, LegacyFormlyForm, provideFormlyConfig, provideFormlyCore, FormlyAttributes as ɵFormlyAttributes, FormlyGroup as ɵFormlyGroup, FormlyTemplate as ɵFormlyTemplate, FormlyValidationMessage as ɵFormlyValidationMessage, clone as ɵclone, defineHiddenProp as ɵdefineHiddenProp, getFieldValue as ɵgetFieldValue, hasKey as ɵhasKey, observe as ɵobserve, reverseDeepMerge as ɵreverseDeepMerge };
2371
2416
  //# sourceMappingURL=ngx-formly-core.mjs.map