@ngx-formly/core 6.3.11 → 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 +1750 -1705
  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 -147
  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 -2417
  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,630 +427,666 @@ function clearControl(form) {
723
427
  delete form?._fields;
724
428
  form.setValidators(null);
725
429
  form.setAsyncValidators(null);
726
- if (form instanceof FormGroup || form instanceof FormArray) {
430
+ if (form instanceof UntypedFormGroup || form instanceof UntypedFormArray) {
727
431
  Object.values(form.controls).forEach((c) => clearControl(c));
728
432
  }
729
433
  }
730
434
 
731
- class FormlyTemplate {
732
- constructor(ref) {
733
- this.ref = ref;
734
- }
735
- ngOnChanges() {
736
- this.name = this.name || 'formly-group';
737
- }
738
- }
739
- FormlyTemplate.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
740
- FormlyTemplate.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FormlyTemplate, selector: "[formlyTemplate]", inputs: { name: ["formlyTemplate", "name"] }, usesOnChanges: true, ngImport: i0 });
741
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplate, decorators: [{
742
- type: Directive,
743
- args: [{ selector: '[formlyTemplate]' }]
744
- }], ctorParameters: function () { return [{ type: i0.TemplateRef }]; }, propDecorators: { name: [{
745
- type: Input,
746
- args: ['formlyTemplate']
747
- }] } });
748
- // workarround for https://github.com/angular/angular/issues/43227#issuecomment-904173738
749
- class FormlyFieldTemplates {
750
- }
751
- FormlyFieldTemplates.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
752
- FormlyFieldTemplates.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates });
753
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates, decorators: [{
754
- type: Injectable
755
- }] });
756
-
757
- /**
758
- * The `<formly-field>` component is used to render the UI widget (layout + type) of a given `field`.
759
- */
760
- class FormlyField {
761
- constructor(config, renderer, _elementRef, hostContainerRef, form) {
762
- this.config = config;
763
- this.renderer = renderer;
764
- this._elementRef = _elementRef;
765
- this.hostContainerRef = hostContainerRef;
766
- this.form = form;
767
- this.hostObservers = [];
768
- this.componentRefs = [];
769
- this.hooksObservers = [];
770
- this.detectFieldBuild = false;
771
- this.valueChangesUnsubscribe = () => { };
772
- }
773
- get containerRef() {
774
- return this.config.extras.renderFormlyFieldElement ? this.viewContainerRef : this.hostContainerRef;
775
- }
776
- get elementRef() {
777
- if (this.config.extras.renderFormlyFieldElement) {
778
- return this._elementRef;
435
+ class FieldExpressionExtension {
436
+ onPopulate(field) {
437
+ if (field._expressions) {
438
+ return;
779
439
  }
780
- if (this.componentRefs?.[0] instanceof ComponentRef) {
781
- return this.componentRefs[0].location;
440
+ // cache built expression
441
+ defineHiddenProp(field, '_expressions', {});
442
+ observe(field, ['hide'], ({ currentValue, firstChange }) => {
443
+ defineHiddenProp(field, '_hide', !!currentValue);
444
+ if (!firstChange || (firstChange && currentValue === true)) {
445
+ field.props.hidden = currentValue;
446
+ field.options._hiddenFieldsForCheck.push({ field });
447
+ }
448
+ });
449
+ if (field.hideExpression) {
450
+ observe(field, ['hideExpression'], ({ currentValue: expr }) => {
451
+ field._expressions.hide = this.parseExpressions(field, 'hide', typeof expr === 'boolean' ? () => expr : expr);
452
+ });
782
453
  }
783
- return null;
784
- }
785
- ngAfterContentInit() {
786
- this.triggerHook('afterContentInit');
787
- }
788
- ngAfterViewInit() {
789
- this.triggerHook('afterViewInit');
790
- }
791
- ngDoCheck() {
792
- if (this.detectFieldBuild && this.field && this.field.options) {
793
- this.render();
454
+ const evalExpr = (key, expr) => {
455
+ if (typeof expr === 'string' || isFunction(expr)) {
456
+ field._expressions[key] = this.parseExpressions(field, key, expr);
457
+ }
458
+ else if (expr instanceof Observable) {
459
+ field._expressions[key] = {
460
+ value$: expr.pipe(tap((v) => {
461
+ this.evalExpr(field, key, v);
462
+ field.options._detectChanges(field);
463
+ })),
464
+ };
465
+ }
466
+ };
467
+ field.expressions = field.expressions || {};
468
+ for (const key of Object.keys(field.expressions)) {
469
+ observe(field, ['expressions', key], ({ currentValue: expr }) => {
470
+ evalExpr(key, isFunction(expr) ? (...args) => expr(field, args[3]) : expr);
471
+ });
472
+ }
473
+ field.expressionProperties = field.expressionProperties || {};
474
+ for (const key of Object.keys(field.expressionProperties)) {
475
+ observe(field, ['expressionProperties', key], ({ currentValue }) => evalExpr(key, currentValue));
794
476
  }
795
477
  }
796
- ngOnInit() {
797
- this.triggerHook('onInit');
798
- }
799
- ngOnChanges(changes) {
800
- this.triggerHook('onChanges', changes);
801
- }
802
- ngOnDestroy() {
803
- this.resetRefs(this.field);
804
- this.hostObservers.forEach((hostObserver) => hostObserver.unsubscribe());
805
- this.hooksObservers.forEach((unsubscribe) => unsubscribe());
806
- this.valueChangesUnsubscribe();
807
- this.triggerHook('onDestroy');
808
- }
809
- renderField(containerRef, f, wrappers = []) {
810
- if (this.containerRef === containerRef) {
811
- this.resetRefs(this.field);
812
- this.containerRef.clear();
813
- wrappers = this.field?.wrappers;
478
+ postPopulate(field) {
479
+ if (field.parent) {
480
+ return;
814
481
  }
815
- if (wrappers?.length > 0) {
816
- const [wrapper, ...wps] = wrappers;
817
- const { component } = this.config.getWrapper(wrapper);
818
- const ref = containerRef.createComponent(component);
819
- this.attachComponentRef(ref, f);
820
- observe(ref.instance, ['fieldComponent'], ({ currentValue, previousValue, firstChange }) => {
821
- if (currentValue) {
822
- if (previousValue && previousValue._lContainer === currentValue._lContainer) {
823
- return;
824
- }
825
- const viewRef = previousValue ? previousValue.detach() : null;
826
- if (viewRef && !viewRef.destroyed) {
827
- currentValue.insert(viewRef);
828
- }
829
- else {
830
- this.renderField(currentValue, f, wps);
831
- }
832
- !firstChange && ref.changeDetectorRef.detectChanges();
482
+ if (!field.options.checkExpressions) {
483
+ let checkLocked = false;
484
+ field.options.checkExpressions = (f, ignoreCache) => {
485
+ if (checkLocked) {
486
+ return;
833
487
  }
834
- });
835
- }
836
- else if (f?.type) {
837
- const inlineType = this.form?.templates?.find((ref) => ref.name === f.type);
838
- let ref;
839
- if (inlineType) {
840
- ref = containerRef.createEmbeddedView(inlineType.ref, { $implicit: f });
841
- }
842
- else {
843
- const { component } = this.config.getType(f.type, true);
844
- ref = containerRef.createComponent(component);
845
- }
846
- this.attachComponentRef(ref, f);
488
+ checkLocked = true;
489
+ const fieldChanged = this.checkExpressions(f, ignoreCache);
490
+ const options = field.options;
491
+ options._hiddenFieldsForCheck
492
+ .sort((f) => (f.field.hide ? -1 : 1))
493
+ .forEach((f) => this.changeHideState(f.field, f.field.hide ?? f.default, !ignoreCache));
494
+ options._hiddenFieldsForCheck = [];
495
+ if (fieldChanged) {
496
+ this.checkExpressions(field);
497
+ }
498
+ checkLocked = false;
499
+ };
500
+ field.options._checkField = (f, ignoreCache) => {
501
+ console.warn(`Formly: 'options._checkField' is deprecated since v6.0, use 'options.checkExpressions' instead.`);
502
+ field.options.checkExpressions(f, ignoreCache);
503
+ };
847
504
  }
848
505
  }
849
- triggerHook(name, changes) {
850
- if (name === 'onInit' || (name === 'onChanges' && changes.field && !changes.field.firstChange)) {
851
- this.valueChangesUnsubscribe();
852
- this.valueChangesUnsubscribe = this.fieldChanges(this.field);
853
- }
854
- if (this.field?.hooks?.[name]) {
855
- if (!changes || changes.field) {
856
- const r = this.field.hooks[name](this.field);
857
- if (isObservable(r) && ['onInit', 'afterContentInit', 'afterViewInit'].indexOf(name) !== -1) {
858
- const sub = r.subscribe();
859
- this.hooksObservers.push(() => sub.unsubscribe());
506
+ parseExpressions(field, path, expr) {
507
+ let parentExpression;
508
+ if (field.parent && ['hide', 'props.disabled'].includes(path)) {
509
+ const rootValue = (f) => {
510
+ return path === 'hide' ? f.hide : f.props.disabled;
511
+ };
512
+ parentExpression = () => {
513
+ let root = field.parent;
514
+ while (root.parent && !rootValue(root)) {
515
+ root = root.parent;
860
516
  }
861
- }
517
+ return rootValue(root);
518
+ };
862
519
  }
863
- if (name === 'onChanges' && changes.field) {
864
- this.resetRefs(changes.field.previousValue);
865
- this.render();
520
+ expr = expr || (() => false);
521
+ if (typeof expr === 'string') {
522
+ expr = evalStringExpression(expr, ['model', 'formState', 'field']);
866
523
  }
524
+ let currentValue;
525
+ return {
526
+ callback: (ignoreCache) => {
527
+ try {
528
+ const exprValue = evalExpression(parentExpression ? (...args) => parentExpression(field) || expr(...args) : expr, { field }, [field.model, field.options.formState, field, ignoreCache]);
529
+ if (ignoreCache ||
530
+ (currentValue !== exprValue &&
531
+ (!isObject(exprValue) ||
532
+ isObservable(exprValue) ||
533
+ JSON.stringify(exprValue) !== JSON.stringify(currentValue)))) {
534
+ currentValue = exprValue;
535
+ this.evalExpr(field, path, exprValue);
536
+ return true;
537
+ }
538
+ return false;
539
+ }
540
+ catch (error) {
541
+ error.message = `[Formly Error] [Expression "${path}"] ${error.message}`;
542
+ throw error;
543
+ }
544
+ },
545
+ };
867
546
  }
868
- attachComponentRef(ref, field) {
869
- this.componentRefs.push(ref);
870
- field._componentRefs.push(ref);
871
- if (ref instanceof ComponentRef) {
872
- Object.assign(ref.instance, { field });
547
+ checkExpressions(field, ignoreCache = false) {
548
+ if (!field) {
549
+ return false;
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
- observeDeep(this.field.options, ['formState'], () => this.field.options.detectChanges(this.field)),
1119
- ];
1120
- const valueChanges = this.field.options.fieldChanges
1121
- .pipe(filter(({ field, type }) => hasKey(field) && type === 'valueChanges'), switchMap(() => (isNoopNgZone(this.ngZone) ? of(null) : this.ngZone.onStable.asObservable().pipe(take(1)))))
1122
- .subscribe(() => this.ngZone.runGuarded(() => {
1123
- // runGuarded is used to keep in sync the expression changes
1124
- // https://github.com/ngx-formly/ngx-formly/issues/2095
1125
- this.checkExpressionChange();
1126
- this.modelChange.emit((this._modelChangeValue = clone(this.model)));
1127
- }));
1128
- return () => {
1129
- fieldChangesDetection.forEach((fnc) => fnc());
1130
- formEvents?.unsubscribe();
1131
- valueChanges.unsubscribe();
740
+ options._markForCheck = (f) => {
741
+ console.warn(`Formly: 'options._markForCheck' is deprecated since v6.0, use 'options.detectChanges' instead.`);
742
+ options.detectChanges(f);
1132
743
  };
1133
- }
1134
- setField(field) {
1135
- if (this.config.extras.immutable) {
1136
- this.field = { ...this.field, ...clone(field) };
1137
- }
1138
- else {
1139
- Object.keys(field).forEach((p) => (this.field[p] = field[p]));
1140
- }
1141
- }
1142
- }
1143
- FormlyForm.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyForm, deps: [{ token: FormlyFormBuilder }, { token: FormlyConfig }, { token: i0.NgZone }, { token: FormlyFieldTemplates }], target: i0.ɵɵFactoryTarget.Component });
1144
- FormlyForm.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: FormlyForm, selector: "formly-form", inputs: { form: "form", model: "model", fields: "fields", options: "options" }, outputs: { modelChange: "modelChange" }, providers: [FormlyFormBuilder, FormlyFieldTemplates], queries: [{ propertyName: "templates", predicate: FormlyTemplate }], usesOnChanges: true, ngImport: i0, template: '<formly-field [field]="field"></formly-field>', isInline: true, components: [{ type: FormlyField, selector: "formly-field", inputs: ["field"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1145
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyForm, decorators: [{
1146
- type: Component,
1147
- args: [{
1148
- selector: 'formly-form',
1149
- template: '<formly-field [field]="field"></formly-field>',
1150
- providers: [FormlyFormBuilder, FormlyFieldTemplates],
1151
- changeDetection: ChangeDetectionStrategy.OnPush,
1152
- }]
1153
- }], ctorParameters: function () { return [{ type: FormlyFormBuilder }, { type: FormlyConfig }, { type: i0.NgZone }, { type: FormlyFieldTemplates }]; }, propDecorators: { form: [{
1154
- type: Input
1155
- }], model: [{
1156
- type: Input
1157
- }], fields: [{
1158
- type: Input
1159
- }], options: [{
1160
- type: Input
1161
- }], modelChange: [{
1162
- type: Output
1163
- }], templates: [{
1164
- type: ContentChildren,
1165
- args: [FormlyTemplate]
1166
- }] } });
1167
-
1168
- /**
1169
- * Allow to link the `field` HTML attributes (`id`, `name` ...) and Event attributes (`focus`, `blur` ...) to an element in the DOM.
1170
- */
1171
- class FormlyAttributes {
1172
- constructor(renderer, elementRef, _document) {
1173
- this.renderer = renderer;
1174
- this.elementRef = elementRef;
1175
- this.uiAttributesCache = {};
1176
- /**
1177
- * HostBinding doesn't register listeners conditionally which may produce some perf issues.
1178
- *
1179
- * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1991
1180
- */
1181
- this.uiEvents = {
1182
- listeners: [],
1183
- events: ['click', 'keyup', 'keydown', 'keypress', 'focus', 'blur', 'change'],
1184
- callback: (eventName, $event) => {
1185
- switch (eventName) {
1186
- case 'focus':
1187
- return this.onFocus($event);
1188
- case 'blur':
1189
- return this.onBlur($event);
1190
- case 'change':
1191
- return this.onChange($event);
1192
- default:
1193
- return this.props[eventName](this.field, $event);
1194
- }
1195
- },
744
+ options._detectChanges = (f) => {
745
+ if (f._componentRefs) {
746
+ markFieldForCheck(f);
747
+ }
748
+ f.fieldGroup?.forEach((f) => f && options._detectChanges(f));
1196
749
  };
1197
- this.document = _document;
1198
- }
1199
- get props() {
1200
- return this.field.props || {};
1201
- }
1202
- get fieldAttrElements() {
1203
- return this.field?.['_elementRefs'] || [];
1204
- }
1205
- ngOnChanges(changes) {
1206
- if (changes.field) {
1207
- this.field.name && this.setAttribute('name', this.field.name);
1208
- this.uiEvents.listeners.forEach((listener) => listener());
1209
- this.uiEvents.events.forEach((eventName) => {
1210
- if (this.props?.[eventName] || ['focus', 'blur', 'change'].indexOf(eventName) !== -1) {
1211
- this.uiEvents.listeners.push(this.renderer.listen(this.elementRef.nativeElement, eventName, (e) => this.uiEvents.callback(eventName, e)));
1212
- }
1213
- });
1214
- if (this.props?.attributes) {
1215
- observe(this.field, ['props', 'attributes'], ({ currentValue, previousValue }) => {
1216
- if (previousValue) {
1217
- Object.keys(previousValue).forEach((attr) => this.removeAttribute(attr));
1218
- }
1219
- if (currentValue) {
1220
- Object.keys(currentValue).forEach((attr) => {
1221
- if (currentValue[attr] != null) {
1222
- this.setAttribute(attr, currentValue[attr]);
1223
- }
1224
- });
1225
- }
1226
- });
750
+ options.detectChanges = (f) => {
751
+ f.options.checkExpressions?.(f);
752
+ options._detectChanges(f);
753
+ };
754
+ options.resetModel = (model) => {
755
+ model = clone(model ?? options._initialModel);
756
+ if (field.model) {
757
+ Object.keys(field.model).forEach((k) => delete field.model[k]);
758
+ Object.assign(field.model, model || {});
1227
759
  }
1228
- this.detachElementRef(changes.field.previousValue);
1229
- this.attachElementRef(changes.field.currentValue);
1230
- if (this.fieldAttrElements.length === 1) {
1231
- !this.id && this.field.id && this.setAttribute('id', this.field.id);
1232
- this.focusObserver = observe(this.field, ['focus'], ({ currentValue }) => {
1233
- this.toggleFocus(currentValue);
1234
- });
760
+ if (!isSignalRequired()) {
761
+ observe(options, ['parentForm', 'submitted']).setValue(false, false);
1235
762
  }
763
+ options.build(field);
764
+ field.form.reset(field.model);
765
+ };
766
+ options.updateInitialValue = (model) => (options._initialModel = clone(model ?? field.model));
767
+ field.options.updateInitialValue();
768
+ }
769
+ initFieldOptions(field) {
770
+ reverseDeepMerge(field, {
771
+ id: getFieldId(`formly_${this.formId}`, field, field.index),
772
+ hooks: {},
773
+ modelOptions: {},
774
+ validation: { messages: {} },
775
+ props: !field.type || !hasKey(field)
776
+ ? {}
777
+ : {
778
+ label: '',
779
+ placeholder: '',
780
+ disabled: false,
781
+ },
782
+ });
783
+ if (this.config.extras.resetFieldOnHide && field.resetOnHide !== false) {
784
+ field.resetOnHide = true;
1236
785
  }
1237
- if (changes.id) {
1238
- this.setAttribute('id', this.id);
786
+ if (field.type !== 'formly-template' &&
787
+ (field.template || field.expressions?.template || field.expressionProperties?.template)) {
788
+ field.type = 'formly-template';
1239
789
  }
1240
- }
1241
- /**
1242
- * We need to re-evaluate all the attributes on every change detection cycle, because
1243
- * by using a HostBinding we run into certain edge cases. This means that whatever logic
1244
- * is in here has to be super lean or we risk seriously damaging or destroying the performance.
1245
- *
1246
- * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1317
1247
- * Material issue: https://github.com/angular/components/issues/14024
1248
- */
1249
- ngDoCheck() {
1250
- if (!this.uiAttributes) {
1251
- const element = this.elementRef.nativeElement;
1252
- this.uiAttributes = [...FORMLY_VALIDATORS, 'tabindex', 'placeholder', 'readonly', 'disabled', 'step'].filter((attr) => !element.hasAttribute || !element.hasAttribute(attr));
790
+ if (!field.type && field.fieldGroup) {
791
+ field.type = 'formly-group';
1253
792
  }
1254
- for (let i = 0; i < this.uiAttributes.length; i++) {
1255
- const attr = this.uiAttributes[i];
1256
- const value = this.props[attr];
1257
- if (this.uiAttributesCache[attr] !== value &&
1258
- (!this.props.attributes || !this.props.attributes.hasOwnProperty(attr.toLowerCase()))) {
1259
- this.uiAttributesCache[attr] = value;
1260
- if (value || value === 0) {
1261
- this.setAttribute(attr, value === true ? attr : `${value}`);
1262
- }
1263
- else {
1264
- this.removeAttribute(attr);
1265
- }
1266
- }
793
+ if (field.type) {
794
+ this.config.getMergedField(field);
1267
795
  }
796
+ if (hasKey(field) &&
797
+ !isUndefined(field.defaultValue) &&
798
+ isUndefined(getFieldValue(field)) &&
799
+ !isHiddenField(field)) {
800
+ assignFieldValue(field, field.defaultValue);
801
+ }
802
+ field.wrappers = field.wrappers || [];
1268
803
  }
1269
- ngOnDestroy() {
1270
- this.uiEvents.listeners.forEach((listener) => listener());
1271
- this.detachElementRef(this.field);
1272
- this.focusObserver?.unsubscribe();
1273
- }
1274
- toggleFocus(value) {
1275
- const element = this.fieldAttrElements ? this.fieldAttrElements[0] : null;
1276
- if (!element || !element.nativeElement.focus) {
1277
- return;
804
+ getFieldComponentInstance(field) {
805
+ const componentRefInstance = () => {
806
+ let componentRef = this.config.resolveFieldTypeRef(field);
807
+ const fieldComponentRef = field._componentRefs?.slice(-1)[0];
808
+ if (fieldComponentRef instanceof ComponentRef &&
809
+ fieldComponentRef?.componentType === componentRef?.componentType) {
810
+ componentRef = fieldComponentRef;
811
+ }
812
+ return componentRef?.instance;
813
+ };
814
+ if (!field._proxyInstance) {
815
+ defineHiddenProp(field, '_proxyInstance', new Proxy({}, {
816
+ get: (_, prop) => componentRefInstance()?.[prop],
817
+ set: (_, prop, value) => (componentRefInstance()[prop] = value),
818
+ }));
1278
819
  }
1279
- const isFocused = !!this.document.activeElement &&
1280
- this.fieldAttrElements.some(({ nativeElement }) => this.document.activeElement === nativeElement || nativeElement.contains(this.document.activeElement));
1281
- if (value && !isFocused) {
1282
- Promise.resolve().then(() => element.nativeElement.focus());
820
+ return field._proxyInstance;
821
+ }
822
+ }
823
+
824
+ class FieldFormExtension {
825
+ prePopulate(field) {
826
+ if (!this.root) {
827
+ this.root = field;
1283
828
  }
1284
- else if (!value && isFocused) {
1285
- Promise.resolve().then(() => element.nativeElement.blur());
829
+ if (field.parent) {
830
+ Object.defineProperty(field, 'form', {
831
+ get: () => field.parent.formControl,
832
+ configurable: true,
833
+ });
1286
834
  }
1287
835
  }
1288
- onFocus($event) {
1289
- this.focusObserver?.setValue(true);
1290
- this.props.focus?.(this.field, $event);
1291
- }
1292
- onBlur($event) {
1293
- this.focusObserver?.setValue(false);
1294
- this.props.blur?.(this.field, $event);
836
+ onPopulate(field) {
837
+ if (field.hasOwnProperty('fieldGroup') && !hasKey(field)) {
838
+ defineHiddenProp(field, 'formControl', field.form);
839
+ }
840
+ else {
841
+ this.addFormControl(field);
842
+ }
1295
843
  }
1296
- // handle custom `change` event, for regular ones rely on DOM listener
1297
- onHostChange($event) {
1298
- if ($event instanceof Event) {
844
+ postPopulate(field) {
845
+ if (this.root !== field) {
1299
846
  return;
1300
847
  }
1301
- this.onChange($event);
1302
- }
1303
- onChange($event) {
1304
- this.props.change?.(this.field, $event);
1305
- this.field.formControl?.markAsDirty();
848
+ this.root = null;
849
+ const markForCheck = this.setValidators(field);
850
+ if (markForCheck && field.parent) {
851
+ let parent = field.parent;
852
+ while (parent) {
853
+ if (hasKey(parent) || !parent.parent) {
854
+ updateValidity(parent.formControl, true);
855
+ }
856
+ parent = parent.parent;
857
+ }
858
+ }
1306
859
  }
1307
- attachElementRef(f) {
1308
- if (!f) {
860
+ addFormControl(field) {
861
+ let control = findControl(field);
862
+ if (field.fieldArray) {
1309
863
  return;
1310
864
  }
1311
- if (f['_elementRefs']?.indexOf(this.elementRef) === -1) {
1312
- f['_elementRefs'].push(this.elementRef);
865
+ if (!control) {
866
+ const controlOptions = { updateOn: field.modelOptions.updateOn };
867
+ if (field.fieldGroup) {
868
+ control = new UntypedFormGroup({}, controlOptions);
869
+ }
870
+ else {
871
+ const value = hasKey(field) ? getFieldValue(field) : field.defaultValue;
872
+ control = new UntypedFormControl({ value, disabled: !!field.props.disabled }, { ...controlOptions, initialValueIsDefault: true });
873
+ }
1313
874
  }
1314
875
  else {
1315
- defineHiddenProp(f, '_elementRefs', [this.elementRef]);
876
+ if (control instanceof FormControl) {
877
+ const value = hasKey(field) ? getFieldValue(field) : field.defaultValue;
878
+ control.defaultValue = value;
879
+ }
1316
880
  }
881
+ registerControl(field, control);
1317
882
  }
1318
- detachElementRef(f) {
1319
- const index = f?.['_elementRefs'] ? this.fieldAttrElements.indexOf(this.elementRef) : -1;
1320
- if (index !== -1) {
1321
- f['_elementRefs'].splice(index, 1);
883
+ setValidators(field, disabled = false) {
884
+ if (disabled === false && hasKey(field) && field.props?.disabled) {
885
+ disabled = true;
886
+ }
887
+ let markForCheck = false;
888
+ field.fieldGroup?.forEach((f) => f && this.setValidators(f, disabled) && (markForCheck = true));
889
+ if (hasKey(field) || !field.parent || (!hasKey(field) && !field.fieldGroup)) {
890
+ const { formControl: c } = field;
891
+ if (c) {
892
+ if (hasKey(field) && c instanceof FormControl) {
893
+ if (disabled && c.enabled) {
894
+ c.disable({ emitEvent: false, onlySelf: true });
895
+ markForCheck = true;
896
+ }
897
+ if (!disabled && c.disabled) {
898
+ c.enable({ emitEvent: false, onlySelf: true });
899
+ markForCheck = true;
900
+ }
901
+ }
902
+ if (null === c.validator && this.hasValidators(field, '_validators')) {
903
+ c.setValidators(() => {
904
+ const v = Validators.compose(this.mergeValidators(field, '_validators'));
905
+ return v ? v(c) : null;
906
+ });
907
+ markForCheck = true;
908
+ }
909
+ if (null === c.asyncValidator && this.hasValidators(field, '_asyncValidators')) {
910
+ c.setAsyncValidators(() => {
911
+ const v = Validators.composeAsync(this.mergeValidators(field, '_asyncValidators'));
912
+ return v ? v(c) : of(null);
913
+ });
914
+ markForCheck = true;
915
+ }
916
+ if (markForCheck) {
917
+ updateValidity(c, true);
918
+ // update validity of `FormGroup` instance created by field with nested key.
919
+ let parent = c.parent;
920
+ for (let i = 1; i < getKeyPath(field).length; i++) {
921
+ if (parent) {
922
+ updateValidity(parent, true);
923
+ parent = parent.parent;
924
+ }
925
+ }
926
+ }
927
+ }
1322
928
  }
929
+ return markForCheck;
1323
930
  }
1324
- setAttribute(attr, value) {
1325
- this.renderer.setAttribute(this.elementRef.nativeElement, attr, value);
931
+ hasValidators(field, type) {
932
+ const c = field.formControl;
933
+ if (c?._fields?.length > 1 && c._fields.some((f) => f[type].length > 0)) {
934
+ return true;
935
+ }
936
+ else if (field[type].length > 0) {
937
+ return true;
938
+ }
939
+ return field.fieldGroup?.some((f) => f?.fieldGroup && !hasKey(f) && this.hasValidators(f, type));
1326
940
  }
1327
- removeAttribute(attr) {
1328
- this.renderer.removeAttribute(this.elementRef.nativeElement, attr);
941
+ mergeValidators(field, type) {
942
+ const validators = [];
943
+ const c = field.formControl;
944
+ if (c?._fields?.length > 1) {
945
+ c._fields
946
+ .filter((f) => !f._hide)
947
+ .forEach((f) => validators.push(...f[type]));
948
+ }
949
+ else if (field[type]) {
950
+ validators.push(...field[type]);
951
+ }
952
+ if (field.fieldGroup) {
953
+ field.fieldGroup
954
+ .filter((f) => f?.fieldGroup && !hasKey(f))
955
+ .forEach((f) => validators.push(...this.mergeValidators(f, type)));
956
+ }
957
+ return validators;
958
+ }
959
+ }
960
+
961
+ class FieldValidationExtension {
962
+ constructor(config) {
963
+ this.config = config;
964
+ }
965
+ onPopulate(field) {
966
+ this.initFieldValidation(field, 'validators');
967
+ this.initFieldValidation(field, 'asyncValidators');
968
+ }
969
+ initFieldValidation(field, type) {
970
+ const validators = [];
971
+ if (type === 'validators' && !(field.hasOwnProperty('fieldGroup') && !hasKey(field))) {
972
+ validators.push(this.getPredefinedFieldValidation(field));
973
+ }
974
+ if (field[type]) {
975
+ for (const validatorName of Object.keys(field[type])) {
976
+ validatorName === 'validation'
977
+ ? validators.push(...field[type].validation.map((v) => this.wrapNgValidatorFn(field, v)))
978
+ : validators.push(this.wrapNgValidatorFn(field, field[type][validatorName], validatorName));
979
+ }
980
+ }
981
+ defineHiddenProp(field, '_' + type, validators);
982
+ }
983
+ getPredefinedFieldValidation(field) {
984
+ let VALIDATORS = [];
985
+ FORMLY_VALIDATORS.forEach((opt) => observe(field, ['props', opt], ({ currentValue, firstChange }) => {
986
+ VALIDATORS = VALIDATORS.filter((o) => o !== opt);
987
+ if (opt === 'required' && currentValue != null && typeof currentValue !== 'boolean') {
988
+ console.warn(`Formly: Invalid prop 'required' of type '${typeof currentValue}', expected 'boolean' (Field:${field.key}).`);
989
+ }
990
+ if (currentValue != null && currentValue !== false) {
991
+ VALIDATORS.push(opt);
992
+ }
993
+ if (!firstChange && field.formControl) {
994
+ updateValidity(field.formControl);
995
+ }
996
+ }));
997
+ return (control) => {
998
+ if (VALIDATORS.length === 0) {
999
+ return null;
1000
+ }
1001
+ return Validators.compose(VALIDATORS.map((opt) => () => {
1002
+ const value = field.props[opt];
1003
+ switch (opt) {
1004
+ case 'required':
1005
+ return Validators.required(control);
1006
+ case 'pattern':
1007
+ return Validators.pattern(value)(control);
1008
+ case 'minLength':
1009
+ const minLengthResult = Validators.minLength(value)(control);
1010
+ const minLengthKey = this.config.getValidatorMessage('minlength') || field.validation?.messages?.minlength
1011
+ ? 'minlength'
1012
+ : 'minLength';
1013
+ return minLengthResult ? { [minLengthKey]: minLengthResult.minlength } : null;
1014
+ case 'maxLength':
1015
+ const maxLengthResult = Validators.maxLength(value)(control);
1016
+ const maxLengthKey = this.config.getValidatorMessage('maxlength') || field.validation?.messages?.maxlength
1017
+ ? 'maxlength'
1018
+ : 'maxLength';
1019
+ return maxLengthResult ? { [maxLengthKey]: maxLengthResult.maxlength } : null;
1020
+ case 'min':
1021
+ return Validators.min(value)(control);
1022
+ case 'max':
1023
+ return Validators.max(value)(control);
1024
+ default:
1025
+ return null;
1026
+ }
1027
+ }))(control);
1028
+ };
1029
+ }
1030
+ wrapNgValidatorFn(field, validator, validatorName) {
1031
+ let validatorOption;
1032
+ if (typeof validator === 'string') {
1033
+ validatorOption = clone(this.config.getValidator(validator));
1034
+ }
1035
+ if (typeof validator === 'object' && validator.name) {
1036
+ validatorOption = clone(this.config.getValidator(validator.name));
1037
+ if (validator.options) {
1038
+ validatorOption.options = validator.options;
1039
+ }
1040
+ }
1041
+ if (typeof validator === 'object' && validator.expression) {
1042
+ const { expression, ...options } = validator;
1043
+ validatorOption = {
1044
+ name: validatorName,
1045
+ validation: expression,
1046
+ options: Object.keys(options).length > 0 ? options : null,
1047
+ };
1048
+ }
1049
+ if (typeof validator === 'function') {
1050
+ validatorOption = {
1051
+ name: validatorName,
1052
+ validation: validator,
1053
+ };
1054
+ }
1055
+ return (control) => {
1056
+ const errors = validatorOption.validation(control, field, validatorOption.options);
1057
+ if (isPromise(errors)) {
1058
+ return errors.then((v) => this.handleResult(field, validatorName ? !!v : v, validatorOption));
1059
+ }
1060
+ if (isObservable(errors)) {
1061
+ return errors.pipe(map((v) => this.handleResult(field, validatorName ? !!v : v, validatorOption)));
1062
+ }
1063
+ return this.handleResult(field, validatorName ? !!errors : errors, validatorOption);
1064
+ };
1065
+ }
1066
+ handleResult(field, errors, { name, options }) {
1067
+ if (typeof errors === 'boolean') {
1068
+ errors = errors ? null : { [name]: options ? options : true };
1069
+ }
1070
+ const ctrl = field.formControl;
1071
+ ctrl?._childrenErrors?.[name]?.();
1072
+ if (isObject(errors)) {
1073
+ Object.keys(errors).forEach((name) => {
1074
+ const errorPath = errors[name].errorPath ? errors[name].errorPath : options?.errorPath;
1075
+ const childCtrl = errorPath ? field.formControl.get(errorPath) : null;
1076
+ if (childCtrl) {
1077
+ const { errorPath: _errorPath, ...opts } = errors[name];
1078
+ childCtrl.setErrors({ ...(childCtrl.errors || {}), [name]: opts });
1079
+ !ctrl._childrenErrors && defineHiddenProp(ctrl, '_childrenErrors', {});
1080
+ ctrl._childrenErrors[name] = () => {
1081
+ const { [name]: _toDelete, ...childErrors } = childCtrl.errors || {};
1082
+ childCtrl.setErrors(Object.keys(childErrors).length === 0 ? null : childErrors);
1083
+ };
1084
+ }
1085
+ });
1086
+ }
1087
+ return errors;
1329
1088
  }
1330
1089
  }
1331
- FormlyAttributes.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyAttributes, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Directive });
1332
- FormlyAttributes.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FormlyAttributes, selector: "[formlyAttributes]", inputs: { field: ["formlyAttributes", "field"], id: "id" }, host: { listeners: { "change": "onHostChange($event)" } }, usesOnChanges: true, ngImport: i0 });
1333
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyAttributes, decorators: [{
1334
- type: Directive,
1335
- args: [{
1336
- selector: '[formlyAttributes]',
1337
- host: {
1338
- '(change)': 'onHostChange($event)',
1339
- },
1340
- }]
1341
- }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: undefined, decorators: [{
1342
- type: Inject,
1343
- args: [DOCUMENT]
1344
- }] }]; }, propDecorators: { field: [{
1345
- type: Input,
1346
- args: ['formlyAttributes']
1347
- }], id: [{
1348
- type: Input
1349
- }] } });
1350
1090
 
1351
1091
  class FieldType {
1352
1092
  constructor() {
@@ -1390,10 +1130,10 @@ class FieldType {
1390
1130
  get formState() {
1391
1131
  return this.options?.formState || {};
1392
1132
  }
1133
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "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 }); }
1393
1135
  }
1394
- FieldType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldType, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1395
- FieldType.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FieldType, inputs: { field: "field" }, viewQueries: [{ propertyName: "_formlyControls", predicate: NgControl, descendants: true }], ngImport: i0 });
1396
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldType, decorators: [{
1136
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FieldType, decorators: [{
1397
1137
  type: Directive
1398
1138
  }], propDecorators: { _formlyControls: [{
1399
1139
  type: ViewChildren,
@@ -1403,914 +1143,1212 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
1403
1143
  }] } });
1404
1144
 
1405
1145
  /** @ignore */
1406
- class FormlyGroup extends FieldType {
1146
+ class FormlyTemplateType extends FieldType {
1147
+ get template() {
1148
+ if (this.field && this.field.template !== this.innerHtml.template) {
1149
+ this.innerHtml = {
1150
+ template: this.field.template,
1151
+ content: this.props.safeHtml
1152
+ ? this.sanitizer.bypassSecurityTrustHtml(this.field.template)
1153
+ : this.field.template,
1154
+ };
1155
+ }
1156
+ return this.innerHtml.content;
1157
+ }
1158
+ constructor(sanitizer) {
1159
+ super();
1160
+ this.sanitizer = sanitizer;
1161
+ this.innerHtml = {};
1162
+ }
1163
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "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 }); }
1407
1165
  }
1408
- FormlyGroup.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyGroup, deps: null, target: i0.ɵɵFactoryTarget.Component });
1409
- FormlyGroup.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: FormlyGroup, selector: "formly-group", host: { properties: { "class": "field.fieldGroupClassName || \"\"" } }, usesInheritance: true, ngImport: i0, template: `
1410
- <formly-field *ngFor="let f of field.fieldGroup" [field]="f"></formly-field>
1411
- <ng-content></ng-content>
1412
- `, isInline: true, components: [{ type: FormlyField, selector: "formly-field", inputs: ["field"] }], directives: [{ type: i2$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1413
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyGroup, decorators: [{
1166
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyTemplateType, decorators: [{
1414
1167
  type: Component,
1415
1168
  args: [{
1416
- selector: 'formly-group',
1417
- template: `
1418
- <formly-field *ngFor="let f of field.fieldGroup" [field]="f"></formly-field>
1419
- <ng-content></ng-content>
1420
- `,
1421
- host: {
1422
- '[class]': 'field.fieldGroupClassName || ""',
1423
- },
1169
+ selector: 'formly-template',
1170
+ template: `<div [innerHtml]="template"></div>`,
1424
1171
  changeDetection: ChangeDetectionStrategy.OnPush,
1425
1172
  }]
1426
- }] });
1173
+ }], ctorParameters: function () { return [{ type: i1.DomSanitizer }]; } });
1427
1174
 
1428
1175
  /**
1429
- * The `<formly-validation-message>` component renders the error message of a given `field`.
1176
+ * Maintains list of formly config options. This can be used to register new field type.
1430
1177
  */
1431
- class FormlyValidationMessage {
1432
- constructor(config) {
1433
- this.config = config;
1434
- }
1435
- ngOnChanges() {
1436
- const EXPR_VALIDATORS = FORMLY_VALIDATORS.map((v) => `templateOptions.${v}`);
1437
- this.errorMessage$ = merge(this.field.formControl.statusChanges, !this.field.options
1438
- ? of(null)
1439
- : this.field.options.fieldChanges.pipe(filter(({ field, type, property }) => {
1440
- return (field === this.field &&
1441
- type === 'expressionChanges' &&
1442
- (property.indexOf('validation') !== -1 || EXPR_VALIDATORS.indexOf(property) !== -1));
1443
- }))).pipe(startWith(null), switchMap(() => (isObservable(this.errorMessage) ? this.errorMessage : of(this.errorMessage))));
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 = {};
1444
1197
  }
1445
- get errorMessage() {
1446
- const fieldForm = this.field.formControl;
1447
- for (const error in fieldForm.errors) {
1448
- if (fieldForm.errors.hasOwnProperty(error)) {
1449
- let message = this.config.getValidatorMessage(error);
1450
- if (isObject(fieldForm.errors[error])) {
1451
- if (fieldForm.errors[error].errorPath) {
1452
- return undefined;
1453
- }
1454
- if (fieldForm.errors[error].message) {
1455
- message = fieldForm.errors[error].message;
1456
- }
1457
- }
1458
- if (this.field.validation?.messages?.[error]) {
1459
- message = this.field.validation.messages[error];
1460
- }
1461
- if (this.field.validators?.[error]?.message) {
1462
- message = this.field.validators[error].message;
1463
- }
1464
- if (this.field.asyncValidators?.[error]?.message) {
1465
- message = this.field.asyncValidators[error].message;
1466
- }
1467
- if (typeof message === 'function') {
1468
- return message(fieldForm.errors[error], this.field);
1469
- }
1470
- return message;
1471
- }
1198
+ addConfig(config) {
1199
+ if (Array.isArray(config)) {
1200
+ config.forEach((c) => this.addConfig(c));
1201
+ return;
1472
1202
  }
1473
- return undefined;
1474
- }
1475
- }
1476
- FormlyValidationMessage.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyValidationMessage, deps: [{ token: FormlyConfig }], target: i0.ɵɵFactoryTarget.Component });
1477
- FormlyValidationMessage.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: FormlyValidationMessage, selector: "formly-validation-message", inputs: { field: "field" }, usesOnChanges: true, ngImport: i0, template: '{{ errorMessage$ | async }}', isInline: true, pipes: { "async": i2$1.AsyncPipe }, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1478
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyValidationMessage, decorators: [{
1479
- type: Component,
1480
- args: [{
1481
- selector: 'formly-validation-message',
1482
- template: '{{ errorMessage$ | async }}',
1483
- changeDetection: ChangeDetectionStrategy.OnPush,
1484
- }]
1485
- }], ctorParameters: function () { return [{ type: FormlyConfig }]; }, propDecorators: { field: [{
1486
- type: Input
1487
- }] } });
1488
-
1489
- class FieldArrayType extends FieldType {
1490
- onPopulate(field) {
1491
- if (hasKey(field)) {
1492
- const control = findControl(field);
1493
- registerControl(field, control ? control : new FormArray([], { updateOn: field.modelOptions.updateOn }));
1203
+ if (config.types) {
1204
+ config.types.forEach((type) => this.setType(type));
1494
1205
  }
1495
- field.fieldGroup = field.fieldGroup || [];
1496
- const length = Array.isArray(field.model) ? field.model.length : 0;
1497
- if (field.fieldGroup.length > length) {
1498
- for (let i = field.fieldGroup.length - 1; i >= length; --i) {
1499
- unregisterControl(field.fieldGroup[i], true);
1500
- field.fieldGroup.splice(i, 1);
1501
- }
1206
+ if (config.validators) {
1207
+ config.validators.forEach((validator) => this.setValidator(validator));
1502
1208
  }
1503
- for (let i = field.fieldGroup.length; i < length; i++) {
1504
- const f = { ...clone(typeof field.fieldArray === 'function' ? field.fieldArray(field) : field.fieldArray) };
1505
- if (f.key !== null) {
1506
- f.key = `${i}`;
1507
- }
1508
- field.fieldGroup.push(f);
1209
+ if (config.wrappers) {
1210
+ config.wrappers.forEach((wrapper) => this.setWrapper(wrapper));
1509
1211
  }
1510
- }
1511
- add(i, initialModel, { markAsDirty } = { markAsDirty: true }) {
1512
- markAsDirty && this.formControl.markAsDirty();
1513
- i = i == null ? this.field.fieldGroup.length : i;
1514
- if (!this.model) {
1515
- assignFieldValue(this.field, []);
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
+ };
1516
1226
  }
1517
- this.model.splice(i, 0, initialModel ? clone(initialModel) : undefined);
1518
- this.markFieldForCheck(this.field.fieldGroup[i]);
1519
- this._build();
1520
1227
  }
1521
- remove(i, { markAsDirty } = { markAsDirty: true }) {
1522
- markAsDirty && this.formControl.markAsDirty();
1523
- this.model.splice(i, 1);
1524
- const field = this.field.fieldGroup[i];
1525
- this.field.fieldGroup.splice(i, 1);
1526
- this.field.fieldGroup.forEach((f, key) => this.updateArrayElementKey(f, `${key}`));
1527
- unregisterControl(field, true);
1528
- this._build();
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
+ }
1529
1246
  }
1530
- _build() {
1531
- const fields = this.field.formControl._fields ?? [this.field];
1532
- fields.forEach((f) => this.options.build(f));
1533
- this.options.fieldChanges.next({
1534
- field: this.field,
1535
- value: getFieldValue(this.field),
1536
- type: 'valueChanges',
1537
- });
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];
1538
1259
  }
1539
- updateArrayElementKey(f, newKey) {
1540
- if (hasKey(f)) {
1541
- f.key = newKey;
1260
+ /** @ignore */
1261
+ getMergedField(field = {}) {
1262
+ const type = this.getType(field.type);
1263
+ if (!type) {
1542
1264
  return;
1543
1265
  }
1544
- if (!f.fieldGroup?.length) {
1545
- return;
1266
+ if (type.defaultOptions) {
1267
+ reverseDeepMerge(field, type.defaultOptions);
1546
1268
  }
1547
- for (let i = 0; i < f.fieldGroup.length; i++) {
1548
- 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];
1549
1287
  }
1550
1288
  }
1551
- markFieldForCheck(f) {
1552
- if (!f) {
1553
- return;
1289
+ /** @ignore @internal */
1290
+ resolveFieldTypeRef(field = {}) {
1291
+ const type = this.getType(field.type);
1292
+ if (!type) {
1293
+ return null;
1554
1294
  }
1555
- f.fieldGroup?.forEach((c) => this.markFieldForCheck(c));
1556
- if (f.hide === false) {
1557
- 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;
1301
+ }
1302
+ const componentRef = _viewContainerRef.createComponent(type.component, { injector: _injector });
1303
+ defineHiddenProp(type, '_componentRef', componentRef);
1304
+ try {
1305
+ componentRef.destroy();
1558
1306
  }
1307
+ catch (e) {
1308
+ console.error(`An error occurred while destroying the Formly component type "${field.type}"`, e);
1309
+ }
1310
+ return type._componentRef;
1559
1311
  }
1560
- }
1561
- FieldArrayType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldArrayType, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1562
- FieldArrayType.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FieldArrayType, usesInheritance: true, ngImport: i0 });
1563
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldArrayType, decorators: [{
1564
- type: Directive
1565
- }] });
1566
-
1567
- class FieldWrapper extends FieldType {
1568
- set _formlyControls(_) { }
1569
- set _staticContent(content) {
1570
- 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
+ }
1571
1319
  }
1572
- }
1573
- FieldWrapper.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldWrapper, deps: null, target: i0.ɵɵFactoryTarget.Directive });
1574
- FieldWrapper.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FieldWrapper, viewQueries: [{ propertyName: "fieldComponent", first: true, predicate: ["fieldComponent"], descendants: true, read: ViewContainerRef }, { propertyName: "_staticContent", first: true, predicate: ["fieldComponent"], descendants: true, read: ViewContainerRef, static: true }], usesInheritance: true, ngImport: i0 });
1575
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FieldWrapper, decorators: [{
1576
- type: Directive
1577
- }], propDecorators: { fieldComponent: [{
1578
- type: ViewChild,
1579
- args: ['fieldComponent', { read: ViewContainerRef }]
1580
- }], _staticContent: [{
1581
- type: ViewChild,
1582
- args: ['fieldComponent', { read: ViewContainerRef, static: true }]
1583
- }] } });
1584
-
1585
- /** @ignore */
1586
- class FormlyTemplateType extends FieldType {
1587
- constructor(sanitizer) {
1588
- super();
1589
- this.sanitizer = sanitizer;
1590
- 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];
1591
1328
  }
1592
- get template() {
1593
- if (this.field && this.field.template !== this.innerHtml.template) {
1594
- this.innerHtml = {
1595
- template: this.field.template,
1596
- content: this.props.safeHtml
1597
- ? this.sanitizer.bypassSecurityTrustHtml(this.field.template)
1598
- : this.field.template,
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,
1599
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;
1600
1391
  }
1601
- return this.innerHtml.content;
1602
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' }); }
1603
1395
  }
1604
- FormlyTemplateType.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplateType, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
1605
- FormlyTemplateType.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: FormlyTemplateType, selector: "formly-template", usesInheritance: true, ngImport: i0, template: `<div [innerHtml]="template"></div>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1606
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplateType, decorators: [{
1607
- type: Component,
1608
- args: [{
1609
- selector: 'formly-template',
1610
- template: `<div [innerHtml]="template"></div>`,
1611
- changeDetection: ChangeDetectionStrategy.OnPush,
1612
- }]
1613
- }], ctorParameters: function () { return [{ type: i1.DomSanitizer }]; } });
1396
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyConfig, decorators: [{
1397
+ type: Injectable,
1398
+ args: [{ providedIn: 'root' }]
1399
+ }] });
1614
1400
 
1615
- function evalStringExpression(expression, argNames) {
1616
- try {
1617
- return Function(...argNames, `return ${expression};`);
1401
+ class FormlyTemplate {
1402
+ constructor(ref) {
1403
+ this.ref = ref;
1618
1404
  }
1619
- catch (error) {
1620
- console.error(error);
1405
+ ngOnChanges() {
1406
+ this.name = this.name || 'formly-group';
1621
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 }); }
1622
1410
  }
1623
- function evalExpression(expression, thisArg, argVal) {
1624
- if (typeof expression === 'function') {
1625
- return expression.apply(thisArg, argVal);
1626
- }
1627
- else {
1628
- return expression ? true : false;
1629
- }
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 }); }
1630
1422
  }
1423
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyFieldTemplates, decorators: [{
1424
+ type: Injectable
1425
+ }] });
1631
1426
 
1632
- class FieldExpressionExtension {
1633
- onPopulate(field) {
1634
- if (field._expressions) {
1635
- 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;
1636
1437
  }
1637
- // cache built expression
1638
- defineHiddenProp(field, '_expressions', {});
1639
- observe(field, ['hide'], ({ currentValue, firstChange }) => {
1640
- defineHiddenProp(field, '_hide', !!currentValue);
1641
- if (!firstChange || (firstChange && currentValue === true)) {
1642
- field.props.hidden = currentValue;
1643
- field.options._hiddenFieldsForCheck.push({ field });
1644
- }
1645
- });
1646
- if (field.hideExpression) {
1647
- observe(field, ['hideExpression'], ({ currentValue: expr }) => {
1648
- field._expressions.hide = this.parseExpressions(field, 'hide', typeof expr === 'boolean' ? () => expr : expr);
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
+ }
1649
1504
  });
1650
1505
  }
1651
- const evalExpr = (key, expr) => {
1652
- if (typeof expr === 'string' || isFunction(expr)) {
1653
- field._expressions[key] = this.parseExpressions(field, key, expr);
1506
+ else if (f?.type) {
1507
+ const inlineType = this.form?.templates?.find((ref) => ref.name === f.type);
1508
+ let ref;
1509
+ if (inlineType) {
1510
+ ref = containerRef.createEmbeddedView(inlineType.ref, { $implicit: f });
1654
1511
  }
1655
- else if (expr instanceof Observable) {
1656
- field._expressions[key] = {
1657
- value$: expr.pipe(tap((v) => {
1658
- this.evalExpr(field, key, v);
1659
- field.options._detectChanges(field);
1660
- })),
1661
- };
1512
+ else {
1513
+ const { component } = this.config.getType(f.type, true);
1514
+ ref = containerRef.createComponent(component);
1662
1515
  }
1663
- };
1664
- field.expressions = field.expressions || {};
1665
- for (const key of Object.keys(field.expressions)) {
1666
- observe(field, ['expressions', key], ({ currentValue: expr }) => {
1667
- evalExpr(key, isFunction(expr) ? (...args) => expr(field, args[3]) : expr);
1668
- });
1669
- }
1670
- field.expressionProperties = field.expressionProperties || {};
1671
- for (const key of Object.keys(field.expressionProperties)) {
1672
- observe(field, ['expressionProperties', key], ({ currentValue }) => evalExpr(key, currentValue));
1516
+ this.attachComponentRef(ref, f);
1673
1517
  }
1674
1518
  }
1675
- postPopulate(field) {
1676
- if (field.parent) {
1677
- 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);
1678
1523
  }
1679
- if (!field.options.checkExpressions) {
1680
- let checkLocked = false;
1681
- field.options.checkExpressions = (f, ignoreCache) => {
1682
- if (checkLocked) {
1683
- return;
1684
- }
1685
- checkLocked = true;
1686
- const fieldChanged = this.checkExpressions(f, ignoreCache);
1687
- const options = field.options;
1688
- options._hiddenFieldsForCheck
1689
- .sort((f) => (f.field.hide ? -1 : 1))
1690
- .forEach((f) => this.changeHideState(f.field, f.field.hide ?? f.default, !ignoreCache));
1691
- options._hiddenFieldsForCheck = [];
1692
- if (fieldChanged) {
1693
- this.checkExpressions(field);
1694
- }
1695
- checkLocked = false;
1696
- };
1697
- field.options._checkField = (f, ignoreCache) => {
1698
- console.warn(`Formly: 'options._checkField' is deprecated since v6.0, use 'options.checkExpressions' instead.`);
1699
- field.options.checkExpressions(f, ignoreCache);
1700
- };
1701
- }
1702
- }
1703
- parseExpressions(field, path, expr) {
1704
- let parentExpression;
1705
- if (field.parent && ['hide', 'props.disabled'].includes(path)) {
1706
- const rootValue = (f) => {
1707
- return path === 'hide' ? f.hide : f.props.disabled;
1708
- };
1709
- parentExpression = () => {
1710
- let root = field.parent;
1711
- while (root.parent && !rootValue(root)) {
1712
- root = root.parent;
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());
1713
1530
  }
1714
- return rootValue(root);
1715
- };
1531
+ }
1716
1532
  }
1717
- expr = expr || (() => false);
1718
- if (typeof expr === 'string') {
1719
- expr = evalStringExpression(expr, ['model', 'formState', 'field']);
1533
+ if (name === 'onChanges' && changes.field) {
1534
+ this.resetRefs(changes.field.previousValue);
1535
+ this.render();
1720
1536
  }
1721
- let currentValue;
1722
- return {
1723
- callback: (ignoreCache) => {
1724
- try {
1725
- const exprValue = evalExpression(parentExpression ? (...args) => parentExpression(field) || expr(...args) : expr, { field }, [field.model, field.options.formState, field, ignoreCache]);
1726
- if (ignoreCache ||
1727
- (currentValue !== exprValue &&
1728
- (!isObject(exprValue) ||
1729
- isObservable(exprValue) ||
1730
- JSON.stringify(exprValue) !== JSON.stringify(currentValue)))) {
1731
- currentValue = exprValue;
1732
- this.evalExpr(field, path, exprValue);
1733
- return true;
1734
- }
1735
- return false;
1736
- }
1737
- catch (error) {
1738
- error.message = `[Formly Error] [Expression "${path}"] ${error.message}`;
1739
- throw error;
1740
- }
1741
- },
1742
- };
1743
1537
  }
1744
- checkExpressions(field, ignoreCache = false) {
1745
- if (!field) {
1746
- return false;
1747
- }
1748
- let fieldChanged = false;
1749
- if (field._expressions) {
1750
- for (const key of Object.keys(field._expressions)) {
1751
- field._expressions[key].callback?.(ignoreCache) && (fieldChanged = true);
1752
- }
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 });
1753
1543
  }
1754
- field.fieldGroup?.forEach((f) => this.checkExpressions(f, ignoreCache) && (fieldChanged = true));
1755
- return fieldChanged;
1756
1544
  }
1757
- changeDisabledState(field, value) {
1758
- if (field.fieldGroup) {
1759
- field.fieldGroup
1760
- .filter((f) => !f._expressions.hasOwnProperty('props.disabled'))
1761
- .forEach((f) => this.changeDisabledState(f, value));
1762
- }
1763
- if (hasKey(field) && field.props.disabled !== value) {
1764
- field.props.disabled = value;
1545
+ render() {
1546
+ if (!this.field) {
1547
+ return;
1765
1548
  }
1766
- }
1767
- changeHideState(field, hide, resetOnHide) {
1768
- if (field.fieldGroup) {
1769
- field.fieldGroup
1770
- .filter((f) => f && !f._expressions.hide)
1771
- .forEach((f) => this.changeHideState(f, hide, resetOnHide));
1549
+ // require Formly build
1550
+ if (!this.field.options) {
1551
+ this.detectFieldBuild = true;
1552
+ return;
1772
1553
  }
1773
- if (field.formControl && hasKey(field)) {
1774
- defineHiddenProp(field, '_hide', !!(hide || field.hide));
1775
- const c = field.formControl;
1776
- if (c._fields?.length > 1) {
1777
- updateValidity(c);
1778
- }
1779
- if (hide === true && (!c._fields || c._fields.every((f) => !!f._hide))) {
1780
- unregisterControl(field, true);
1781
- if (resetOnHide && field.resetOnHide) {
1782
- assignFieldValue(field, undefined);
1783
- field.formControl.reset({ value: undefined, disabled: field.formControl.disabled });
1784
- field.options.fieldChanges.next({ value: undefined, field, type: 'valueChanges' });
1785
- if (field.fieldGroup && field.formControl instanceof FormArray) {
1786
- field.fieldGroup.length = 0;
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' : '');
1787
1564
  }
1788
1565
  }
1789
- }
1790
- else if (hide === false) {
1791
- if (field.resetOnHide && !isUndefined(field.defaultValue) && isUndefined(getFieldValue(field))) {
1792
- 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
+ }
1793
1579
  }
1794
- registerControl(field, undefined, true);
1795
- if (field.resetOnHide && field.fieldArray && field.fieldGroup?.length !== field.model?.length) {
1796
- 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);
1797
1586
  }
1798
- }
1587
+ }),
1588
+ ];
1589
+ if (!isSignalRequired()) {
1590
+ ['touched', 'pristine', 'status'].forEach((prop) => this.hostObservers.push(observe(this.field, ['formControl', prop], ({ firstChange }) => !firstChange && markFieldForCheck(this.field))));
1799
1591
  }
1800
- if (field.options.fieldChanges) {
1801
- 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);
1802
1595
  }
1803
1596
  }
1804
- evalExpr(field, prop, value) {
1805
- if (prop.indexOf('model.') === 0) {
1806
- const key = prop.replace(/^model\./, ''), parent = field.fieldGroup ? field : field.parent;
1807
- let control = field?.key === key ? field.formControl : field.form.get(key);
1808
- if (!control && field.get(key)) {
1809
- control = field.get(key).formControl;
1810
- }
1811
- assignFieldValue({ key, parent, model: field.model }, value);
1812
- if (control && !(isNil(control.value) && isNil(value)) && control.value !== value) {
1813
- control.patchValue(value);
1597
+ resetRefs(field) {
1598
+ if (field) {
1599
+ if (field._localFields) {
1600
+ field._localFields = [];
1814
1601
  }
1815
- }
1816
- else {
1817
- try {
1818
- let target = field;
1819
- const paths = this._evalExpressionPath(field, prop);
1820
- const lastIndex = paths.length - 1;
1821
- for (let i = 0; i < lastIndex; i++) {
1822
- target = target[paths[i]];
1823
- }
1824
- target[paths[lastIndex]] = value;
1602
+ else {
1603
+ defineHiddenProp(this.field, '_localFields', []);
1825
1604
  }
1826
- catch (error) {
1827
- error.message = `[Formly Error] [Expression "${prop}"] ${error.message}`;
1828
- throw error;
1605
+ if (field._componentRefs) {
1606
+ field._componentRefs = field._componentRefs.filter((ref) => this.componentRefs.indexOf(ref) === -1);
1829
1607
  }
1830
- if (['templateOptions.disabled', 'props.disabled'].includes(prop) && hasKey(field)) {
1831
- this.changeDisabledState(field, value);
1608
+ else {
1609
+ defineHiddenProp(this.field, '_componentRefs', []);
1832
1610
  }
1833
1611
  }
1834
- this.emitExpressionChanges(field, prop, value);
1835
- }
1836
- emitExpressionChanges(field, property, value) {
1837
- if (!field.options.fieldChanges) {
1838
- return;
1839
- }
1840
- field.options.fieldChanges.next({
1841
- field,
1842
- type: 'expressionChanges',
1843
- property,
1844
- value,
1845
- });
1612
+ this.componentRefs = [];
1846
1613
  }
1847
- _evalExpressionPath(field, prop) {
1848
- if (field._expressions[prop] && field._expressions[prop].paths) {
1849
- return field._expressions[prop].paths;
1850
- }
1851
- let paths = [];
1852
- if (prop.indexOf('[') === -1) {
1853
- paths = prop.split('.');
1614
+ fieldChanges(field) {
1615
+ if (!field) {
1616
+ return () => { };
1854
1617
  }
1855
- else {
1856
- prop
1857
- .split(/[[\]]{1,2}/) // https://stackoverflow.com/a/20198206
1858
- .filter((p) => p)
1859
- .forEach((path) => {
1860
- const arrayPath = path.match(/['|"](.*?)['|"]/);
1861
- if (arrayPath) {
1862
- paths.push(arrayPath[1]);
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;
1863
1629
  }
1864
- else {
1865
- paths.push(...path.split('.').filter((p) => p));
1630
+ if (isObservable(currentValue.value$)) {
1631
+ currentValue.subscription = currentValue.value$.subscribe();
1866
1632
  }
1867
1633
  });
1634
+ subscribes.push(() => {
1635
+ if (field._expressions[key]?.subscription) {
1636
+ field._expressions[key].subscription.unsubscribe();
1637
+ }
1638
+ expressionObserver.unsubscribe();
1639
+ });
1868
1640
  }
1869
- if (field._expressions[prop]) {
1870
- 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());
1871
1644
  }
1872
- return paths;
1873
- }
1874
- }
1875
-
1876
- class FieldValidationExtension {
1877
- 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 = []) {
1878
1764
  this.config = config;
1765
+ this.injector = injector;
1766
+ this.viewContainerRef = viewContainerRef;
1767
+ this.parentForm = parentForm;
1768
+ configs.forEach((c) => config.addConfig(c));
1879
1769
  }
1880
- onPopulate(field) {
1881
- this.initFieldValidation(field, 'validators');
1882
- this.initFieldValidation(field, 'asyncValidators');
1770
+ buildForm(form, fieldGroup = [], model, options) {
1771
+ this.build({ fieldGroup, model, form, options });
1883
1772
  }
1884
- initFieldValidation(field, type) {
1885
- const validators = [];
1886
- if (type === 'validators' && !(field.hasOwnProperty('fieldGroup') && !hasKey(field))) {
1887
- 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`.');
1888
1776
  }
1889
- if (field[type]) {
1890
- for (const validatorName of Object.keys(field[type])) {
1891
- validatorName === 'validation'
1892
- ? validators.push(...field[type].validation.map((v) => this.wrapNgValidatorFn(field, v)))
1893
- : validators.push(this.wrapNgValidatorFn(field, field[type][validatorName], validatorName));
1894
- }
1777
+ if (!field.parent) {
1778
+ this._setOptions(field);
1895
1779
  }
1896
- defineHiddenProp(field, '_' + type, validators);
1897
- }
1898
- getPredefinedFieldValidation(field) {
1899
- let VALIDATORS = [];
1900
- FORMLY_VALIDATORS.forEach((opt) => observe(field, ['props', opt], ({ currentValue, firstChange }) => {
1901
- VALIDATORS = VALIDATORS.filter((o) => o !== opt);
1902
- if (opt === 'required' && currentValue != null && typeof currentValue !== 'boolean') {
1903
- console.warn(`Formly: Invalid prop 'required' of type '${typeof currentValue}', expected 'boolean' (Field:${field.key}).`);
1904
- }
1905
- if (currentValue != null && currentValue !== false) {
1906
- VALIDATORS.push(opt);
1907
- }
1908
- if (!firstChange && field.formControl) {
1909
- updateValidity(field.formControl);
1910
- }
1911
- }));
1912
- return (control) => {
1913
- if (VALIDATORS.length === 0) {
1914
- return null;
1915
- }
1916
- return Validators.compose(VALIDATORS.map((opt) => () => {
1917
- const value = field.props[opt];
1918
- switch (opt) {
1919
- case 'required':
1920
- return Validators.required(control);
1921
- case 'pattern':
1922
- return Validators.pattern(value)(control);
1923
- case 'minLength':
1924
- const minLengthResult = Validators.minLength(value)(control);
1925
- const minLengthKey = this.config.getValidatorMessage('minlength') || field.validation?.messages?.minlength
1926
- ? 'minlength'
1927
- : 'minLength';
1928
- return minLengthResult ? { [minLengthKey]: minLengthResult.minlength } : null;
1929
- case 'maxLength':
1930
- const maxLengthResult = Validators.maxLength(value)(control);
1931
- const maxLengthKey = this.config.getValidatorMessage('maxlength') || field.validation?.messages?.maxlength
1932
- ? 'maxlength'
1933
- : 'maxLength';
1934
- return maxLengthResult ? { [maxLengthKey]: maxLengthResult.maxlength } : null;
1935
- case 'min':
1936
- return Validators.min(value)(control);
1937
- case 'max':
1938
- return Validators.max(value)(control);
1939
- default:
1940
- return null;
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 });
1941
1790
  }
1942
- }))(control);
1943
- };
1791
+ options.checkExpressions?.(field, true);
1792
+ options._detectChanges?.(field);
1793
+ }
1794
+ });
1944
1795
  }
1945
- wrapNgValidatorFn(field, validator, validatorName) {
1946
- let validatorOption;
1947
- if (typeof validator === 'string') {
1948
- validatorOption = clone(this.config.getValidator(validator));
1796
+ _build(field) {
1797
+ if (!field) {
1798
+ return;
1949
1799
  }
1950
- if (typeof validator === 'object' && validator.name) {
1951
- validatorOption = clone(this.config.getValidator(validator.name));
1952
- if (validator.options) {
1953
- validatorOption.options = validator.options;
1954
- }
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);
1955
1813
  }
1956
- if (typeof validator === 'object' && validator.expression) {
1957
- const { expression, ...options } = validator;
1958
- validatorOption = {
1959
- name: validatorName,
1960
- validation: expression,
1961
- options: Object.keys(options).length > 0 ? options : null,
1962
- };
1814
+ if (!options._injector) {
1815
+ defineHiddenProp(options, '_injector', this.injector);
1963
1816
  }
1964
- if (typeof validator === 'function') {
1965
- validatorOption = {
1966
- name: validatorName,
1967
- 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;
1968
1825
  };
1969
1826
  }
1970
- return (control) => {
1971
- const errors = validatorOption.validation(control, field, validatorOption.options);
1972
- if (isPromise(errors)) {
1973
- return errors.then((v) => this.handleResult(field, validatorName ? !!v : v, validatorOption));
1974
- }
1975
- if (isObservable(errors)) {
1976
- 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
+ });
1977
1835
  }
1978
- 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();
1979
1954
  };
1980
1955
  }
1981
- handleResult(field, errors, { name, options }) {
1982
- if (typeof errors === 'boolean') {
1983
- errors = errors ? null : { [name]: options ? options : true };
1956
+ setField(field) {
1957
+ if (this.config.extras.immutable) {
1958
+ this.field = { ...this.field, ...clone(field) };
1984
1959
  }
1985
- const ctrl = field.formControl;
1986
- ctrl?._childrenErrors?.[name]?.();
1987
- if (isObject(errors)) {
1988
- Object.keys(errors).forEach((name) => {
1989
- const errorPath = errors[name].errorPath ? errors[name].errorPath : options?.errorPath;
1990
- const childCtrl = errorPath ? field.formControl.get(errorPath) : null;
1991
- if (childCtrl) {
1992
- const { errorPath: _errorPath, ...opts } = errors[name];
1993
- childCtrl.setErrors({ ...(childCtrl.errors || {}), [name]: opts });
1994
- !ctrl._childrenErrors && defineHiddenProp(ctrl, '_childrenErrors', {});
1995
- ctrl._childrenErrors[name] = () => {
1996
- const { [name]: _toDelete, ...childErrors } = childCtrl.errors || {};
1997
- childCtrl.setErrors(Object.keys(childErrors).length === 0 ? null : childErrors);
1998
- };
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)));
1999
2050
  }
2000
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);
2001
2077
  }
2002
- return errors;
2003
2078
  }
2004
- }
2005
-
2006
- class FieldFormExtension {
2007
- prePopulate(field) {
2008
- if (!this.root) {
2009
- this.root = field;
2079
+ /**
2080
+ * We need to re-evaluate all the attributes on every change detection cycle, because
2081
+ * by using a HostBinding we run into certain edge cases. This means that whatever logic
2082
+ * is in here has to be super lean or we risk seriously damaging or destroying the performance.
2083
+ *
2084
+ * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1317
2085
+ * Material issue: https://github.com/angular/components/issues/14024
2086
+ */
2087
+ ngDoCheck() {
2088
+ if (!this.uiAttributes) {
2089
+ const element = this.elementRef.nativeElement;
2090
+ this.uiAttributes = [...FORMLY_VALIDATORS, 'tabindex', 'placeholder', 'readonly', 'disabled', 'step'].filter((attr) => !element.hasAttribute || !element.hasAttribute(attr));
2010
2091
  }
2011
- if (field.parent) {
2012
- Object.defineProperty(field, 'form', {
2013
- get: () => field.parent.formControl,
2014
- configurable: true,
2015
- });
2092
+ for (let i = 0; i < this.uiAttributes.length; i++) {
2093
+ const attr = this.uiAttributes[i];
2094
+ const value = this.props[attr];
2095
+ if (this.uiAttributesCache[attr] !== value &&
2096
+ (!this.props.attributes || !this.props.attributes.hasOwnProperty(attr.toLowerCase()))) {
2097
+ this.uiAttributesCache[attr] = value;
2098
+ if (value || value === 0) {
2099
+ this.setAttribute(attr, value === true ? attr : `${value}`);
2100
+ }
2101
+ else {
2102
+ this.removeAttribute(attr);
2103
+ }
2104
+ }
2016
2105
  }
2017
2106
  }
2018
- onPopulate(field) {
2019
- if (field.hasOwnProperty('fieldGroup') && !hasKey(field)) {
2020
- defineHiddenProp(field, 'formControl', field.form);
2107
+ ngOnDestroy() {
2108
+ this.uiEvents.listeners.forEach((listener) => listener());
2109
+ this.detachElementRef(this.field);
2110
+ this.focusObserver?.unsubscribe();
2111
+ }
2112
+ toggleFocus(value) {
2113
+ const element = this.fieldAttrElements ? this.fieldAttrElements[0] : null;
2114
+ if (!element || !element.nativeElement.focus) {
2115
+ return;
2021
2116
  }
2022
- else {
2023
- this.addFormControl(field);
2117
+ const isFocused = !!this.document.activeElement &&
2118
+ this.fieldAttrElements.some(({ nativeElement }) => this.document.activeElement === nativeElement || nativeElement.contains(this.document.activeElement));
2119
+ if (value && !isFocused) {
2120
+ Promise.resolve().then(() => element.nativeElement.focus());
2121
+ }
2122
+ else if (!value && isFocused) {
2123
+ Promise.resolve().then(() => element.nativeElement.blur());
2024
2124
  }
2025
2125
  }
2026
- postPopulate(field) {
2027
- if (this.root !== field) {
2126
+ onFocus($event) {
2127
+ this.focusObserver?.setValue(true);
2128
+ this.props.focus?.(this.field, $event);
2129
+ }
2130
+ onBlur($event) {
2131
+ this.focusObserver?.setValue(false);
2132
+ this.props.blur?.(this.field, $event);
2133
+ }
2134
+ // handle custom `change` event, for regular ones rely on DOM listener
2135
+ onHostChange($event) {
2136
+ if ($event instanceof Event) {
2028
2137
  return;
2029
2138
  }
2030
- this.root = null;
2031
- const markForCheck = this.setValidators(field);
2032
- if (markForCheck && field.parent) {
2033
- let parent = field.parent;
2034
- while (parent) {
2035
- if (hasKey(parent) || !parent.parent) {
2036
- updateValidity(parent.formControl, true);
2037
- }
2038
- parent = parent.parent;
2039
- }
2040
- }
2139
+ this.onChange($event);
2041
2140
  }
2042
- addFormControl(field) {
2043
- let control = findControl(field);
2044
- if (field.fieldArray) {
2141
+ onChange($event) {
2142
+ this.props.change?.(this.field, $event);
2143
+ this.field.formControl?.markAsDirty();
2144
+ }
2145
+ attachElementRef(f) {
2146
+ if (!f) {
2045
2147
  return;
2046
2148
  }
2047
- if (!control) {
2048
- const controlOptions = { updateOn: field.modelOptions.updateOn };
2049
- if (field.fieldGroup) {
2050
- control = new FormGroup({}, controlOptions);
2051
- }
2052
- else {
2053
- const value = hasKey(field) ? getFieldValue(field) : field.defaultValue;
2054
- control = new FormControl({ value, disabled: !!field.props.disabled }, { ...controlOptions, initialValueIsDefault: true });
2055
- }
2149
+ if (f['_elementRefs']?.indexOf(this.elementRef) === -1) {
2150
+ f['_elementRefs'].push(this.elementRef);
2056
2151
  }
2057
2152
  else {
2058
- if (control instanceof FormControl) {
2059
- const value = hasKey(field) ? getFieldValue(field) : field.defaultValue;
2060
- control.defaultValue = value;
2061
- }
2153
+ defineHiddenProp(f, '_elementRefs', [this.elementRef]);
2062
2154
  }
2063
- registerControl(field, control);
2064
2155
  }
2065
- setValidators(field, disabled = false) {
2066
- if (disabled === false && hasKey(field) && field.props?.disabled) {
2067
- disabled = true;
2068
- }
2069
- let markForCheck = false;
2070
- field.fieldGroup?.forEach((f) => f && this.setValidators(f, disabled) && (markForCheck = true));
2071
- if (hasKey(field) || !field.parent || (!hasKey(field) && !field.fieldGroup)) {
2072
- const { formControl: c } = field;
2073
- if (c) {
2074
- if (hasKey(field) && c instanceof FormControl) {
2075
- if (disabled && c.enabled) {
2076
- c.disable({ emitEvent: false, onlySelf: true });
2077
- markForCheck = true;
2078
- }
2079
- if (!disabled && c.disabled) {
2080
- c.enable({ emitEvent: false, onlySelf: true });
2081
- markForCheck = true;
2082
- }
2083
- }
2084
- if (null === c.validator && this.hasValidators(field, '_validators')) {
2085
- c.setValidators(() => {
2086
- const v = Validators.compose(this.mergeValidators(field, '_validators'));
2087
- return v ? v(c) : null;
2088
- });
2089
- markForCheck = true;
2090
- }
2091
- if (null === c.asyncValidator && this.hasValidators(field, '_asyncValidators')) {
2092
- c.setAsyncValidators(() => {
2093
- const v = Validators.composeAsync(this.mergeValidators(field, '_asyncValidators'));
2094
- return v ? v(c) : of(null);
2095
- });
2096
- markForCheck = true;
2097
- }
2098
- if (markForCheck) {
2099
- updateValidity(c, true);
2100
- // update validity of `FormGroup` instance created by field with nested key.
2101
- let parent = c.parent;
2102
- for (let i = 1; i < getKeyPath(field).length; i++) {
2103
- if (parent) {
2104
- updateValidity(parent, true);
2105
- parent = parent.parent;
2106
- }
2107
- }
2108
- }
2109
- }
2156
+ detachElementRef(f) {
2157
+ const index = f?.['_elementRefs'] ? this.fieldAttrElements.indexOf(this.elementRef) : -1;
2158
+ if (index !== -1) {
2159
+ f['_elementRefs'].splice(index, 1);
2110
2160
  }
2111
- return markForCheck;
2112
2161
  }
2113
- hasValidators(field, type) {
2114
- const c = field.formControl;
2115
- if (c?._fields?.length > 1 && c._fields.some((f) => f[type].length > 0)) {
2116
- return true;
2117
- }
2118
- else if (field[type].length > 0) {
2119
- return true;
2120
- }
2121
- return field.fieldGroup?.some((f) => f?.fieldGroup && !hasKey(f) && this.hasValidators(f, type));
2162
+ setAttribute(attr, value) {
2163
+ this.renderer.setAttribute(this.elementRef.nativeElement, attr, value);
2122
2164
  }
2123
- mergeValidators(field, type) {
2124
- const validators = [];
2125
- const c = field.formControl;
2126
- if (c?._fields?.length > 1) {
2127
- c._fields
2128
- .filter((f) => !f._hide)
2129
- .forEach((f) => validators.push(...f[type]));
2130
- }
2131
- else if (field[type]) {
2132
- validators.push(...field[type]);
2133
- }
2134
- if (field.fieldGroup) {
2135
- field.fieldGroup
2136
- .filter((f) => f?.fieldGroup && !hasKey(f))
2137
- .forEach((f) => validators.push(...this.mergeValidators(f, type)));
2165
+ removeAttribute(attr) {
2166
+ this.renderer.removeAttribute(this.elementRef.nativeElement, attr);
2167
+ }
2168
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "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
+ }
2138
2233
  }
2139
- return validators;
2234
+ return undefined;
2140
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 }); }
2141
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
+ }] } });
2142
2249
 
2143
- class CoreExtension {
2144
- constructor(config) {
2145
- this.config = config;
2146
- this.formId = 0;
2147
- }
2148
- prePopulate(field) {
2149
- const root = field.parent;
2150
- this.initRootOptions(field);
2151
- this.initFieldProps(field);
2152
- if (root) {
2153
- Object.defineProperty(field, 'options', { get: () => root.options, configurable: true });
2154
- Object.defineProperty(field, 'model', {
2155
- get: () => (hasKey(field) && field.fieldGroup ? getFieldValue(field) : root.model),
2156
- configurable: true,
2157
- });
2250
+ class FieldArrayType extends FieldType {
2251
+ onPopulate(field) {
2252
+ if (hasKey(field)) {
2253
+ const control = findControl(field);
2254
+ registerControl(field, control ? control : new UntypedFormArray([], { updateOn: field.modelOptions.updateOn }));
2255
+ }
2256
+ field.fieldGroup = field.fieldGroup || [];
2257
+ const length = Array.isArray(field.model) ? field.model.length : 0;
2258
+ if (field.fieldGroup.length > length) {
2259
+ for (let i = field.fieldGroup.length - 1; i >= length; --i) {
2260
+ unregisterControl(field.fieldGroup[i], true);
2261
+ field.fieldGroup.splice(i, 1);
2262
+ }
2263
+ }
2264
+ for (let i = field.fieldGroup.length; i < length; i++) {
2265
+ const f = { ...clone(typeof field.fieldArray === 'function' ? field.fieldArray(field) : field.fieldArray) };
2266
+ if (f.key !== null) {
2267
+ f.key = `${i}`;
2268
+ }
2269
+ field.fieldGroup.push(f);
2158
2270
  }
2159
- Object.defineProperty(field, 'get', {
2160
- value: (key) => getField(field, key),
2161
- configurable: true,
2162
- });
2163
- this.getFieldComponentInstance(field).prePopulate?.(field);
2164
2271
  }
2165
- onPopulate(field) {
2166
- this.initFieldOptions(field);
2167
- this.getFieldComponentInstance(field).onPopulate?.(field);
2168
- if (field.fieldGroup) {
2169
- field.fieldGroup.forEach((f, index) => {
2170
- if (f) {
2171
- Object.defineProperty(f, 'parent', { get: () => field, configurable: true });
2172
- Object.defineProperty(f, 'index', { get: () => index, configurable: true });
2173
- }
2174
- this.formId++;
2175
- });
2272
+ add(i, initialModel, { markAsDirty } = { markAsDirty: true }) {
2273
+ markAsDirty && this.formControl.markAsDirty();
2274
+ i = i == null ? this.field.fieldGroup.length : i;
2275
+ if (!this.model) {
2276
+ assignFieldValue(this.field, []);
2176
2277
  }
2278
+ this.model.splice(i, 0, initialModel ? clone(initialModel) : undefined);
2279
+ this.markFieldForCheck(this.field.fieldGroup[i]);
2280
+ this._build();
2177
2281
  }
2178
- postPopulate(field) {
2179
- this.getFieldComponentInstance(field).postPopulate?.(field);
2282
+ remove(i, { markAsDirty } = { markAsDirty: true }) {
2283
+ markAsDirty && this.formControl.markAsDirty();
2284
+ this.model.splice(i, 1);
2285
+ const field = this.field.fieldGroup[i];
2286
+ this.field.fieldGroup.splice(i, 1);
2287
+ this.field.fieldGroup.forEach((f, key) => this.updateArrayElementKey(f, `${key}`));
2288
+ unregisterControl(field, true);
2289
+ this._build();
2180
2290
  }
2181
- initFieldProps(field) {
2182
- field.props ?? (field.props = field.templateOptions);
2183
- Object.defineProperty(field, 'templateOptions', {
2184
- get: () => field.props,
2185
- set: (props) => (field.props = props),
2186
- configurable: true,
2291
+ _build() {
2292
+ const fields = this.field.formControl._fields ?? [this.field];
2293
+ fields.forEach((f) => this.options.build(f));
2294
+ this.options.fieldChanges.next({
2295
+ field: this.field,
2296
+ value: getFieldValue(this.field),
2297
+ type: 'valueChanges',
2187
2298
  });
2188
2299
  }
2189
- initRootOptions(field) {
2190
- if (field.parent) {
2300
+ updateArrayElementKey(f, newKey) {
2301
+ if (hasKey(f)) {
2302
+ f.key = newKey;
2191
2303
  return;
2192
2304
  }
2193
- const options = field.options;
2194
- field.options.formState = field.options.formState || {};
2195
- if (!options.showError) {
2196
- options.showError = this.config.extras.showError;
2197
- }
2198
- if (!options.fieldChanges) {
2199
- defineHiddenProp(options, 'fieldChanges', new Subject());
2305
+ if (!f.fieldGroup?.length) {
2306
+ return;
2200
2307
  }
2201
- if (!options._hiddenFieldsForCheck) {
2202
- options._hiddenFieldsForCheck = [];
2308
+ for (let i = 0; i < f.fieldGroup.length; i++) {
2309
+ this.updateArrayElementKey(f.fieldGroup[i], newKey);
2203
2310
  }
2204
- options._markForCheck = (f) => {
2205
- console.warn(`Formly: 'options._markForCheck' is deprecated since v6.0, use 'options.detectChanges' instead.`);
2206
- options.detectChanges(f);
2207
- };
2208
- options._detectChanges = (f) => {
2209
- if (f._componentRefs) {
2210
- markFieldForCheck(f);
2211
- }
2212
- f.fieldGroup?.forEach((f) => f && options._detectChanges(f));
2213
- };
2214
- options.detectChanges = (f) => {
2215
- f.options.checkExpressions?.(f);
2216
- options._detectChanges(f);
2217
- };
2218
- options.resetModel = (model) => {
2219
- model = clone(model ?? options._initialModel);
2220
- if (field.model) {
2221
- Object.keys(field.model).forEach((k) => delete field.model[k]);
2222
- Object.assign(field.model, model || {});
2223
- }
2224
- if (!isSignalRequired()) {
2225
- observe(options, ['parentForm', 'submitted']).setValue(false, false);
2226
- }
2227
- options.build(field);
2228
- field.form.reset(field.model);
2229
- };
2230
- options.updateInitialValue = (model) => (options._initialModel = clone(model ?? field.model));
2231
- field.options.updateInitialValue();
2232
2311
  }
2233
- initFieldOptions(field) {
2234
- reverseDeepMerge(field, {
2235
- id: getFieldId(`formly_${this.formId}`, field, field.index),
2236
- hooks: {},
2237
- modelOptions: {},
2238
- validation: { messages: {} },
2239
- props: !field.type || !hasKey(field)
2240
- ? {}
2241
- : {
2242
- label: '',
2243
- placeholder: '',
2244
- disabled: false,
2245
- },
2246
- });
2247
- if (this.config.extras.resetFieldOnHide && field.resetOnHide !== false) {
2248
- field.resetOnHide = true;
2249
- }
2250
- if (field.type !== 'formly-template' &&
2251
- (field.template || field.expressions?.template || field.expressionProperties?.template)) {
2252
- field.type = 'formly-template';
2253
- }
2254
- if (!field.type && field.fieldGroup) {
2255
- field.type = 'formly-group';
2256
- }
2257
- if (field.type) {
2258
- this.config.getMergedField(field);
2259
- }
2260
- if (hasKey(field) &&
2261
- !isUndefined(field.defaultValue) &&
2262
- isUndefined(getFieldValue(field)) &&
2263
- !isHiddenField(field)) {
2264
- assignFieldValue(field, field.defaultValue);
2312
+ markFieldForCheck(f) {
2313
+ if (!f) {
2314
+ return;
2265
2315
  }
2266
- field.wrappers = field.wrappers || [];
2267
- }
2268
- getFieldComponentInstance(field) {
2269
- const componentRefInstance = () => {
2270
- let componentRef = this.config.resolveFieldTypeRef(field);
2271
- const fieldComponentRef = field._componentRefs?.slice(-1)[0];
2272
- if (fieldComponentRef instanceof ComponentRef &&
2273
- fieldComponentRef?.componentType === componentRef?.componentType) {
2274
- componentRef = fieldComponentRef;
2275
- }
2276
- return componentRef?.instance;
2277
- };
2278
- if (!field._proxyInstance) {
2279
- defineHiddenProp(field, '_proxyInstance', new Proxy({}, {
2280
- get: (_, prop) => componentRefInstance()?.[prop],
2281
- set: (_, prop, value) => (componentRefInstance()[prop] = value),
2282
- }));
2316
+ f.fieldGroup?.forEach((c) => this.markFieldForCheck(c));
2317
+ if (f.hide === false) {
2318
+ this.options._hiddenFieldsForCheck.push({ field: f });
2283
2319
  }
2284
- return field._proxyInstance;
2285
2320
  }
2321
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "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 }); }
2286
2323
  }
2324
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FieldArrayType, decorators: [{
2325
+ type: Directive
2326
+ }] });
2287
2327
 
2288
- function defaultFormlyConfig(config) {
2289
- return {
2290
- types: [
2291
- { name: 'formly-group', component: FormlyGroup },
2292
- { name: 'formly-template', component: FormlyTemplateType },
2293
- ],
2294
- extensions: [
2295
- { name: 'core', extension: new CoreExtension(config), priority: -250 },
2296
- { name: 'field-validation', extension: new FieldValidationExtension(config), priority: -200 },
2297
- { name: 'field-form', extension: new FieldFormExtension(), priority: -150 },
2298
- { name: 'field-expression', extension: new FieldExpressionExtension(), priority: -100 },
2299
- ],
2300
- };
2328
+ class FieldWrapper extends FieldType {
2329
+ set _formlyControls(_) { }
2330
+ set _staticContent(content) {
2331
+ this.fieldComponent = content;
2332
+ }
2333
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "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 }); }
2301
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
+
2302
2346
  class FormlyModule {
2303
- constructor(configService, configs = []) {
2304
- if (!configs) {
2305
- return;
2306
- }
2307
- configs.forEach((config) => configService.addConfig(config));
2308
- }
2309
2347
  static forRoot(config = {}) {
2310
2348
  return {
2311
2349
  ngModule: FormlyModule,
2312
2350
  providers: [
2313
- { provide: FORMLY_CONFIG, multi: true, useFactory: defaultFormlyConfig, deps: [FormlyConfig] },
2351
+ { provide: FORMLY_CONFIG, multi: true, useFactory: withDefaultConfig, deps: [FormlyConfig] },
2314
2352
  { provide: FORMLY_CONFIG, useValue: config, multi: true },
2315
2353
  FormlyConfig,
2316
2354
  FormlyFormBuilder,
@@ -2321,43 +2359,50 @@ class FormlyModule {
2321
2359
  return {
2322
2360
  ngModule: FormlyModule,
2323
2361
  providers: [
2324
- { provide: FORMLY_CONFIG, multi: true, useFactory: defaultFormlyConfig, deps: [FormlyConfig] },
2362
+ { provide: FORMLY_CONFIG, multi: true, useFactory: withDefaultConfig, deps: [FormlyConfig] },
2325
2363
  { provide: FORMLY_CONFIG, useValue: config, multi: true },
2326
2364
  FormlyFormBuilder,
2327
2365
  ],
2328
2366
  };
2329
2367
  }
2368
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "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] }); }
2330
2382
  }
2331
- FormlyModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyModule, deps: [{ token: FormlyConfig }, { token: FORMLY_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.NgModule });
2332
- FormlyModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyModule, declarations: [FormlyTemplate,
2333
- FormlyForm,
2334
- FormlyField,
2335
- FormlyAttributes,
2336
- FormlyGroup,
2337
- FormlyValidationMessage,
2338
- FormlyTemplateType], imports: [CommonModule], exports: [FormlyTemplate, FormlyForm, FormlyField, FormlyAttributes, FormlyGroup, FormlyValidationMessage] });
2339
- FormlyModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyModule, imports: [[CommonModule]] });
2340
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyModule, decorators: [{
2383
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormlyModule, decorators: [{
2341
2384
  type: NgModule,
2342
2385
  args: [{
2343
2386
  declarations: [
2344
2387
  FormlyTemplate,
2345
- FormlyForm,
2346
- FormlyField,
2388
+ LegacyFormlyForm,
2389
+ LegacyFormlyField,
2347
2390
  FormlyAttributes,
2348
2391
  FormlyGroup,
2349
2392
  FormlyValidationMessage,
2350
2393
  FormlyTemplateType,
2351
2394
  ],
2352
- exports: [FormlyTemplate, FormlyForm, FormlyField, FormlyAttributes, FormlyGroup, FormlyValidationMessage],
2395
+ exports: [
2396
+ FormlyTemplate,
2397
+ LegacyFormlyForm,
2398
+ LegacyFormlyField,
2399
+ FormlyAttributes,
2400
+ FormlyGroup,
2401
+ FormlyValidationMessage,
2402
+ ],
2353
2403
  imports: [CommonModule],
2354
2404
  }]
2355
- }], ctorParameters: function () { return [{ type: FormlyConfig }, { type: undefined, decorators: [{
2356
- type: Optional
2357
- }, {
2358
- type: Inject,
2359
- args: [FORMLY_CONFIG]
2360
- }] }]; } });
2405
+ }] });
2361
2406
 
2362
2407
  /*
2363
2408
  * Public API Surface of core
@@ -2367,5 +2412,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImpo
2367
2412
  * Generated bundle index. Do not edit.
2368
2413
  */
2369
2414
 
2370
- export { FORMLY_CONFIG, FieldArrayType, FieldType, FieldWrapper, FormlyConfig, FormlyField, FormlyForm, FormlyFormBuilder, FormlyModule, FormlyAttributes as ɵFormlyAttributes, FormlyGroup as ɵFormlyGroup, FormlyTemplate as ɵFormlyTemplate, FormlyValidationMessage as ɵFormlyValidationMessage, clone as ɵclone, defineHiddenProp as ɵdefineHiddenProp, getFieldValue as ɵgetFieldValue, hasKey as ɵhasKey, observe as ɵobserve, reverseDeepMerge as ɵreverseDeepMerge };
2415
+ export { FORMLY_CONFIG, FieldArrayType, FieldType, FieldWrapper, FormlyConfig, FormlyField, FormlyForm, FormlyFormBuilder, FormlyModule, LegacyFormlyField, LegacyFormlyForm, provideFormlyConfig, provideFormlyCore, FormlyAttributes as ɵFormlyAttributes, FormlyGroup as ɵFormlyGroup, FormlyTemplate as ɵFormlyTemplate, FormlyValidationMessage as ɵFormlyValidationMessage, clone as ɵclone, defineHiddenProp as ɵdefineHiddenProp, getFieldValue as ɵgetFieldValue, hasKey as ɵhasKey, observe as ɵobserve, reverseDeepMerge as ɵreverseDeepMerge };
2371
2416
  //# sourceMappingURL=ngx-formly-core.mjs.map