@ngx-formly/core 6.3.10 → 7.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) 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 +7 -7
  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/index.mjs +2 -0
  15. package/esm2022/lib/models/fieldconfig.cache.mjs +2 -0
  16. package/esm2022/lib/models/fieldconfig.mjs +2 -0
  17. package/esm2022/lib/services/formly.builder.mjs +100 -0
  18. package/esm2022/lib/services/formly.config.mjs +229 -0
  19. package/esm2022/lib/templates/field-array.type.mjs +84 -0
  20. package/{esm2020 → esm2022}/lib/templates/field-template.type.mjs +9 -9
  21. package/esm2022/lib/templates/field.type.mjs +57 -0
  22. package/{esm2020 → esm2022}/lib/templates/field.wrapper.mjs +4 -4
  23. package/esm2022/lib/templates/formly.attributes.mjs +187 -0
  24. package/{esm2020 → esm2022}/lib/templates/formly.group.mjs +8 -8
  25. package/{esm2020 → esm2022}/lib/templates/formly.validation-message.mjs +4 -4
  26. package/esm2022/lib/utils.mjs +312 -0
  27. package/esm2022/preset/preset.config.mjs +11 -0
  28. package/esm2022/preset/preset.module.mjs +18 -0
  29. package/{esm2020 → esm2022}/select/select-options.pipe.mjs +4 -4
  30. package/{esm2020 → esm2022}/select/select.module.mjs +5 -5
  31. package/{esm2020 → esm2022}/testing/component-factory.mjs +5 -4
  32. package/{fesm2020 → fesm2022}/ngx-formly-core-json-schema.mjs +6 -6
  33. package/fesm2022/ngx-formly-core-json-schema.mjs.map +1 -0
  34. package/{fesm2020 → fesm2022}/ngx-formly-core-preset.mjs +14 -19
  35. package/fesm2022/ngx-formly-core-preset.mjs.map +1 -0
  36. package/{fesm2020 → fesm2022}/ngx-formly-core-select.mjs +7 -7
  37. package/{fesm2020 → fesm2022}/ngx-formly-core-select.mjs.map +1 -1
  38. package/{fesm2020 → fesm2022}/ngx-formly-core-testing.mjs +4 -3
  39. package/fesm2022/ngx-formly-core-testing.mjs.map +1 -0
  40. package/{fesm2020 → fesm2022}/ngx-formly-core.mjs +1751 -1707
  41. package/fesm2022/ngx-formly-core.mjs.map +1 -0
  42. package/lib/components/formly.field.d.ts +5 -1
  43. package/lib/components/formly.form.d.ts +8 -4
  44. package/lib/components/formly.template.d.ts +1 -1
  45. package/lib/core.config.d.ts +10 -0
  46. package/lib/core.d.ts +4 -3
  47. package/lib/core.module.d.ts +2 -5
  48. package/lib/extensions/field-expression/utils.d.ts +1 -1
  49. package/lib/extensions/index.d.ts +1 -0
  50. package/lib/models/config.d.ts +1 -1
  51. package/lib/models/fieldconfig.cache.d.ts +3 -3
  52. package/lib/models/fieldconfig.d.ts +8 -7
  53. package/lib/services/formly.builder.d.ts +5 -5
  54. package/lib/services/formly.config.d.ts +2 -6
  55. package/lib/templates/field-array.type.d.ts +3 -3
  56. package/lib/templates/field-template.type.d.ts +1 -1
  57. package/lib/templates/field.type.d.ts +5 -5
  58. package/lib/templates/field.wrapper.d.ts +1 -1
  59. package/lib/templates/formly.attributes.d.ts +1 -1
  60. package/lib/templates/formly.group.d.ts +1 -1
  61. package/lib/templates/formly.validation-message.d.ts +1 -1
  62. package/lib/utils.d.ts +3 -3
  63. package/package.json +23 -37
  64. package/preset/preset.config.d.ts +2 -0
  65. package/select/select-options.pipe.d.ts +1 -1
  66. package/esm2020/json-schema/formly-json-schema.service.mjs +0 -628
  67. package/esm2020/lib/components/formly.field.mjs +0 -284
  68. package/esm2020/lib/components/formly.form.mjs +0 -148
  69. package/esm2020/lib/core.mjs +0 -19
  70. package/esm2020/lib/core.module.mjs +0 -91
  71. package/esm2020/lib/extensions/core/core.mjs +0 -148
  72. package/esm2020/lib/extensions/field-expression/field-expression.mjs +0 -250
  73. package/esm2020/lib/extensions/field-expression/utils.mjs +0 -17
  74. package/esm2020/lib/extensions/field-form/field-form.mjs +0 -141
  75. package/esm2020/lib/extensions/field-form/utils.mjs +0 -100
  76. package/esm2020/lib/models/fieldconfig.cache.mjs +0 -2
  77. package/esm2020/lib/models/fieldconfig.mjs +0 -2
  78. package/esm2020/lib/services/formly.builder.mjs +0 -93
  79. package/esm2020/lib/services/formly.config.mjs +0 -229
  80. package/esm2020/lib/templates/field-array.type.mjs +0 -84
  81. package/esm2020/lib/templates/field.type.mjs +0 -57
  82. package/esm2020/lib/templates/formly.attributes.mjs +0 -187
  83. package/esm2020/lib/utils.mjs +0 -312
  84. package/esm2020/preset/preset.module.mjs +0 -32
  85. package/fesm2015/ngx-formly-core-json-schema.mjs +0 -615
  86. package/fesm2015/ngx-formly-core-json-schema.mjs.map +0 -1
  87. package/fesm2015/ngx-formly-core-preset.mjs +0 -69
  88. package/fesm2015/ngx-formly-core-preset.mjs.map +0 -1
  89. package/fesm2015/ngx-formly-core-select.mjs +0 -120
  90. package/fesm2015/ngx-formly-core-select.mjs.map +0 -1
  91. package/fesm2015/ngx-formly-core-testing.mjs +0 -87
  92. package/fesm2015/ngx-formly-core-testing.mjs.map +0 -1
  93. package/fesm2015/ngx-formly-core.mjs +0 -2418
  94. package/fesm2015/ngx-formly-core.mjs.map +0 -1
  95. package/fesm2020/ngx-formly-core-json-schema.mjs.map +0 -1
  96. package/fesm2020/ngx-formly-core-preset.mjs.map +0 -1
  97. package/fesm2020/ngx-formly-core-testing.mjs.map +0 -1
  98. package/fesm2020/ngx-formly-core.mjs.map +0 -1
  99. package/json-schema/package.json +0 -10
  100. package/preset/package.json +0 -10
  101. package/select/package.json +0 -10
  102. package/testing/package.json +0 -10
  103. /package/{esm2020 → esm2022}/json-schema/ngx-formly-core-json-schema.mjs +0 -0
  104. /package/{esm2020 → esm2022}/json-schema/public_api.mjs +0 -0
  105. /package/{esm2020 → esm2022}/lib/extensions/field-validation/field-validation.mjs +0 -0
  106. /package/{esm2020 → esm2022}/lib/models/config.mjs +0 -0
  107. /package/{esm2020 → esm2022}/lib/models/index.mjs +0 -0
  108. /package/{esm2020 → esm2022}/ngx-formly-core.mjs +0 -0
  109. /package/{esm2020 → esm2022}/preset/ngx-formly-core-preset.mjs +0 -0
  110. /package/{esm2020 → esm2022}/preset/preset-substitution.extension.mjs +0 -0
  111. /package/{esm2020 → esm2022}/preset/public_api.mjs +0 -0
  112. /package/{esm2020 → esm2022}/public_api.mjs +0 -0
  113. /package/{esm2020 → esm2022}/select/ngx-formly-core-select.mjs +0 -0
  114. /package/{esm2020 → esm2022}/select/public_api.mjs +0 -0
  115. /package/{esm2020 → esm2022}/testing/ngx-formly-core-testing.mjs +0 -0
  116. /package/{esm2020 → esm2022}/testing/public_api.mjs +0 -0
  117. /package/{ngx-formly-core.d.ts → index.d.ts} +0 -0
  118. /package/json-schema/{ngx-formly-core-json-schema.d.ts → index.d.ts} +0 -0
  119. /package/preset/{ngx-formly-core-preset.d.ts → index.d.ts} +0 -0
  120. /package/select/{ngx-formly-core-select.d.ts → index.d.ts} +0 -0
  121. /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, VERSION, Directive, ViewChildren, Input, Component, ChangeDetectionStrategy, Injectable, ViewContainerRef, Optional, ViewChild, InjectionToken, Inject, EventEmitter, Output, ContentChildren, 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);
@@ -317,389 +317,93 @@ function isSignalRequired() {
317
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,631 +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;
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
+ }
873
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
- const valueChanges = this.field.options.fieldChanges
1119
- .pipe(filter(({ field, type }) => hasKey(field) && type === 'valueChanges'), switchMap(() => (isNoopNgZone(this.ngZone) ? of(null) : this.ngZone.onStable.asObservable().pipe(take(1)))))
1120
- .subscribe(() => this.ngZone.runGuarded(() => {
1121
- // runGuarded is used to keep in sync the expression changes
1122
- // https://github.com/ngx-formly/ngx-formly/issues/2095
1123
- this.checkExpressionChange();
1124
- if (this.field.options) {
1125
- fieldChangesDetection.push(observeDeep(this.field.options, ['formState'], () => this.field.options.detectChanges(this.field)));
740
+ options._markForCheck = (f) => {
741
+ console.warn(`Formly: 'options._markForCheck' is deprecated since v6.0, use 'options.detectChanges' instead.`);
742
+ options.detectChanges(f);
743
+ };
744
+ options._detectChanges = (f) => {
745
+ if (f._componentRefs) {
746
+ markFieldForCheck(f);
1126
747
  }
1127
- this.modelChange.emit((this._modelChangeValue = clone(this.model)));
1128
- }));
1129
- return () => {
1130
- fieldChangesDetection.forEach((fnc) => fnc());
1131
- formEvents?.unsubscribe();
1132
- valueChanges.unsubscribe();
748
+ f.fieldGroup?.forEach((f) => f && options._detectChanges(f));
1133
749
  };
1134
- }
1135
- setField(field) {
1136
- if (this.config.extras.immutable) {
1137
- this.field = { ...this.field, ...clone(field) };
1138
- }
1139
- else {
1140
- Object.keys(field).forEach((p) => (this.field[p] = field[p]));
1141
- }
1142
- }
1143
- }
1144
- 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 });
1145
- 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 });
1146
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyForm, decorators: [{
1147
- type: Component,
1148
- args: [{
1149
- selector: 'formly-form',
1150
- template: '<formly-field [field]="field"></formly-field>',
1151
- providers: [FormlyFormBuilder, FormlyFieldTemplates],
1152
- changeDetection: ChangeDetectionStrategy.OnPush,
1153
- }]
1154
- }], ctorParameters: function () { return [{ type: FormlyFormBuilder }, { type: FormlyConfig }, { type: i0.NgZone }, { type: FormlyFieldTemplates }]; }, propDecorators: { form: [{
1155
- type: Input
1156
- }], model: [{
1157
- type: Input
1158
- }], fields: [{
1159
- type: Input
1160
- }], options: [{
1161
- type: Input
1162
- }], modelChange: [{
1163
- type: Output
1164
- }], templates: [{
1165
- type: ContentChildren,
1166
- args: [FormlyTemplate]
1167
- }] } });
1168
-
1169
- /**
1170
- * Allow to link the `field` HTML attributes (`id`, `name` ...) and Event attributes (`focus`, `blur` ...) to an element in the DOM.
1171
- */
1172
- class FormlyAttributes {
1173
- constructor(renderer, elementRef, _document) {
1174
- this.renderer = renderer;
1175
- this.elementRef = elementRef;
1176
- this.uiAttributesCache = {};
1177
- /**
1178
- * HostBinding doesn't register listeners conditionally which may produce some perf issues.
1179
- *
1180
- * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1991
1181
- */
1182
- this.uiEvents = {
1183
- listeners: [],
1184
- events: ['click', 'keyup', 'keydown', 'keypress', 'focus', 'blur', 'change'],
1185
- callback: (eventName, $event) => {
1186
- switch (eventName) {
1187
- case 'focus':
1188
- return this.onFocus($event);
1189
- case 'blur':
1190
- return this.onBlur($event);
1191
- case 'change':
1192
- return this.onChange($event);
1193
- default:
1194
- return this.props[eventName](this.field, $event);
1195
- }
1196
- },
750
+ options.detectChanges = (f) => {
751
+ f.options.checkExpressions?.(f);
752
+ options._detectChanges(f);
1197
753
  };
1198
- this.document = _document;
1199
- }
1200
- get props() {
1201
- return this.field.props || {};
1202
- }
1203
- get fieldAttrElements() {
1204
- return this.field?.['_elementRefs'] || [];
1205
- }
1206
- ngOnChanges(changes) {
1207
- if (changes.field) {
1208
- this.field.name && this.setAttribute('name', this.field.name);
1209
- this.uiEvents.listeners.forEach((listener) => listener());
1210
- this.uiEvents.events.forEach((eventName) => {
1211
- if (this.props?.[eventName] || ['focus', 'blur', 'change'].indexOf(eventName) !== -1) {
1212
- this.uiEvents.listeners.push(this.renderer.listen(this.elementRef.nativeElement, eventName, (e) => this.uiEvents.callback(eventName, e)));
1213
- }
1214
- });
1215
- if (this.props?.attributes) {
1216
- observe(this.field, ['props', 'attributes'], ({ currentValue, previousValue }) => {
1217
- if (previousValue) {
1218
- Object.keys(previousValue).forEach((attr) => this.removeAttribute(attr));
1219
- }
1220
- if (currentValue) {
1221
- Object.keys(currentValue).forEach((attr) => {
1222
- if (currentValue[attr] != null) {
1223
- this.setAttribute(attr, currentValue[attr]);
1224
- }
1225
- });
1226
- }
1227
- });
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 || {});
1228
759
  }
1229
- this.detachElementRef(changes.field.previousValue);
1230
- this.attachElementRef(changes.field.currentValue);
1231
- if (this.fieldAttrElements.length === 1) {
1232
- !this.id && this.field.id && this.setAttribute('id', this.field.id);
1233
- this.focusObserver = observe(this.field, ['focus'], ({ currentValue }) => {
1234
- this.toggleFocus(currentValue);
1235
- });
760
+ if (!isSignalRequired()) {
761
+ observe(options, ['parentForm', 'submitted']).setValue(false, false);
1236
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;
1237
785
  }
1238
- if (changes.id) {
1239
- 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';
1240
789
  }
1241
- }
1242
- /**
1243
- * We need to re-evaluate all the attributes on every change detection cycle, because
1244
- * by using a HostBinding we run into certain edge cases. This means that whatever logic
1245
- * is in here has to be super lean or we risk seriously damaging or destroying the performance.
1246
- *
1247
- * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1317
1248
- * Material issue: https://github.com/angular/components/issues/14024
1249
- */
1250
- ngDoCheck() {
1251
- if (!this.uiAttributes) {
1252
- const element = this.elementRef.nativeElement;
1253
- 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';
1254
792
  }
1255
- for (let i = 0; i < this.uiAttributes.length; i++) {
1256
- const attr = this.uiAttributes[i];
1257
- const value = this.props[attr];
1258
- if (this.uiAttributesCache[attr] !== value &&
1259
- (!this.props.attributes || !this.props.attributes.hasOwnProperty(attr.toLowerCase()))) {
1260
- this.uiAttributesCache[attr] = value;
1261
- if (value || value === 0) {
1262
- this.setAttribute(attr, value === true ? attr : `${value}`);
1263
- }
1264
- else {
1265
- this.removeAttribute(attr);
1266
- }
1267
- }
793
+ if (field.type) {
794
+ this.config.getMergedField(field);
1268
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 || [];
1269
803
  }
1270
- ngOnDestroy() {
1271
- this.uiEvents.listeners.forEach((listener) => listener());
1272
- this.detachElementRef(this.field);
1273
- this.focusObserver?.unsubscribe();
1274
- }
1275
- toggleFocus(value) {
1276
- const element = this.fieldAttrElements ? this.fieldAttrElements[0] : null;
1277
- if (!element || !element.nativeElement.focus) {
1278
- 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
+ }));
1279
819
  }
1280
- const isFocused = !!this.document.activeElement &&
1281
- this.fieldAttrElements.some(({ nativeElement }) => this.document.activeElement === nativeElement || nativeElement.contains(this.document.activeElement));
1282
- if (value && !isFocused) {
1283
- 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;
1284
828
  }
1285
- else if (!value && isFocused) {
1286
- Promise.resolve().then(() => element.nativeElement.blur());
829
+ if (field.parent) {
830
+ Object.defineProperty(field, 'form', {
831
+ get: () => field.parent.formControl,
832
+ configurable: true,
833
+ });
1287
834
  }
1288
835
  }
1289
- onFocus($event) {
1290
- this.focusObserver?.setValue(true);
1291
- this.props.focus?.(this.field, $event);
1292
- }
1293
- onBlur($event) {
1294
- this.focusObserver?.setValue(false);
1295
- 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
+ }
1296
843
  }
1297
- // handle custom `change` event, for regular ones rely on DOM listener
1298
- onHostChange($event) {
1299
- if ($event instanceof Event) {
844
+ postPopulate(field) {
845
+ if (this.root !== field) {
1300
846
  return;
1301
847
  }
1302
- this.onChange($event);
1303
- }
1304
- onChange($event) {
1305
- this.props.change?.(this.field, $event);
1306
- 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
+ }
1307
859
  }
1308
- attachElementRef(f) {
1309
- if (!f) {
860
+ addFormControl(field) {
861
+ let control = findControl(field);
862
+ if (field.fieldArray) {
1310
863
  return;
1311
864
  }
1312
- if (f['_elementRefs']?.indexOf(this.elementRef) === -1) {
1313
- 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
+ }
1314
874
  }
1315
875
  else {
1316
- defineHiddenProp(f, '_elementRefs', [this.elementRef]);
1317
- }
1318
- }
1319
- detachElementRef(f) {
1320
- const index = f?.['_elementRefs'] ? this.fieldAttrElements.indexOf(this.elementRef) : -1;
1321
- if (index !== -1) {
1322
- f['_elementRefs'].splice(index, 1);
876
+ if (control instanceof FormControl) {
877
+ const value = hasKey(field) ? getFieldValue(field) : field.defaultValue;
878
+ control.defaultValue = value;
879
+ }
1323
880
  }
881
+ registerControl(field, control);
1324
882
  }
1325
- setAttribute(attr, value) {
1326
- this.renderer.setAttribute(this.elementRef.nativeElement, attr, value);
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
+ }
928
+ }
929
+ return markForCheck;
1327
930
  }
1328
- removeAttribute(attr) {
1329
- this.renderer.removeAttribute(this.elementRef.nativeElement, attr);
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));
940
+ }
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;
1330
1088
  }
1331
1089
  }
1332
- 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 });
1333
- 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 });
1334
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyAttributes, decorators: [{
1335
- type: Directive,
1336
- args: [{
1337
- selector: '[formlyAttributes]',
1338
- host: {
1339
- '(change)': 'onHostChange($event)',
1340
- },
1341
- }]
1342
- }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: undefined, decorators: [{
1343
- type: Inject,
1344
- args: [DOCUMENT]
1345
- }] }]; }, propDecorators: { field: [{
1346
- type: Input,
1347
- args: ['formlyAttributes']
1348
- }], id: [{
1349
- type: Input
1350
- }] } });
1351
1090
 
1352
1091
  class FieldType {
1353
1092
  constructor() {
@@ -1391,10 +1130,10 @@ class FieldType {
1391
1130
  get formState() {
1392
1131
  return this.options?.formState || {};
1393
1132
  }
1133
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FieldType, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1134
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: FieldType, inputs: { field: "field" }, viewQueries: [{ propertyName: "_formlyControls", predicate: NgControl, descendants: true }], ngImport: i0 }); }
1394
1135
  }
1395
- FieldType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldType, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1396
- 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 });
1397
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldType, decorators: [{
1136
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FieldType, decorators: [{
1398
1137
  type: Directive
1399
1138
  }], propDecorators: { _formlyControls: [{
1400
1139
  type: ViewChildren,
@@ -1404,914 +1143,1212 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1404
1143
  }] } });
1405
1144
 
1406
1145
  /** @ignore */
1407
- 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: "16.2.12", ngImport: i0, type: FormlyTemplateType, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
1164
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: FormlyTemplateType, selector: "formly-template", usesInheritance: true, ngImport: i0, template: `<div [innerHtml]="template"></div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1408
1165
  }
1409
- FormlyGroup.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyGroup, deps: null, target: i0.ɵɵFactoryTarget.Component });
1410
- 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: `
1411
- <formly-field *ngFor="let f of field.fieldGroup" [field]="f"></formly-field>
1412
- <ng-content></ng-content>
1413
- `, 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 });
1414
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyGroup, decorators: [{
1166
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyTemplateType, decorators: [{
1415
1167
  type: Component,
1416
1168
  args: [{
1417
- selector: 'formly-group',
1418
- template: `
1419
- <formly-field *ngFor="let f of field.fieldGroup" [field]="f"></formly-field>
1420
- <ng-content></ng-content>
1421
- `,
1422
- host: {
1423
- '[class]': 'field.fieldGroupClassName || ""',
1424
- },
1169
+ selector: 'formly-template',
1170
+ template: `<div [innerHtml]="template"></div>`,
1425
1171
  changeDetection: ChangeDetectionStrategy.OnPush,
1426
1172
  }]
1427
- }] });
1173
+ }], ctorParameters: function () { return [{ type: i1.DomSanitizer }]; } });
1428
1174
 
1429
1175
  /**
1430
- * 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.
1431
1177
  */
1432
- class FormlyValidationMessage {
1433
- constructor(config) {
1434
- this.config = config;
1435
- }
1436
- ngOnChanges() {
1437
- const EXPR_VALIDATORS = FORMLY_VALIDATORS.map((v) => `templateOptions.${v}`);
1438
- this.errorMessage$ = merge(this.field.formControl.statusChanges, !this.field.options
1439
- ? of(null)
1440
- : this.field.options.fieldChanges.pipe(filter(({ field, type, property }) => {
1441
- return (field === this.field &&
1442
- type === 'expressionChanges' &&
1443
- (property.indexOf('validation') !== -1 || EXPR_VALIDATORS.indexOf(property) !== -1));
1444
- }))).pipe(startWith(null), switchMap(() => (isObservable(this.errorMessage) ? this.errorMessage : of(this.errorMessage))));
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 = {};
1445
1197
  }
1446
- get errorMessage() {
1447
- const fieldForm = this.field.formControl;
1448
- for (const error in fieldForm.errors) {
1449
- if (fieldForm.errors.hasOwnProperty(error)) {
1450
- let message = this.config.getValidatorMessage(error);
1451
- if (isObject(fieldForm.errors[error])) {
1452
- if (fieldForm.errors[error].errorPath) {
1453
- return undefined;
1454
- }
1455
- if (fieldForm.errors[error].message) {
1456
- message = fieldForm.errors[error].message;
1457
- }
1458
- }
1459
- if (this.field.validation?.messages?.[error]) {
1460
- message = this.field.validation.messages[error];
1461
- }
1462
- if (this.field.validators?.[error]?.message) {
1463
- message = this.field.validators[error].message;
1464
- }
1465
- if (this.field.asyncValidators?.[error]?.message) {
1466
- message = this.field.asyncValidators[error].message;
1467
- }
1468
- if (typeof message === 'function') {
1469
- return message(fieldForm.errors[error], this.field);
1470
- }
1471
- return message;
1472
- }
1198
+ addConfig(config) {
1199
+ if (Array.isArray(config)) {
1200
+ config.forEach((c) => this.addConfig(c));
1201
+ return;
1473
1202
  }
1474
- return undefined;
1475
- }
1476
- }
1477
- FormlyValidationMessage.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyValidationMessage, deps: [{ token: FormlyConfig }], target: i0.ɵɵFactoryTarget.Component });
1478
- 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 });
1479
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyValidationMessage, decorators: [{
1480
- type: Component,
1481
- args: [{
1482
- selector: 'formly-validation-message',
1483
- template: '{{ errorMessage$ | async }}',
1484
- changeDetection: ChangeDetectionStrategy.OnPush,
1485
- }]
1486
- }], ctorParameters: function () { return [{ type: FormlyConfig }]; }, propDecorators: { field: [{
1487
- type: Input
1488
- }] } });
1489
-
1490
- class FieldArrayType extends FieldType {
1491
- onPopulate(field) {
1492
- if (hasKey(field)) {
1493
- const control = findControl(field);
1494
- registerControl(field, control ? control : new FormArray([], { updateOn: field.modelOptions.updateOn }));
1203
+ if (config.types) {
1204
+ config.types.forEach((type) => this.setType(type));
1495
1205
  }
1496
- field.fieldGroup = field.fieldGroup || [];
1497
- const length = Array.isArray(field.model) ? field.model.length : 0;
1498
- if (field.fieldGroup.length > length) {
1499
- for (let i = field.fieldGroup.length - 1; i >= length; --i) {
1500
- unregisterControl(field.fieldGroup[i], true);
1501
- field.fieldGroup.splice(i, 1);
1502
- }
1206
+ if (config.validators) {
1207
+ config.validators.forEach((validator) => this.setValidator(validator));
1503
1208
  }
1504
- for (let i = field.fieldGroup.length; i < length; i++) {
1505
- const f = { ...clone(typeof field.fieldArray === 'function' ? field.fieldArray(field) : field.fieldArray) };
1506
- if (f.key !== null) {
1507
- f.key = `${i}`;
1508
- }
1509
- field.fieldGroup.push(f);
1209
+ if (config.wrappers) {
1210
+ config.wrappers.forEach((wrapper) => this.setWrapper(wrapper));
1510
1211
  }
1511
- }
1512
- add(i, initialModel, { markAsDirty } = { markAsDirty: true }) {
1513
- markAsDirty && this.formControl.markAsDirty();
1514
- i = i == null ? this.field.fieldGroup.length : i;
1515
- if (!this.model) {
1516
- assignFieldValue(this.field, []);
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
+ };
1517
1226
  }
1518
- this.model.splice(i, 0, initialModel ? clone(initialModel) : undefined);
1519
- this.markFieldForCheck(this.field.fieldGroup[i]);
1520
- this._build();
1521
1227
  }
1522
- remove(i, { markAsDirty } = { markAsDirty: true }) {
1523
- markAsDirty && this.formControl.markAsDirty();
1524
- this.model.splice(i, 1);
1525
- const field = this.field.fieldGroup[i];
1526
- this.field.fieldGroup.splice(i, 1);
1527
- this.field.fieldGroup.forEach((f, key) => this.updateArrayElementKey(f, `${key}`));
1528
- unregisterControl(field, true);
1529
- this._build();
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 };
1239
+ }
1240
+ ['component', 'extends', 'defaultOptions', 'wrappers'].forEach((prop) => {
1241
+ if (options.hasOwnProperty(prop)) {
1242
+ this.types[options.name][prop] = options[prop];
1243
+ }
1244
+ });
1245
+ }
1530
1246
  }
1531
- _build() {
1532
- const fields = this.field.formControl._fields ?? [this.field];
1533
- fields.forEach((f) => this.options.build(f));
1534
- this.options.fieldChanges.next({
1535
- field: this.field,
1536
- value: getFieldValue(this.field),
1537
- type: 'valueChanges',
1538
- });
1247
+ getType(name, throwIfNotFound = false) {
1248
+ if (name instanceof Type) {
1249
+ return { component: name, name: name.prototype.constructor.name };
1250
+ }
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.`);
1254
+ }
1255
+ return null;
1256
+ }
1257
+ this.mergeExtendedType(name);
1258
+ return this.types[name];
1539
1259
  }
1540
- updateArrayElementKey(f, newKey) {
1541
- if (hasKey(f)) {
1542
- f.key = newKey;
1260
+ /** @ignore */
1261
+ getMergedField(field = {}) {
1262
+ const type = this.getType(field.type);
1263
+ if (!type) {
1543
1264
  return;
1544
1265
  }
1545
- if (!f.fieldGroup?.length) {
1546
- return;
1266
+ if (type.defaultOptions) {
1267
+ reverseDeepMerge(field, type.defaultOptions);
1547
1268
  }
1548
- for (let i = 0; i < f.fieldGroup.length; i++) {
1549
- this.updateArrayElementKey(f.fieldGroup[i], newKey);
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];
1550
1287
  }
1551
1288
  }
1552
- markFieldForCheck(f) {
1553
- if (!f) {
1554
- return;
1289
+ /** @ignore @internal */
1290
+ resolveFieldTypeRef(field = {}) {
1291
+ const type = this.getType(field.type);
1292
+ if (!type) {
1293
+ return null;
1555
1294
  }
1556
- f.fieldGroup?.forEach((c) => this.markFieldForCheck(c));
1557
- if (f.hide === false) {
1558
- this.options._hiddenFieldsForCheck.push({ field: f });
1295
+ if (!type.component || type._componentRef) {
1296
+ return type._componentRef;
1297
+ }
1298
+ const { _viewContainerRef, _injector } = field.options;
1299
+ if (!_viewContainerRef || !_injector) {
1300
+ return null;
1559
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;
1560
1311
  }
1561
- }
1562
- FieldArrayType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldArrayType, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1563
- FieldArrayType.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FieldArrayType, usesInheritance: true, ngImport: i0 });
1564
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldArrayType, decorators: [{
1565
- type: Directive
1566
- }] });
1567
-
1568
- class FieldWrapper extends FieldType {
1569
- set _formlyControls(_) { }
1570
- set _staticContent(content) {
1571
- this.fieldComponent = content;
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
+ }
1572
1319
  }
1573
- }
1574
- FieldWrapper.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldWrapper, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1575
- 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 });
1576
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldWrapper, decorators: [{
1577
- type: Directive
1578
- }], propDecorators: { fieldComponent: [{
1579
- type: ViewChild,
1580
- args: ['fieldComponent', { read: ViewContainerRef }]
1581
- }], _staticContent: [{
1582
- type: ViewChild,
1583
- args: ['fieldComponent', { read: ViewContainerRef, static: true }]
1584
- }] } });
1585
-
1586
- /** @ignore */
1587
- class FormlyTemplateType extends FieldType {
1588
- constructor(sanitizer) {
1589
- super();
1590
- this.sanitizer = sanitizer;
1591
- this.innerHtml = {};
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];
1592
1328
  }
1593
- get template() {
1594
- if (this.field && this.field.template !== this.innerHtml.template) {
1595
- this.innerHtml = {
1596
- template: this.field.template,
1597
- content: this.props.safeHtml
1598
- ? this.sanitizer.bypassSecurityTrustHtml(this.field.template)
1599
- : this.field.template,
1329
+ /** @ignore */
1330
+ setTypeWrapper(type, name) {
1331
+ if (!this.types[type]) {
1332
+ this.types[type] = {};
1333
+ }
1334
+ if (!this.types[type].wrappers) {
1335
+ this.types[type].wrappers = [];
1336
+ }
1337
+ if (this.types[type].wrappers.indexOf(name) === -1) {
1338
+ this.types[type].wrappers.push(name);
1339
+ }
1340
+ }
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.`);
1347
+ }
1348
+ return this.validators[name];
1349
+ }
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,
1600
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;
1601
1391
  }
1602
- return this.innerHtml.content;
1603
1392
  }
1393
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1394
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyConfig, providedIn: 'root' }); }
1604
1395
  }
1605
- FormlyTemplateType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplateType, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
1606
- 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 });
1607
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplateType, decorators: [{
1608
- type: Component,
1609
- args: [{
1610
- selector: 'formly-template',
1611
- template: `<div [innerHtml]="template"></div>`,
1612
- changeDetection: ChangeDetectionStrategy.OnPush,
1613
- }]
1614
- }], ctorParameters: function () { return [{ type: i1.DomSanitizer }]; } });
1396
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyConfig, decorators: [{
1397
+ type: Injectable,
1398
+ args: [{ providedIn: 'root' }]
1399
+ }] });
1615
1400
 
1616
- function evalStringExpression(expression, argNames) {
1617
- try {
1618
- return Function(...argNames, `return ${expression};`);
1401
+ class FormlyTemplate {
1402
+ constructor(ref) {
1403
+ this.ref = ref;
1619
1404
  }
1620
- catch (error) {
1621
- console.error(error);
1405
+ ngOnChanges() {
1406
+ this.name = this.name || 'formly-group';
1622
1407
  }
1408
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
1409
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: FormlyTemplate, selector: "[formlyTemplate]", inputs: { name: ["formlyTemplate", "name"] }, usesOnChanges: true, ngImport: i0 }); }
1623
1410
  }
1624
- function evalExpression(expression, thisArg, argVal) {
1625
- if (typeof expression === 'function') {
1626
- return expression.apply(thisArg, argVal);
1627
- }
1628
- else {
1629
- return expression ? true : false;
1630
- }
1411
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyTemplate, decorators: [{
1412
+ type: Directive,
1413
+ args: [{ selector: '[formlyTemplate]' }]
1414
+ }], ctorParameters: function () { return [{ type: i0.TemplateRef }]; }, propDecorators: { name: [{
1415
+ type: Input,
1416
+ args: ['formlyTemplate']
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: "16.2.12", ngImport: i0, type: FormlyFieldTemplates, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1421
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyFieldTemplates }); }
1631
1422
  }
1423
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyFieldTemplates, decorators: [{
1424
+ type: Injectable
1425
+ }] });
1632
1426
 
1633
- class FieldExpressionExtension {
1634
- onPopulate(field) {
1635
- if (field._expressions) {
1636
- return;
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;
1433
+ }
1434
+ get elementRef() {
1435
+ if (this.config.extras.renderFormlyFieldElement) {
1436
+ return this._elementRef;
1637
1437
  }
1638
- // cache built expression
1639
- defineHiddenProp(field, '_expressions', {});
1640
- observe(field, ['hide'], ({ currentValue, firstChange }) => {
1641
- defineHiddenProp(field, '_hide', !!currentValue);
1642
- if (!firstChange || (firstChange && currentValue === true)) {
1643
- field.props.hidden = currentValue;
1644
- field.options._hiddenFieldsForCheck.push({ field });
1645
- }
1646
- });
1647
- if (field.hideExpression) {
1648
- observe(field, ['hideExpression'], ({ currentValue: expr }) => {
1649
- field._expressions.hide = this.parseExpressions(field, 'hide', typeof expr === 'boolean' ? () => expr : expr);
1438
+ if (this.componentRefs?.[0] instanceof ComponentRef) {
1439
+ return this.componentRefs[0].location;
1440
+ }
1441
+ return null;
1442
+ }
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 = () => { };
1454
+ }
1455
+ ngAfterContentInit() {
1456
+ this.triggerHook('afterContentInit');
1457
+ }
1458
+ ngAfterViewInit() {
1459
+ this.triggerHook('afterViewInit');
1460
+ }
1461
+ ngDoCheck() {
1462
+ if (this.detectFieldBuild && this.field && this.field.options) {
1463
+ this.render();
1464
+ }
1465
+ }
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;
1484
+ }
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
+ }
1650
1504
  });
1651
1505
  }
1652
- const evalExpr = (key, expr) => {
1653
- if (typeof expr === 'string' || isFunction(expr)) {
1654
- 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 });
1655
1511
  }
1656
- else if (expr instanceof Observable) {
1657
- field._expressions[key] = {
1658
- value$: expr.pipe(tap((v) => {
1659
- this.evalExpr(field, key, v);
1660
- field.options._detectChanges(field);
1661
- })),
1662
- };
1512
+ else {
1513
+ const { component } = this.config.getType(f.type, true);
1514
+ ref = containerRef.createComponent(component);
1663
1515
  }
1664
- };
1665
- field.expressions = field.expressions || {};
1666
- for (const key of Object.keys(field.expressions)) {
1667
- observe(field, ['expressions', key], ({ currentValue: expr }) => {
1668
- evalExpr(key, isFunction(expr) ? (...args) => expr(field, args[3]) : expr);
1669
- });
1670
- }
1671
- field.expressionProperties = field.expressionProperties || {};
1672
- for (const key of Object.keys(field.expressionProperties)) {
1673
- observe(field, ['expressionProperties', key], ({ currentValue }) => evalExpr(key, currentValue));
1516
+ this.attachComponentRef(ref, f);
1674
1517
  }
1675
1518
  }
1676
- postPopulate(field) {
1677
- if (field.parent) {
1678
- return;
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);
1679
1523
  }
1680
- if (!field.options.checkExpressions) {
1681
- let checkLocked = false;
1682
- field.options.checkExpressions = (f, ignoreCache) => {
1683
- if (checkLocked) {
1684
- return;
1685
- }
1686
- checkLocked = true;
1687
- const fieldChanged = this.checkExpressions(f, ignoreCache);
1688
- const options = field.options;
1689
- options._hiddenFieldsForCheck
1690
- .sort((f) => (f.field.hide ? -1 : 1))
1691
- .forEach((f) => this.changeHideState(f.field, f.field.hide ?? f.default, !ignoreCache));
1692
- options._hiddenFieldsForCheck = [];
1693
- if (fieldChanged) {
1694
- this.checkExpressions(field);
1695
- }
1696
- checkLocked = false;
1697
- };
1698
- field.options._checkField = (f, ignoreCache) => {
1699
- console.warn(`Formly: 'options._checkField' is deprecated since v6.0, use 'options.checkExpressions' instead.`);
1700
- field.options.checkExpressions(f, ignoreCache);
1701
- };
1702
- }
1703
- }
1704
- parseExpressions(field, path, expr) {
1705
- let parentExpression;
1706
- if (field.parent && ['hide', 'props.disabled'].includes(path)) {
1707
- const rootValue = (f) => {
1708
- return path === 'hide' ? f.hide : f.props.disabled;
1709
- };
1710
- parentExpression = () => {
1711
- let root = field.parent;
1712
- while (root.parent && !rootValue(root)) {
1713
- root = root.parent;
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());
1714
1530
  }
1715
- return rootValue(root);
1716
- };
1531
+ }
1717
1532
  }
1718
- expr = expr || (() => false);
1719
- if (typeof expr === 'string') {
1720
- expr = evalStringExpression(expr, ['model', 'formState', 'field']);
1533
+ if (name === 'onChanges' && changes.field) {
1534
+ this.resetRefs(changes.field.previousValue);
1535
+ this.render();
1721
1536
  }
1722
- let currentValue;
1723
- return {
1724
- callback: (ignoreCache) => {
1725
- try {
1726
- const exprValue = evalExpression(parentExpression ? (...args) => parentExpression(field) || expr(...args) : expr, { field }, [field.model, field.options.formState, field, ignoreCache]);
1727
- if (ignoreCache ||
1728
- (currentValue !== exprValue &&
1729
- (!isObject(exprValue) ||
1730
- isObservable(exprValue) ||
1731
- JSON.stringify(exprValue) !== JSON.stringify(currentValue)))) {
1732
- currentValue = exprValue;
1733
- this.evalExpr(field, path, exprValue);
1734
- return true;
1735
- }
1736
- return false;
1737
- }
1738
- catch (error) {
1739
- error.message = `[Formly Error] [Expression "${path}"] ${error.message}`;
1740
- throw error;
1741
- }
1742
- },
1743
- };
1744
1537
  }
1745
- checkExpressions(field, ignoreCache = false) {
1746
- if (!field) {
1747
- return false;
1748
- }
1749
- let fieldChanged = false;
1750
- if (field._expressions) {
1751
- for (const key of Object.keys(field._expressions)) {
1752
- field._expressions[key].callback?.(ignoreCache) && (fieldChanged = true);
1753
- }
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 });
1754
1543
  }
1755
- field.fieldGroup?.forEach((f) => this.checkExpressions(f, ignoreCache) && (fieldChanged = true));
1756
- return fieldChanged;
1757
1544
  }
1758
- changeDisabledState(field, value) {
1759
- if (field.fieldGroup) {
1760
- field.fieldGroup
1761
- .filter((f) => !f._expressions.hasOwnProperty('props.disabled'))
1762
- .forEach((f) => this.changeDisabledState(f, value));
1763
- }
1764
- if (hasKey(field) && field.props.disabled !== value) {
1765
- field.props.disabled = value;
1545
+ render() {
1546
+ if (!this.field) {
1547
+ return;
1766
1548
  }
1767
- }
1768
- changeHideState(field, hide, resetOnHide) {
1769
- if (field.fieldGroup) {
1770
- field.fieldGroup
1771
- .filter((f) => f && !f._expressions.hide)
1772
- .forEach((f) => this.changeHideState(f, hide, resetOnHide));
1549
+ // require Formly build
1550
+ if (!this.field.options) {
1551
+ this.detectFieldBuild = true;
1552
+ return;
1773
1553
  }
1774
- if (field.formControl && hasKey(field)) {
1775
- defineHiddenProp(field, '_hide', !!(hide || field.hide));
1776
- const c = field.formControl;
1777
- if (c._fields?.length > 1) {
1778
- updateValidity(c);
1779
- }
1780
- if (hide === true && (!c._fields || c._fields.every((f) => !!f._hide))) {
1781
- unregisterControl(field, true);
1782
- if (resetOnHide && field.resetOnHide) {
1783
- assignFieldValue(field, undefined);
1784
- field.formControl.reset({ value: undefined, disabled: field.formControl.disabled });
1785
- field.options.fieldChanges.next({ value: undefined, field, type: 'valueChanges' });
1786
- if (field.fieldGroup && field.formControl instanceof FormArray) {
1787
- field.fieldGroup.length = 0;
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' : '');
1788
1564
  }
1789
1565
  }
1790
- }
1791
- else if (hide === false) {
1792
- if (field.resetOnHide && !isUndefined(field.defaultValue) && isUndefined(getFieldValue(field))) {
1793
- assignFieldValue(field, field.defaultValue);
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
+ }
1578
+ }
1794
1579
  }
1795
- registerControl(field, undefined, true);
1796
- if (field.resetOnHide && field.fieldArray && field.fieldGroup?.length !== field.model?.length) {
1797
- field.options.build(field);
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);
1798
1586
  }
1799
- }
1587
+ }),
1588
+ ];
1589
+ if (!isSignalRequired()) {
1590
+ ['touched', 'pristine', 'status'].forEach((prop) => this.hostObservers.push(observe(this.field, ['formControl', prop], ({ firstChange }) => !firstChange && markFieldForCheck(this.field))));
1800
1591
  }
1801
- if (field.options.fieldChanges) {
1802
- field.options.fieldChanges.next({ field, type: 'hidden', value: hide });
1592
+ else if (this.field.formControl) {
1593
+ const events = this.field.formControl.events.subscribe(() => markFieldForCheck(this.field));
1594
+ this.hostObservers.push(events);
1803
1595
  }
1804
1596
  }
1805
- evalExpr(field, prop, value) {
1806
- if (prop.indexOf('model.') === 0) {
1807
- const key = prop.replace(/^model\./, ''), parent = field.fieldGroup ? field : field.parent;
1808
- let control = field?.key === key ? field.formControl : field.form.get(key);
1809
- if (!control && field.get(key)) {
1810
- control = field.get(key).formControl;
1811
- }
1812
- assignFieldValue({ key, parent, model: field.model }, value);
1813
- if (control && !(isNil(control.value) && isNil(value)) && control.value !== value) {
1814
- control.patchValue(value);
1597
+ resetRefs(field) {
1598
+ if (field) {
1599
+ if (field._localFields) {
1600
+ field._localFields = [];
1815
1601
  }
1816
- }
1817
- else {
1818
- try {
1819
- let target = field;
1820
- const paths = this._evalExpressionPath(field, prop);
1821
- const lastIndex = paths.length - 1;
1822
- for (let i = 0; i < lastIndex; i++) {
1823
- target = target[paths[i]];
1824
- }
1825
- target[paths[lastIndex]] = value;
1602
+ else {
1603
+ defineHiddenProp(this.field, '_localFields', []);
1826
1604
  }
1827
- catch (error) {
1828
- error.message = `[Formly Error] [Expression "${prop}"] ${error.message}`;
1829
- throw error;
1605
+ if (field._componentRefs) {
1606
+ field._componentRefs = field._componentRefs.filter((ref) => this.componentRefs.indexOf(ref) === -1);
1830
1607
  }
1831
- if (['templateOptions.disabled', 'props.disabled'].includes(prop) && hasKey(field)) {
1832
- this.changeDisabledState(field, value);
1608
+ else {
1609
+ defineHiddenProp(this.field, '_componentRefs', []);
1833
1610
  }
1834
1611
  }
1835
- this.emitExpressionChanges(field, prop, value);
1836
- }
1837
- emitExpressionChanges(field, property, value) {
1838
- if (!field.options.fieldChanges) {
1839
- return;
1840
- }
1841
- field.options.fieldChanges.next({
1842
- field,
1843
- type: 'expressionChanges',
1844
- property,
1845
- value,
1846
- });
1612
+ this.componentRefs = [];
1847
1613
  }
1848
- _evalExpressionPath(field, prop) {
1849
- if (field._expressions[prop] && field._expressions[prop].paths) {
1850
- return field._expressions[prop].paths;
1851
- }
1852
- let paths = [];
1853
- if (prop.indexOf('[') === -1) {
1854
- paths = prop.split('.');
1614
+ fieldChanges(field) {
1615
+ if (!field) {
1616
+ return () => { };
1855
1617
  }
1856
- else {
1857
- prop
1858
- .split(/[[\]]{1,2}/) // https://stackoverflow.com/a/20198206
1859
- .filter((p) => p)
1860
- .forEach((path) => {
1861
- const arrayPath = path.match(/['|"](.*?)['|"]/);
1862
- if (arrayPath) {
1863
- paths.push(arrayPath[1]);
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;
1864
1629
  }
1865
- else {
1866
- paths.push(...path.split('.').filter((p) => p));
1630
+ if (isObservable(currentValue.value$)) {
1631
+ currentValue.subscription = currentValue.value$.subscribe();
1632
+ }
1633
+ });
1634
+ subscribes.push(() => {
1635
+ if (field._expressions[key]?.subscription) {
1636
+ field._expressions[key].subscription.unsubscribe();
1867
1637
  }
1638
+ expressionObserver.unsubscribe();
1868
1639
  });
1869
1640
  }
1870
- if (field._expressions[prop]) {
1871
- field._expressions[prop].paths = paths;
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());
1872
1644
  }
1873
- return paths;
1874
- }
1875
- }
1876
-
1877
- class FieldValidationExtension {
1878
- constructor(config) {
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);
1651
+ }
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));
1661
+ }
1662
+ const { updateOn, debounce } = field.modelOptions;
1663
+ if ((!updateOn || updateOn === 'change') && debounce?.default > 0) {
1664
+ valueChanges = control.valueChanges.pipe(debounceTime(debounce.default));
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());
1677
+ }
1678
+ let templateFieldsSubs = [];
1679
+ observe(field, ['_localFields'], ({ currentValue }) => {
1680
+ templateFieldsSubs.forEach((unsubscribe) => unsubscribe());
1681
+ templateFieldsSubs = (currentValue || []).map((f) => this.fieldChanges(f));
1682
+ });
1683
+ return () => {
1684
+ subscribes.forEach((unsubscribe) => unsubscribe());
1685
+ templateFieldsSubs.forEach((unsubscribe) => unsubscribe());
1686
+ };
1687
+ }
1688
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.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 }); }
1689
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", 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: "16.2.12", 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: function () { return [{ 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: "16.2.12", ngImport: i0, type: LegacyFormlyField, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1704
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", 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: "16.2.12", 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: "16.2.12", ngImport: i0, type: FormlyGroup, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1714
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", 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: "16.2.12", 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 = []) {
1879
1764
  this.config = config;
1765
+ this.injector = injector;
1766
+ this.viewContainerRef = viewContainerRef;
1767
+ this.parentForm = parentForm;
1768
+ configs.forEach((c) => config.addConfig(c));
1880
1769
  }
1881
- onPopulate(field) {
1882
- this.initFieldValidation(field, 'validators');
1883
- this.initFieldValidation(field, 'asyncValidators');
1770
+ buildForm(form, fieldGroup = [], model, options) {
1771
+ this.build({ fieldGroup, model, form, options });
1884
1772
  }
1885
- initFieldValidation(field, type) {
1886
- const validators = [];
1887
- if (type === 'validators' && !(field.hasOwnProperty('fieldGroup') && !hasKey(field))) {
1888
- validators.push(this.getPredefinedFieldValidation(field));
1773
+ build(field) {
1774
+ if (!this.config.extensions.core) {
1775
+ throw new Error('NgxFormly: missing `forRoot()` call. use `forRoot()` when registering the `FormlyModule`.');
1889
1776
  }
1890
- if (field[type]) {
1891
- for (const validatorName of Object.keys(field[type])) {
1892
- validatorName === 'validation'
1893
- ? validators.push(...field[type].validation.map((v) => this.wrapNgValidatorFn(field, v)))
1894
- : validators.push(this.wrapNgValidatorFn(field, field[type][validatorName], validatorName));
1895
- }
1777
+ if (!field.parent) {
1778
+ this._setOptions(field);
1896
1779
  }
1897
- defineHiddenProp(field, '_' + type, validators);
1898
- }
1899
- getPredefinedFieldValidation(field) {
1900
- let VALIDATORS = [];
1901
- FORMLY_VALIDATORS.forEach((opt) => observe(field, ['props', opt], ({ currentValue, firstChange }) => {
1902
- VALIDATORS = VALIDATORS.filter((o) => o !== opt);
1903
- if (opt === 'required' && currentValue != null && typeof currentValue !== 'boolean') {
1904
- console.warn(`Formly: Invalid prop 'required' of type '${typeof currentValue}', expected 'boolean' (Field:${field.key}).`);
1905
- }
1906
- if (currentValue != null && currentValue !== false) {
1907
- VALIDATORS.push(opt);
1908
- }
1909
- if (!firstChange && field.formControl) {
1910
- updateValidity(field.formControl);
1911
- }
1912
- }));
1913
- return (control) => {
1914
- if (VALIDATORS.length === 0) {
1915
- return null;
1916
- }
1917
- return Validators.compose(VALIDATORS.map((opt) => () => {
1918
- const value = field.props[opt];
1919
- switch (opt) {
1920
- case 'required':
1921
- return Validators.required(control);
1922
- case 'pattern':
1923
- return Validators.pattern(value)(control);
1924
- case 'minLength':
1925
- const minLengthResult = Validators.minLength(value)(control);
1926
- const minLengthKey = this.config.getValidatorMessage('minlength') || field.validation?.messages?.minlength
1927
- ? 'minlength'
1928
- : 'minLength';
1929
- return minLengthResult ? { [minLengthKey]: minLengthResult.minlength } : null;
1930
- case 'maxLength':
1931
- const maxLengthResult = Validators.maxLength(value)(control);
1932
- const maxLengthKey = this.config.getValidatorMessage('maxlength') || field.validation?.messages?.maxlength
1933
- ? 'maxlength'
1934
- : 'maxLength';
1935
- return maxLengthResult ? { [maxLengthKey]: maxLengthResult.maxlength } : null;
1936
- case 'min':
1937
- return Validators.min(value)(control);
1938
- case 'max':
1939
- return Validators.max(value)(control);
1940
- default:
1941
- return null;
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 });
1942
1790
  }
1943
- }))(control);
1944
- };
1791
+ options.checkExpressions?.(field, true);
1792
+ options._detectChanges?.(field);
1793
+ }
1794
+ });
1945
1795
  }
1946
- wrapNgValidatorFn(field, validator, validatorName) {
1947
- let validatorOption;
1948
- if (typeof validator === 'string') {
1949
- validatorOption = clone(this.config.getValidator(validator));
1796
+ _build(field) {
1797
+ if (!field) {
1798
+ return;
1950
1799
  }
1951
- if (typeof validator === 'object' && validator.name) {
1952
- validatorOption = clone(this.config.getValidator(validator.name));
1953
- if (validator.options) {
1954
- validatorOption.options = validator.options;
1955
- }
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);
1956
1813
  }
1957
- if (typeof validator === 'object' && validator.expression) {
1958
- const { expression, ...options } = validator;
1959
- validatorOption = {
1960
- name: validatorName,
1961
- validation: expression,
1962
- options: Object.keys(options).length > 0 ? options : null,
1963
- };
1814
+ if (!options._injector) {
1815
+ defineHiddenProp(options, '_injector', this.injector);
1964
1816
  }
1965
- if (typeof validator === 'function') {
1966
- validatorOption = {
1967
- name: validatorName,
1968
- validation: validator,
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;
1969
1825
  };
1970
1826
  }
1971
- return (control) => {
1972
- const errors = validatorOption.validation(control, field, validatorOption.options);
1973
- if (isPromise(errors)) {
1974
- return errors.then((v) => this.handleResult(field, validatorName ? !!v : v, validatorOption));
1975
- }
1976
- if (isObservable(errors)) {
1977
- return errors.pipe(map((v) => this.handleResult(field, validatorName ? !!v : v, validatorOption)));
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
+ });
1978
1835
  }
1979
- return this.handleResult(field, validatorName ? !!errors : errors, validatorOption);
1836
+ }
1837
+ }
1838
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", 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: "16.2.12", ngImport: i0, type: FormlyFormBuilder, providedIn: 'root' }); }
1840
+ }
1841
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyFormBuilder, decorators: [{
1842
+ type: Injectable,
1843
+ args: [{ providedIn: 'root' }]
1844
+ }], ctorParameters: function () { return [{ 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
+ }] }]; } });
1854
+
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;
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 = () => { };
1905
+ }
1906
+ ngDoCheck() {
1907
+ if (this.config.extras.checkExpressionOn === 'changeDetectionCheck') {
1908
+ this.checkExpressionChange();
1909
+ }
1910
+ }
1911
+ ngOnChanges(changes) {
1912
+ if (changes.fields && this.form) {
1913
+ clearControl(this.form);
1914
+ }
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();
1919
+ }
1920
+ }
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;
1936
+ }
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();
1980
1954
  };
1981
1955
  }
1982
- handleResult(field, errors, { name, options }) {
1983
- if (typeof errors === 'boolean') {
1984
- errors = errors ? null : { [name]: options ? options : true };
1956
+ setField(field) {
1957
+ if (this.config.extras.immutable) {
1958
+ this.field = { ...this.field, ...clone(field) };
1985
1959
  }
1986
- const ctrl = field.formControl;
1987
- ctrl?._childrenErrors?.[name]?.();
1988
- if (isObject(errors)) {
1989
- Object.keys(errors).forEach((name) => {
1990
- const errorPath = errors[name].errorPath ? errors[name].errorPath : options?.errorPath;
1991
- const childCtrl = errorPath ? field.formControl.get(errorPath) : null;
1992
- if (childCtrl) {
1993
- const { errorPath: _errorPath, ...opts } = errors[name];
1994
- childCtrl.setErrors({ ...(childCtrl.errors || {}), [name]: opts });
1995
- !ctrl._childrenErrors && defineHiddenProp(ctrl, '_childrenErrors', {});
1996
- ctrl._childrenErrors[name] = () => {
1997
- const { [name]: _toDelete, ...childErrors } = childCtrl.errors || {};
1998
- childCtrl.setErrors(Object.keys(childErrors).length === 0 ? null : childErrors);
1999
- };
1960
+ else {
1961
+ Object.keys(field).forEach((p) => (this.field[p] = field[p]));
1962
+ }
1963
+ }
1964
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", 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: "16.2.12", 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: "16.2.12", 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: function () { return [{ 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: "16.2.12", ngImport: i0, type: LegacyFormlyForm, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1993
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", 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: "16.2.12", 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)));
2000
2050
  }
2001
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
+ });
2065
+ }
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
+ });
2073
+ }
2074
+ }
2075
+ if (changes.id) {
2076
+ this.setAttribute('id', this.id);
2002
2077
  }
2003
- return errors;
2004
2078
  }
2005
- }
2006
-
2007
- class FieldFormExtension {
2008
- prePopulate(field) {
2009
- if (!this.root) {
2010
- 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));
2011
2091
  }
2012
- if (field.parent) {
2013
- Object.defineProperty(field, 'form', {
2014
- get: () => field.parent.formControl,
2015
- configurable: true,
2016
- });
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
+ }
2017
2105
  }
2018
2106
  }
2019
- onPopulate(field) {
2020
- if (field.hasOwnProperty('fieldGroup') && !hasKey(field)) {
2021
- 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;
2022
2116
  }
2023
- else {
2024
- 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());
2025
2124
  }
2026
2125
  }
2027
- postPopulate(field) {
2028
- 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) {
2029
2137
  return;
2030
2138
  }
2031
- this.root = null;
2032
- const markForCheck = this.setValidators(field);
2033
- if (markForCheck && field.parent) {
2034
- let parent = field.parent;
2035
- while (parent) {
2036
- if (hasKey(parent) || !parent.parent) {
2037
- updateValidity(parent.formControl, true);
2038
- }
2039
- parent = parent.parent;
2040
- }
2041
- }
2139
+ this.onChange($event);
2042
2140
  }
2043
- addFormControl(field) {
2044
- let control = findControl(field);
2045
- 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) {
2046
2147
  return;
2047
2148
  }
2048
- if (!control) {
2049
- const controlOptions = { updateOn: field.modelOptions.updateOn };
2050
- if (field.fieldGroup) {
2051
- control = new FormGroup({}, controlOptions);
2052
- }
2053
- else {
2054
- const value = hasKey(field) ? getFieldValue(field) : field.defaultValue;
2055
- control = new FormControl({ value, disabled: !!field.props.disabled }, { ...controlOptions, initialValueIsDefault: true });
2056
- }
2149
+ if (f['_elementRefs']?.indexOf(this.elementRef) === -1) {
2150
+ f['_elementRefs'].push(this.elementRef);
2057
2151
  }
2058
2152
  else {
2059
- if (control instanceof FormControl) {
2060
- const value = hasKey(field) ? getFieldValue(field) : field.defaultValue;
2061
- control.defaultValue = value;
2062
- }
2153
+ defineHiddenProp(f, '_elementRefs', [this.elementRef]);
2063
2154
  }
2064
- registerControl(field, control);
2065
2155
  }
2066
- setValidators(field, disabled = false) {
2067
- if (disabled === false && hasKey(field) && field.props?.disabled) {
2068
- disabled = true;
2069
- }
2070
- let markForCheck = false;
2071
- field.fieldGroup?.forEach((f) => f && this.setValidators(f, disabled) && (markForCheck = true));
2072
- if (hasKey(field) || !field.parent || (!hasKey(field) && !field.fieldGroup)) {
2073
- const { formControl: c } = field;
2074
- if (c) {
2075
- if (hasKey(field) && c instanceof FormControl) {
2076
- if (disabled && c.enabled) {
2077
- c.disable({ emitEvent: false, onlySelf: true });
2078
- markForCheck = true;
2079
- }
2080
- if (!disabled && c.disabled) {
2081
- c.enable({ emitEvent: false, onlySelf: true });
2082
- markForCheck = true;
2083
- }
2084
- }
2085
- if (null === c.validator && this.hasValidators(field, '_validators')) {
2086
- c.setValidators(() => {
2087
- const v = Validators.compose(this.mergeValidators(field, '_validators'));
2088
- return v ? v(c) : null;
2089
- });
2090
- markForCheck = true;
2091
- }
2092
- if (null === c.asyncValidator && this.hasValidators(field, '_asyncValidators')) {
2093
- c.setAsyncValidators(() => {
2094
- const v = Validators.composeAsync(this.mergeValidators(field, '_asyncValidators'));
2095
- return v ? v(c) : of(null);
2096
- });
2097
- markForCheck = true;
2098
- }
2099
- if (markForCheck) {
2100
- updateValidity(c, true);
2101
- // update validity of `FormGroup` instance created by field with nested key.
2102
- let parent = c.parent;
2103
- for (let i = 1; i < getKeyPath(field).length; i++) {
2104
- if (parent) {
2105
- updateValidity(parent, true);
2106
- parent = parent.parent;
2107
- }
2108
- }
2109
- }
2110
- }
2156
+ detachElementRef(f) {
2157
+ const index = f?.['_elementRefs'] ? this.fieldAttrElements.indexOf(this.elementRef) : -1;
2158
+ if (index !== -1) {
2159
+ f['_elementRefs'].splice(index, 1);
2111
2160
  }
2112
- return markForCheck;
2113
2161
  }
2114
- hasValidators(field, type) {
2115
- const c = field.formControl;
2116
- if (c?._fields?.length > 1 && c._fields.some((f) => f[type].length > 0)) {
2117
- return true;
2118
- }
2119
- else if (field[type].length > 0) {
2120
- return true;
2121
- }
2122
- 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);
2123
2164
  }
2124
- mergeValidators(field, type) {
2125
- const validators = [];
2126
- const c = field.formControl;
2127
- if (c?._fields?.length > 1) {
2128
- c._fields
2129
- .filter((f) => !f._hide)
2130
- .forEach((f) => validators.push(...f[type]));
2131
- }
2132
- else if (field[type]) {
2133
- validators.push(...field[type]);
2134
- }
2135
- if (field.fieldGroup) {
2136
- field.fieldGroup
2137
- .filter((f) => f?.fieldGroup && !hasKey(f))
2138
- .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: "16.2.12", 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: "16.2.12", 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: "16.2.12", ngImport: i0, type: FormlyAttributes, decorators: [{
2172
+ type: Directive,
2173
+ args: [{
2174
+ selector: '[formlyAttributes]',
2175
+ host: {
2176
+ '(change)': 'onHostChange($event)',
2177
+ },
2178
+ }]
2179
+ }], ctorParameters: function () { return [{ 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
+ }
2139
2233
  }
2140
- return validators;
2234
+ return undefined;
2141
2235
  }
2236
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyValidationMessage, deps: [{ token: FormlyConfig }], target: i0.ɵɵFactoryTarget.Component }); }
2237
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", 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 }); }
2142
2238
  }
2239
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", 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: function () { return [{ type: FormlyConfig }]; }, propDecorators: { field: [{
2247
+ type: Input
2248
+ }] } });
2143
2249
 
2144
- class CoreExtension {
2145
- constructor(config) {
2146
- this.config = config;
2147
- this.formId = 0;
2148
- }
2149
- prePopulate(field) {
2150
- const root = field.parent;
2151
- this.initRootOptions(field);
2152
- this.initFieldProps(field);
2153
- if (root) {
2154
- Object.defineProperty(field, 'options', { get: () => root.options, configurable: true });
2155
- Object.defineProperty(field, 'model', {
2156
- get: () => (hasKey(field) && field.fieldGroup ? getFieldValue(field) : root.model),
2157
- configurable: true,
2158
- });
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);
2159
2270
  }
2160
- Object.defineProperty(field, 'get', {
2161
- value: (key) => getField(field, key),
2162
- configurable: true,
2163
- });
2164
- this.getFieldComponentInstance(field).prePopulate?.(field);
2165
2271
  }
2166
- onPopulate(field) {
2167
- this.initFieldOptions(field);
2168
- this.getFieldComponentInstance(field).onPopulate?.(field);
2169
- if (field.fieldGroup) {
2170
- field.fieldGroup.forEach((f, index) => {
2171
- if (f) {
2172
- Object.defineProperty(f, 'parent', { get: () => field, configurable: true });
2173
- Object.defineProperty(f, 'index', { get: () => index, configurable: true });
2174
- }
2175
- this.formId++;
2176
- });
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, []);
2177
2277
  }
2278
+ this.model.splice(i, 0, initialModel ? clone(initialModel) : undefined);
2279
+ this.markFieldForCheck(this.field.fieldGroup[i]);
2280
+ this._build();
2178
2281
  }
2179
- postPopulate(field) {
2180
- 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();
2181
2290
  }
2182
- initFieldProps(field) {
2183
- field.props ?? (field.props = field.templateOptions);
2184
- Object.defineProperty(field, 'templateOptions', {
2185
- get: () => field.props,
2186
- set: (props) => (field.props = props),
2187
- 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',
2188
2298
  });
2189
2299
  }
2190
- initRootOptions(field) {
2191
- if (field.parent) {
2300
+ updateArrayElementKey(f, newKey) {
2301
+ if (hasKey(f)) {
2302
+ f.key = newKey;
2192
2303
  return;
2193
2304
  }
2194
- const options = field.options;
2195
- field.options.formState = field.options.formState || {};
2196
- if (!options.showError) {
2197
- options.showError = this.config.extras.showError;
2198
- }
2199
- if (!options.fieldChanges) {
2200
- defineHiddenProp(options, 'fieldChanges', new Subject());
2305
+ if (!f.fieldGroup?.length) {
2306
+ return;
2201
2307
  }
2202
- if (!options._hiddenFieldsForCheck) {
2203
- options._hiddenFieldsForCheck = [];
2308
+ for (let i = 0; i < f.fieldGroup.length; i++) {
2309
+ this.updateArrayElementKey(f.fieldGroup[i], newKey);
2204
2310
  }
2205
- options._markForCheck = (f) => {
2206
- console.warn(`Formly: 'options._markForCheck' is deprecated since v6.0, use 'options.detectChanges' instead.`);
2207
- options.detectChanges(f);
2208
- };
2209
- options._detectChanges = (f) => {
2210
- if (f._componentRefs) {
2211
- markFieldForCheck(f);
2212
- }
2213
- f.fieldGroup?.forEach((f) => f && options._detectChanges(f));
2214
- };
2215
- options.detectChanges = (f) => {
2216
- f.options.checkExpressions?.(f);
2217
- options._detectChanges(f);
2218
- };
2219
- options.resetModel = (model) => {
2220
- model = clone(model ?? options._initialModel);
2221
- if (field.model) {
2222
- Object.keys(field.model).forEach((k) => delete field.model[k]);
2223
- Object.assign(field.model, model || {});
2224
- }
2225
- if (!isSignalRequired()) {
2226
- observe(options, ['parentForm', 'submitted']).setValue(false, false);
2227
- }
2228
- options.build(field);
2229
- field.form.reset(field.model);
2230
- };
2231
- options.updateInitialValue = (model) => (options._initialModel = clone(model ?? field.model));
2232
- field.options.updateInitialValue();
2233
2311
  }
2234
- initFieldOptions(field) {
2235
- reverseDeepMerge(field, {
2236
- id: getFieldId(`formly_${this.formId}`, field, field.index),
2237
- hooks: {},
2238
- modelOptions: {},
2239
- validation: { messages: {} },
2240
- props: !field.type || !hasKey(field)
2241
- ? {}
2242
- : {
2243
- label: '',
2244
- placeholder: '',
2245
- disabled: false,
2246
- },
2247
- });
2248
- if (this.config.extras.resetFieldOnHide && field.resetOnHide !== false) {
2249
- field.resetOnHide = true;
2250
- }
2251
- if (field.type !== 'formly-template' &&
2252
- (field.template || field.expressions?.template || field.expressionProperties?.template)) {
2253
- field.type = 'formly-template';
2254
- }
2255
- if (!field.type && field.fieldGroup) {
2256
- field.type = 'formly-group';
2257
- }
2258
- if (field.type) {
2259
- this.config.getMergedField(field);
2260
- }
2261
- if (hasKey(field) &&
2262
- !isUndefined(field.defaultValue) &&
2263
- isUndefined(getFieldValue(field)) &&
2264
- !isHiddenField(field)) {
2265
- assignFieldValue(field, field.defaultValue);
2312
+ markFieldForCheck(f) {
2313
+ if (!f) {
2314
+ return;
2266
2315
  }
2267
- field.wrappers = field.wrappers || [];
2268
- }
2269
- getFieldComponentInstance(field) {
2270
- const componentRefInstance = () => {
2271
- let componentRef = this.config.resolveFieldTypeRef(field);
2272
- const fieldComponentRef = field._componentRefs?.slice(-1)[0];
2273
- if (fieldComponentRef instanceof ComponentRef &&
2274
- fieldComponentRef?.componentType === componentRef?.componentType) {
2275
- componentRef = fieldComponentRef;
2276
- }
2277
- return componentRef?.instance;
2278
- };
2279
- if (!field._proxyInstance) {
2280
- defineHiddenProp(field, '_proxyInstance', new Proxy({}, {
2281
- get: (_, prop) => componentRefInstance()?.[prop],
2282
- set: (_, prop, value) => (componentRefInstance()[prop] = value),
2283
- }));
2316
+ f.fieldGroup?.forEach((c) => this.markFieldForCheck(c));
2317
+ if (f.hide === false) {
2318
+ this.options._hiddenFieldsForCheck.push({ field: f });
2284
2319
  }
2285
- return field._proxyInstance;
2286
2320
  }
2321
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FieldArrayType, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
2322
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: FieldArrayType, usesInheritance: true, ngImport: i0 }); }
2287
2323
  }
2324
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FieldArrayType, decorators: [{
2325
+ type: Directive
2326
+ }] });
2288
2327
 
2289
- function defaultFormlyConfig(config) {
2290
- return {
2291
- types: [
2292
- { name: 'formly-group', component: FormlyGroup },
2293
- { name: 'formly-template', component: FormlyTemplateType },
2294
- ],
2295
- extensions: [
2296
- { name: 'core', extension: new CoreExtension(config), priority: -250 },
2297
- { name: 'field-validation', extension: new FieldValidationExtension(config), priority: -200 },
2298
- { name: 'field-form', extension: new FieldFormExtension(), priority: -150 },
2299
- { name: 'field-expression', extension: new FieldExpressionExtension(), priority: -100 },
2300
- ],
2301
- };
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: "16.2.12", ngImport: i0, type: FieldWrapper, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
2334
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.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 }); }
2302
2335
  }
2336
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", 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
+
2303
2346
  class FormlyModule {
2304
- constructor(configService, configs = []) {
2305
- if (!configs) {
2306
- return;
2307
- }
2308
- configs.forEach((config) => configService.addConfig(config));
2309
- }
2310
2347
  static forRoot(config = {}) {
2311
2348
  return {
2312
2349
  ngModule: FormlyModule,
2313
2350
  providers: [
2314
- { provide: FORMLY_CONFIG, multi: true, useFactory: defaultFormlyConfig, deps: [FormlyConfig] },
2351
+ { provide: FORMLY_CONFIG, multi: true, useFactory: withDefaultConfig, deps: [FormlyConfig] },
2315
2352
  { provide: FORMLY_CONFIG, useValue: config, multi: true },
2316
2353
  FormlyConfig,
2317
2354
  FormlyFormBuilder,
@@ -2322,43 +2359,50 @@ class FormlyModule {
2322
2359
  return {
2323
2360
  ngModule: FormlyModule,
2324
2361
  providers: [
2325
- { provide: FORMLY_CONFIG, multi: true, useFactory: defaultFormlyConfig, deps: [FormlyConfig] },
2362
+ { provide: FORMLY_CONFIG, multi: true, useFactory: withDefaultConfig, deps: [FormlyConfig] },
2326
2363
  { provide: FORMLY_CONFIG, useValue: config, multi: true },
2327
2364
  FormlyFormBuilder,
2328
2365
  ],
2329
2366
  };
2330
2367
  }
2368
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
2369
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.2.12", 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: "16.2.12", ngImport: i0, type: FormlyModule, imports: [CommonModule] }); }
2331
2382
  }
2332
- 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 });
2333
- FormlyModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyModule, declarations: [FormlyTemplate,
2334
- FormlyForm,
2335
- FormlyField,
2336
- FormlyAttributes,
2337
- FormlyGroup,
2338
- FormlyValidationMessage,
2339
- FormlyTemplateType], imports: [CommonModule], exports: [FormlyTemplate, FormlyForm, FormlyField, FormlyAttributes, FormlyGroup, FormlyValidationMessage] });
2340
- FormlyModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyModule, imports: [[CommonModule]] });
2341
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyModule, decorators: [{
2383
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyModule, decorators: [{
2342
2384
  type: NgModule,
2343
2385
  args: [{
2344
2386
  declarations: [
2345
2387
  FormlyTemplate,
2346
- FormlyForm,
2347
- FormlyField,
2388
+ LegacyFormlyForm,
2389
+ LegacyFormlyField,
2348
2390
  FormlyAttributes,
2349
2391
  FormlyGroup,
2350
2392
  FormlyValidationMessage,
2351
2393
  FormlyTemplateType,
2352
2394
  ],
2353
- exports: [FormlyTemplate, FormlyForm, FormlyField, FormlyAttributes, FormlyGroup, FormlyValidationMessage],
2395
+ exports: [
2396
+ FormlyTemplate,
2397
+ LegacyFormlyForm,
2398
+ LegacyFormlyField,
2399
+ FormlyAttributes,
2400
+ FormlyGroup,
2401
+ FormlyValidationMessage,
2402
+ ],
2354
2403
  imports: [CommonModule],
2355
2404
  }]
2356
- }], ctorParameters: function () { return [{ type: FormlyConfig }, { type: undefined, decorators: [{
2357
- type: Optional
2358
- }, {
2359
- type: Inject,
2360
- args: [FORMLY_CONFIG]
2361
- }] }]; } });
2405
+ }] });
2362
2406
 
2363
2407
  /*
2364
2408
  * Public API Surface of core
@@ -2368,5 +2412,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
2368
2412
  * Generated bundle index. Do not edit.
2369
2413
  */
2370
2414
 
2371
- 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 };
2372
2416
  //# sourceMappingURL=ngx-formly-core.mjs.map