@ngx-formly/core
Version:
Formly is a dynamic (JSON powered) form library for Angular that bring unmatched maintainability to your application's forms.
1 lines • 191 kB
Source Map (JSON)
{"version":3,"file":"ngx-formly-core.mjs","sources":["../../../../src/core/src/lib/utils.ts","../../../../src/core/src/lib/services/formly.config.ts","../../../../src/core/src/lib/services/formly.builder.ts","../../../../src/core/src/lib/extensions/field-form/utils.ts","../../../../src/core/src/lib/components/formly.template.ts","../../../../src/core/src/lib/components/formly.field.ts","../../../../src/core/src/lib/components/formly.form.ts","../../../../src/core/src/lib/templates/formly.attributes.ts","../../../../src/core/src/lib/templates/field.type.ts","../../../../src/core/src/lib/templates/formly.group.ts","../../../../src/core/src/lib/templates/formly.validation-message.ts","../../../../src/core/src/lib/templates/field-array.type.ts","../../../../src/core/src/lib/templates/field.wrapper.ts","../../../../src/core/src/lib/templates/field-template.type.ts","../../../../src/core/src/lib/extensions/field-expression/utils.ts","../../../../src/core/src/lib/extensions/field-expression/field-expression.ts","../../../../src/core/src/lib/extensions/field-validation/field-validation.ts","../../../../src/core/src/lib/extensions/field-form/field-form.ts","../../../../src/core/src/lib/extensions/core/core.ts","../../../../src/core/src/lib/core.module.ts","../../../../src/core/src/public_api.ts","../../../../src/core/src/ngx-formly-core.ts"],"sourcesContent":["import { FormlyFieldConfig } from './models';\nimport { isObservable } from 'rxjs';\nimport { AbstractControl } from '@angular/forms';\nimport { FormlyFieldConfigCache } from './models';\nimport { ChangeDetectorRef, ComponentRef, TemplateRef, Type } from '@angular/core';\n\nexport function disableTreeValidityCall(form: any, callback: Function) {\n const _updateTreeValidity = form._updateTreeValidity.bind(form);\n form._updateTreeValidity = () => {};\n callback();\n form._updateTreeValidity = _updateTreeValidity;\n}\n\nexport function getFieldId(formId: string, field: FormlyFieldConfig, index: string | number) {\n if (field.id) {\n return field.id;\n }\n let type = field.type;\n if (!type && field.template) {\n type = 'template';\n }\n\n if (type instanceof Type) {\n type = type.prototype.constructor.name;\n }\n\n return [formId, type, field.key, index].join('_');\n}\n\nexport function hasKey(field: FormlyFieldConfig) {\n return !isNil(field.key) && field.key !== '';\n}\n\nexport function getKeyPath(field: FormlyFieldConfigCache): string[] {\n if (!hasKey(field)) {\n return [];\n }\n\n /* We store the keyPath in the field for performance reasons. This function will be called frequently. */\n if (field._keyPath?.key !== field.key) {\n let path: (string | number)[] = [];\n if (typeof field.key === 'string') {\n const key = field.key.indexOf('[') === -1 ? field.key : field.key.replace(/\\[(\\w+)\\]/g, '.$1');\n path = key.indexOf('.') !== -1 ? key.split('.') : [key];\n } else if (Array.isArray(field.key)) {\n path = field.key.slice(0);\n } else {\n path = [`${field.key}`];\n }\n\n defineHiddenProp(field, '_keyPath', { key: field.key, path });\n }\n\n return field._keyPath.path.slice(0);\n}\n\nexport const FORMLY_VALIDATORS = ['required', 'pattern', 'minLength', 'maxLength', 'min', 'max'];\n\nexport function assignFieldValue(field: FormlyFieldConfigCache, value: any) {\n let paths = getKeyPath(field);\n if (paths.length === 0) {\n return;\n }\n\n let root = field;\n while (root.parent) {\n root = root.parent;\n paths = [...getKeyPath(root), ...paths];\n }\n\n if (value === undefined && field.resetOnHide) {\n const k = paths.pop();\n const m = paths.reduce((model, path) => model[path] || {}, root.model);\n delete m[k];\n return;\n }\n\n assignModelValue(root.model, paths, value);\n}\n\nexport function assignModelValue(model: any, paths: string[], value: any) {\n for (let i = 0; i < paths.length - 1; i++) {\n const path = paths[i];\n if (!model[path] || !isObject(model[path])) {\n model[path] = /^\\d+$/.test(paths[i + 1]) ? [] : {};\n }\n\n model = model[path];\n }\n\n model[paths[paths.length - 1]] = clone(value);\n}\n\nexport function getFieldValue(field: FormlyFieldConfig): any {\n let model = field.parent ? field.parent.model : field.model;\n for (const path of getKeyPath(field)) {\n if (!model) {\n return model;\n }\n model = model[path];\n }\n\n return model;\n}\n\nexport function reverseDeepMerge(dest: any, ...args: any[]) {\n args.forEach((src) => {\n for (const srcArg in src) {\n if (isNil(dest[srcArg]) || isBlankString(dest[srcArg])) {\n dest[srcArg] = clone(src[srcArg]);\n } else if (objAndSameType(dest[srcArg], src[srcArg])) {\n reverseDeepMerge(dest[srcArg], src[srcArg]);\n }\n }\n });\n return dest;\n}\n\n// check a value is null or undefined\nexport function isNil(value: any) {\n return value == null;\n}\n\nexport function isUndefined(value: any) {\n return value === undefined;\n}\n\nexport function isBlankString(value: any) {\n return value === '';\n}\n\nexport function isFunction(value: any) {\n return typeof value === 'function';\n}\n\nexport function objAndSameType(obj1: any, obj2: any) {\n return (\n isObject(obj1) &&\n isObject(obj2) &&\n Object.getPrototypeOf(obj1) === Object.getPrototypeOf(obj2) &&\n !(Array.isArray(obj1) || Array.isArray(obj2))\n );\n}\n\nexport function isObject(x: any) {\n return x != null && typeof x === 'object';\n}\n\nexport function isPromise(obj: any): obj is Promise<any> {\n return !!obj && typeof obj.then === 'function';\n}\n\nexport function clone(value: any): any {\n if (\n !isObject(value) ||\n isObservable(value) ||\n value instanceof TemplateRef ||\n /* instanceof SafeHtmlImpl */ value.changingThisBreaksApplicationSecurity ||\n ['RegExp', 'FileList', 'File', 'Blob'].indexOf(value.constructor.name) !== -1\n ) {\n return value;\n }\n\n if (value instanceof Set) {\n return new Set(value);\n }\n\n if (value instanceof Map) {\n return new Map(value);\n }\n\n // https://github.com/moment/moment/blob/master/moment.js#L252\n if (value._isAMomentObject && isFunction(value.clone)) {\n return value.clone();\n }\n\n if (value instanceof AbstractControl) {\n return null;\n }\n\n if (value instanceof Date) {\n return new Date(value.getTime());\n }\n\n if (Array.isArray(value)) {\n return value.slice(0).map((v) => clone(v));\n }\n\n // best way to clone a js object maybe\n // https://stackoverflow.com/questions/41474986/how-to-clone-a-javascript-es6-class-instance\n const proto = Object.getPrototypeOf(value);\n let c = Object.create(proto);\n c = Object.setPrototypeOf(c, proto);\n // need to make a deep copy so we dont use Object.assign\n // also Object.assign wont copy property descriptor exactly\n return Object.keys(value).reduce((newVal, prop) => {\n const propDesc = Object.getOwnPropertyDescriptor(value, prop);\n if (propDesc.get) {\n Object.defineProperty(newVal, prop, propDesc);\n } else {\n newVal[prop] = clone(value[prop]);\n }\n\n return newVal;\n }, c);\n}\n\nexport function defineHiddenProp(field: any, prop: string, defaultValue: any) {\n Object.defineProperty(field, prop, { enumerable: false, writable: true, configurable: true });\n field[prop] = defaultValue;\n}\n\ntype IObserveFn<T> = (change: { currentValue: T; previousValue?: T; firstChange: boolean }) => void;\nexport interface IObserver<T> {\n setValue: (value: T, emitEvent?: boolean) => void;\n unsubscribe: Function;\n}\ninterface IObserveTarget<T> {\n [prop: string]: any;\n _observers?: {\n [prop: string]: {\n value: T;\n onChange: IObserveFn<T>[];\n };\n };\n}\n\nexport function observeDeep<T = any>(source: IObserveTarget<T>, paths: string[], setFn: () => void): () => void {\n let observers: Function[] = [];\n\n const unsubscribe = () => {\n observers.forEach((observer) => observer());\n observers = [];\n };\n const observer = observe(source, paths, ({ firstChange, currentValue }) => {\n !firstChange && setFn();\n\n unsubscribe();\n if (isObject(currentValue) && currentValue.constructor.name === 'Object') {\n Object.keys(currentValue).forEach((prop) => {\n observers.push(observeDeep(source, [...paths, prop], setFn));\n });\n }\n });\n\n return () => {\n observer.unsubscribe();\n unsubscribe();\n };\n}\n\nexport function observe<T = any>(o: IObserveTarget<T>, paths: string[], setFn?: IObserveFn<T>): IObserver<T> {\n if (!o._observers) {\n defineHiddenProp(o, '_observers', {});\n }\n\n let target = o;\n for (let i = 0; i < paths.length - 1; i++) {\n if (!target[paths[i]] || !isObject(target[paths[i]])) {\n target[paths[i]] = /^\\d+$/.test(paths[i + 1]) ? [] : {};\n }\n target = target[paths[i]];\n }\n\n const key = paths[paths.length - 1];\n const prop = paths.join('.');\n if (!o._observers[prop]) {\n o._observers[prop] = { value: target[key], onChange: [] };\n }\n\n const state = o._observers[prop];\n if (target[key] !== state.value) {\n state.value = target[key];\n }\n\n if (setFn && state.onChange.indexOf(setFn) === -1) {\n state.onChange.push(setFn);\n setFn({ currentValue: state.value, firstChange: true });\n if (state.onChange.length >= 1 && isObject(target)) {\n const { enumerable } = Object.getOwnPropertyDescriptor(target, key) || { enumerable: true };\n Object.defineProperty(target, key, {\n enumerable,\n configurable: true,\n get: () => state.value,\n set: (currentValue) => {\n if (currentValue !== state.value) {\n const previousValue = state.value;\n state.value = currentValue;\n state.onChange.forEach((changeFn) => changeFn({ previousValue, currentValue, firstChange: false }));\n }\n },\n });\n }\n }\n\n return {\n setValue(currentValue: T, emitEvent = true) {\n if (currentValue === state.value) {\n return;\n }\n\n const previousValue = state.value;\n state.value = currentValue;\n state.onChange.forEach((changeFn) => {\n if (changeFn !== setFn && emitEvent) {\n changeFn({ previousValue, currentValue, firstChange: false });\n }\n });\n },\n unsubscribe() {\n state.onChange = state.onChange.filter((changeFn) => changeFn !== setFn);\n if (state.onChange.length === 0) {\n delete o._observers[prop];\n }\n },\n };\n}\n\nexport function getField(f: FormlyFieldConfig, key: FormlyFieldConfig['key']): FormlyFieldConfig {\n key = (Array.isArray(key) ? key.join('.') : key) as string;\n if (!f.fieldGroup) {\n return undefined;\n }\n\n for (let i = 0, len = f.fieldGroup.length; i < len; i++) {\n const c = f.fieldGroup[i];\n const k = (Array.isArray(c.key) ? c.key.join('.') : c.key) as string;\n if (k === key) {\n return c;\n }\n\n if (c.fieldGroup && (isNil(k) || key.indexOf(`${k}.`) === 0)) {\n const field = getField(c, isNil(k) ? key : key.slice(k.length + 1));\n if (field) {\n return field;\n }\n }\n }\n\n return undefined;\n}\n\nexport function markFieldForCheck(field: FormlyFieldConfigCache) {\n field._componentRefs?.forEach((ref) => {\n // NOTE: we cannot use ref.changeDetectorRef, see https://github.com/ngx-formly/ngx-formly/issues/2191\n if (ref instanceof ComponentRef) {\n const changeDetectorRef = ref.injector.get(ChangeDetectorRef);\n changeDetectorRef.markForCheck();\n } else {\n ref.markForCheck();\n }\n });\n}\n","import { Injectable, InjectionToken, ComponentRef, Type } from '@angular/core';\nimport { FieldType } from './../templates/field.type';\nimport { reverseDeepMerge, defineHiddenProp } from './../utils';\nimport {\n FormlyFieldConfig,\n FormlyFieldConfigCache,\n ConfigOption,\n TypeOption,\n ValidatorOption,\n WrapperOption,\n FormlyExtension,\n ValidationMessageOption,\n ExtensionOption,\n FormlyFieldConfigPresetProvider,\n} from '../models';\nimport { FieldWrapper } from '../templates/field.wrapper';\n\n/**\n * An InjectionToken for registering additional formly config options (types, wrappers ...).\n */\nexport const FORMLY_CONFIG = new InjectionToken<ConfigOption[]>('FORMLY_CONFIG');\ndeclare const ngDevMode: any;\n\n/**\n * Maintains list of formly config options. This can be used to register new field type.\n */\n@Injectable({ providedIn: 'root' })\nexport class FormlyConfig {\n types: { [name: string]: TypeOption } = {};\n validators: { [name: string]: ValidatorOption } = {};\n wrappers: { [name: string]: WrapperOption } = {};\n messages: { [name: string]: ValidationMessageOption['message'] } = {};\n\n extras: NonNullable<ConfigOption['extras']> = {\n checkExpressionOn: 'modelChange',\n lazyRender: true,\n resetFieldOnHide: true,\n renderFormlyFieldElement: true,\n showError(field: FieldType) {\n return (\n field.formControl?.invalid &&\n (field.formControl?.touched || field.options.parentForm?.submitted || !!field.field.validation?.show)\n );\n },\n };\n extensions: { [name: string]: FormlyExtension } = {};\n presets: { [name: string]: FormlyFieldConfig | FormlyFieldConfigPresetProvider } = {};\n\n private extensionsByPriority: Record<number, { [name: string]: FormlyExtension }> = {};\n\n addConfig(config: ConfigOption) {\n if (config.types) {\n config.types.forEach((type) => this.setType(type));\n }\n if (config.validators) {\n config.validators.forEach((validator) => this.setValidator(validator));\n }\n if (config.wrappers) {\n config.wrappers.forEach((wrapper) => this.setWrapper(wrapper));\n }\n if (config.validationMessages) {\n config.validationMessages.forEach((validation) => this.addValidatorMessage(validation.name, validation.message));\n }\n if (config.extensions) {\n this.setSortedExtensions(config.extensions);\n }\n if (config.extras) {\n this.extras = { ...this.extras, ...config.extras };\n }\n if (config.presets) {\n this.presets = {\n ...this.presets,\n ...config.presets.reduce((acc, curr) => ({ ...acc, [curr.name]: curr.config }), {}),\n };\n }\n }\n\n /**\n * Allows you to specify a custom type which you can use in your field configuration.\n * You can pass an object of options, or an array of objects of options.\n */\n setType(options: TypeOption | TypeOption[]) {\n if (Array.isArray(options)) {\n options.forEach((option) => this.setType(option));\n } else {\n if (!this.types[options.name]) {\n this.types[options.name] = <TypeOption>{ name: options.name };\n }\n\n (['component', 'extends', 'defaultOptions', 'wrappers'] as (keyof TypeOption)[]).forEach((prop) => {\n if (options.hasOwnProperty(prop)) {\n this.types[options.name][prop] = options[prop] as any;\n }\n });\n }\n }\n\n getType(name: FormlyFieldConfig['type'], throwIfNotFound = false): TypeOption {\n if (name instanceof Type) {\n return { component: name, name: name.prototype.constructor.name };\n }\n\n if (!this.types[name]) {\n if (throwIfNotFound) {\n throw new Error(\n `[Formly Error] The type \"${name}\" could not be found. Please make sure that is registered through the FormlyModule declaration.`,\n );\n }\n\n return null;\n }\n\n this.mergeExtendedType(name);\n\n return this.types[name];\n }\n\n /** @ignore */\n getMergedField(field: FormlyFieldConfig = {}): any {\n const type = this.getType(field.type);\n if (!type) {\n return;\n }\n\n if (type.defaultOptions) {\n reverseDeepMerge(field, type.defaultOptions);\n }\n\n const extendDefaults = type.extends && this.getType(type.extends).defaultOptions;\n if (extendDefaults) {\n reverseDeepMerge(field, extendDefaults);\n }\n\n if (field?.optionsTypes) {\n field.optionsTypes.forEach((option) => {\n const defaultOptions = this.getType(option).defaultOptions;\n if (defaultOptions) {\n reverseDeepMerge(field, defaultOptions);\n }\n });\n }\n\n const componentRef = this.resolveFieldTypeRef(field);\n if (componentRef?.instance?.defaultOptions) {\n reverseDeepMerge(field, componentRef.instance.defaultOptions);\n }\n\n if (!field.wrappers && type.wrappers) {\n field.wrappers = [...type.wrappers];\n }\n }\n\n /** @ignore @internal */\n resolveFieldTypeRef(field: FormlyFieldConfigCache = {}): ComponentRef<FieldType> {\n const type: TypeOption & { _componentRef?: ComponentRef<any> } = this.getType(field.type);\n if (!type) {\n return null;\n }\n\n if (!type.component || type._componentRef) {\n return type._componentRef;\n }\n\n const { _viewContainerRef, _injector } = field.options;\n if (!_viewContainerRef || !_injector) {\n return null;\n }\n\n const componentRef = _viewContainerRef.createComponent<FieldType>(type.component, { injector: _injector });\n defineHiddenProp(type, '_componentRef', componentRef);\n try {\n componentRef.destroy();\n } catch (e) {\n console.error(`An error occurred while destroying the Formly component type \"${field.type}\"`, e);\n }\n\n return type._componentRef;\n }\n\n setWrapper(options: WrapperOption) {\n this.wrappers[options.name] = options;\n if (options.types) {\n options.types.forEach((type) => {\n this.setTypeWrapper(type, options.name);\n });\n }\n }\n\n getWrapper(name: string | Type<FieldWrapper>): WrapperOption {\n if (name instanceof Type) {\n return { component: name, name: name.prototype.constructor.name };\n }\n\n if (!this.wrappers[name]) {\n throw new Error(\n `[Formly Error] The wrapper \"${name}\" could not be found. Please make sure that is registered through the FormlyModule declaration.`,\n );\n }\n\n return this.wrappers[name];\n }\n\n /** @ignore */\n setTypeWrapper(type: string, name: string) {\n if (!this.types[type]) {\n this.types[type] = <TypeOption>{};\n }\n if (!this.types[type].wrappers) {\n this.types[type].wrappers = [];\n }\n if (this.types[type].wrappers.indexOf(name) === -1) {\n this.types[type].wrappers.push(name);\n }\n }\n\n setValidator(options: ValidatorOption) {\n this.validators[options.name] = options;\n }\n\n getValidator(name: string): ValidatorOption {\n if (!this.validators[name]) {\n throw new Error(\n `[Formly Error] The validator \"${name}\" could not be found. Please make sure that is registered through the FormlyModule declaration.`,\n );\n }\n\n return this.validators[name];\n }\n\n addValidatorMessage(name: string, message: ValidationMessageOption['message']) {\n this.messages[name] = message;\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n const deprecated = { minlength: 'minLength', maxlength: 'maxLength' } as any;\n if (deprecated[name]) {\n console.warn(\n `Formly deprecation: passing validation messages key '${name}' is deprecated since v6.0, use '${deprecated[name]}' instead.`,\n );\n this.messages[deprecated[name]] = message;\n }\n }\n }\n\n getValidatorMessage(name: string) {\n return this.messages[name];\n }\n\n private setSortedExtensions(extensionOptions: ExtensionOption[]) {\n // insert new extensions, grouped by priority\n extensionOptions.forEach((extensionOption) => {\n const priority = extensionOption.priority ?? 1;\n this.extensionsByPriority[priority] = {\n ...this.extensionsByPriority[priority],\n [extensionOption.name]: extensionOption.extension,\n };\n });\n // flatten extensions object with sorted keys\n this.extensions = Object.keys(this.extensionsByPriority)\n .map(Number)\n .sort((a, b) => a - b)\n .reduce(\n (acc, prio) => ({\n ...acc,\n ...this.extensionsByPriority[prio],\n }),\n {},\n );\n }\n\n private mergeExtendedType(name: string) {\n if (!this.types[name].extends) {\n return;\n }\n\n const extendedType = this.getType(this.types[name].extends);\n if (!this.types[name].component) {\n this.types[name].component = extendedType.component;\n }\n\n if (!this.types[name].wrappers) {\n this.types[name].wrappers = extendedType.wrappers;\n }\n }\n}\n","import { Injectable, Injector, Optional, ViewContainerRef } from '@angular/core';\nimport { FormGroup, FormArray, FormGroupDirective } from '@angular/forms';\nimport { FormlyConfig } from './formly.config';\nimport { FormlyFieldConfig, FormlyFormOptions, FormlyFieldConfigCache } from '../models';\nimport { defineHiddenProp, observe, disableTreeValidityCall } from '../utils';\n\n@Injectable({ providedIn: 'root' })\nexport class FormlyFormBuilder {\n constructor(\n private config: FormlyConfig,\n private injector: Injector,\n @Optional() private viewContainerRef: ViewContainerRef,\n @Optional() private parentForm: FormGroupDirective,\n ) {}\n\n buildForm(form: FormGroup | FormArray, fieldGroup: FormlyFieldConfig[] = [], model: any, options: FormlyFormOptions) {\n this.build({ fieldGroup, model, form, options });\n }\n\n build(field: FormlyFieldConfig) {\n if (!this.config.extensions.core) {\n throw new Error('NgxFormly: missing `forRoot()` call. use `forRoot()` when registering the `FormlyModule`.');\n }\n\n if (!field.parent) {\n this._setOptions(field);\n }\n\n disableTreeValidityCall(field.form, () => {\n this._build(field);\n if (!field.parent) {\n const options = (field as FormlyFieldConfigCache).options;\n options.checkExpressions?.(field, true);\n options._detectChanges?.(field);\n }\n });\n }\n\n private _build(field: FormlyFieldConfigCache) {\n if (!field) {\n return;\n }\n\n const extensions = Object.values(this.config.extensions);\n extensions.forEach((extension) => extension.prePopulate?.(field));\n extensions.forEach((extension) => extension.onPopulate?.(field));\n field.fieldGroup?.forEach((f) => this._build(f));\n extensions.forEach((extension) => extension.postPopulate?.(field));\n }\n\n private _setOptions(field: FormlyFieldConfigCache) {\n field.form = field.form || new FormGroup({});\n field.model = field.model || {};\n field.options = field.options || {};\n const options = field.options;\n\n if (!options._viewContainerRef) {\n defineHiddenProp(options, '_viewContainerRef', this.viewContainerRef);\n }\n\n if (!options._injector) {\n defineHiddenProp(options, '_injector', this.injector);\n }\n\n if (!options.build) {\n options._buildForm = () => {\n console.warn(`Formly: 'options._buildForm' is deprecated since v6.0, use 'options.build' instead.`);\n this.build(field);\n };\n\n options.build = (f: FormlyFieldConfig = field) => {\n this.build(f);\n\n return f;\n };\n }\n\n if (!options.parentForm && this.parentForm) {\n defineHiddenProp(options, 'parentForm', this.parentForm);\n observe(options, ['parentForm', 'submitted'], ({ firstChange }) => {\n if (!firstChange) {\n options.detectChanges(field);\n }\n });\n }\n }\n}\n","import { EventEmitter } from '@angular/core';\nimport { FormArray, FormGroup, FormControl, AbstractControl } from '@angular/forms';\nimport { getKeyPath, getFieldValue, isNil, defineHiddenProp, observe, hasKey } from '../../utils';\nimport { FormlyFieldConfigCache } from '../../models';\n\nexport function unregisterControl(field: FormlyFieldConfigCache, emitEvent = false) {\n const control = field.formControl;\n const fieldIndex = control._fields ? control._fields.indexOf(field) : -1;\n if (fieldIndex !== -1) {\n control._fields.splice(fieldIndex, 1);\n }\n\n const form = control.parent as FormArray | FormGroup;\n if (!form) {\n return;\n }\n\n const opts = { emitEvent };\n if (form instanceof FormArray) {\n const key = form.controls.findIndex((c) => c === control);\n if (key !== -1) {\n form.removeAt(key, opts);\n }\n } else if (form instanceof FormGroup) {\n const paths = getKeyPath(field);\n const key = paths[paths.length - 1];\n if (form.get([key]) === control) {\n form.removeControl(key, opts);\n }\n }\n\n control.setParent(null);\n}\n\nexport function findControl(field: FormlyFieldConfigCache): AbstractControl {\n if (field.formControl) {\n return field.formControl;\n }\n\n if (field.shareFormControl === false) {\n return null;\n }\n\n return field.form?.get(getKeyPath(field));\n}\n\nexport function registerControl(\n field: FormlyFieldConfigCache,\n control?: FormlyFieldConfigCache['formControl'],\n emitEvent = false,\n) {\n control = control || field.formControl;\n\n if (!control._fields) {\n defineHiddenProp(control, '_fields', []);\n }\n if (control._fields.indexOf(field) === -1) {\n control._fields.push(field);\n }\n\n if (!field.formControl && control) {\n defineHiddenProp(field, 'formControl', control);\n control.setValidators(null);\n control.setAsyncValidators(null);\n\n field.props.disabled = !!field.props.disabled;\n const disabledObserver = observe(field, ['props', 'disabled'], ({ firstChange, currentValue }) => {\n if (!firstChange) {\n currentValue ? field.formControl.disable() : field.formControl.enable();\n }\n });\n if (control instanceof FormControl) {\n control.registerOnDisabledChange(disabledObserver.setValue);\n }\n }\n\n if (!field.form || !hasKey(field)) {\n return;\n }\n\n let form = field.form;\n const paths = getKeyPath(field);\n const value = getFieldValue(field);\n if (!(isNil(control.value) && isNil(value)) && control.value !== value && control instanceof FormControl) {\n control.patchValue(value);\n }\n\n for (let i = 0; i < paths.length - 1; i++) {\n const path = paths[i];\n if (!form.get([path])) {\n (form as FormGroup).setControl(path, new FormGroup({}), { emitEvent });\n }\n\n form = <FormGroup>form.get([path]);\n }\n\n const key = paths[paths.length - 1];\n if (!field._hide && form.get([key]) !== control) {\n (form as FormGroup).setControl(key, control, { emitEvent });\n }\n}\n\nexport function updateValidity(c: AbstractControl, onlySelf = false) {\n const status = c.status;\n const value = c.value;\n c.updateValueAndValidity({ emitEvent: false, onlySelf });\n if (status !== c.status) {\n (c.statusChanges as EventEmitter<string>).emit(c.status);\n }\n\n if (value !== c.value) {\n (c.valueChanges as EventEmitter<any>).emit(c.value);\n }\n}\n\nexport function clearControl(form: FormlyFieldConfigCache['formControl']) {\n delete form?._fields;\n form.setValidators(null);\n form.setAsyncValidators(null);\n if (form instanceof FormGroup || form instanceof FormArray) {\n Object.values(form.controls).forEach((c) => clearControl(c));\n }\n}\n","import { Directive, Injectable, Input, OnChanges, QueryList, TemplateRef } from '@angular/core';\n\n@Directive({ selector: '[formlyTemplate]' })\nexport class FormlyTemplate implements OnChanges {\n @Input('formlyTemplate') name: string;\n\n constructor(public ref: TemplateRef<any>) {}\n\n ngOnChanges() {\n this.name = this.name || 'formly-group';\n }\n}\n\n// workarround for https://github.com/angular/angular/issues/43227#issuecomment-904173738\n@Injectable()\nexport class FormlyFieldTemplates {\n templates!: QueryList<FormlyTemplate>;\n}\n","import {\n Component,\n Input,\n ViewContainerRef,\n ViewChild,\n ComponentRef,\n SimpleChanges,\n DoCheck,\n OnInit,\n OnChanges,\n OnDestroy,\n AfterContentInit,\n AfterViewInit,\n Renderer2,\n ElementRef,\n EmbeddedViewRef,\n Optional,\n} from '@angular/core';\nimport { FormControl } from '@angular/forms';\nimport { FormlyConfig } from '../services/formly.config';\nimport { FormlyFieldConfig, FormlyFieldConfigCache, FormlyHookConfig } from '../models';\nimport {\n defineHiddenProp,\n observe,\n observeDeep,\n getFieldValue,\n assignFieldValue,\n isObject,\n markFieldForCheck,\n hasKey,\n IObserver,\n} from '../utils';\nimport { FieldWrapper } from '../templates/field.wrapper';\nimport { FieldType } from '../templates/field.type';\nimport { isObservable } from 'rxjs';\nimport { debounceTime, distinctUntilChanged, startWith } from 'rxjs/operators';\nimport { FormlyFieldTemplates } from './formly.template';\n\n/**\n * The `<formly-field>` component is used to render the UI widget (layout + type) of a given `field`.\n */\n@Component({\n selector: 'formly-field',\n template: '<ng-template #container></ng-template>',\n styleUrls: ['./formly.field.scss'],\n})\nexport class FormlyField implements DoCheck, OnInit, OnChanges, AfterContentInit, AfterViewInit, OnDestroy {\n /** The field config. */\n @Input() field: FormlyFieldConfig;\n @ViewChild('container', { read: ViewContainerRef, static: true }) viewContainerRef!: ViewContainerRef;\n\n private hostObservers: IObserver<any>[] = [];\n private componentRefs: (ComponentRef<FieldType> | EmbeddedViewRef<FieldType>)[] = [];\n private hooksObservers: Function[] = [];\n private detectFieldBuild = false;\n\n private get containerRef() {\n return this.config.extras.renderFormlyFieldElement ? this.viewContainerRef : this.hostContainerRef;\n }\n\n private get elementRef() {\n if (this.config.extras.renderFormlyFieldElement) {\n return this._elementRef;\n }\n if (this.componentRefs?.[0] instanceof ComponentRef) {\n return this.componentRefs[0].location;\n }\n\n return null;\n }\n\n valueChangesUnsubscribe = () => {};\n\n constructor(\n private config: FormlyConfig,\n private renderer: Renderer2,\n private _elementRef: ElementRef,\n private hostContainerRef: ViewContainerRef,\n @Optional() private form: FormlyFieldTemplates,\n ) {}\n\n ngAfterContentInit() {\n this.triggerHook('afterContentInit');\n }\n\n ngAfterViewInit() {\n this.triggerHook('afterViewInit');\n }\n\n ngDoCheck() {\n if (this.detectFieldBuild && this.field && this.field.options) {\n this.render();\n }\n }\n\n ngOnInit() {\n this.triggerHook('onInit');\n }\n\n ngOnChanges(changes: SimpleChanges) {\n this.triggerHook('onChanges', changes);\n }\n\n ngOnDestroy() {\n this.resetRefs(this.field);\n this.hostObservers.forEach((hostObserver) => hostObserver.unsubscribe());\n this.hooksObservers.forEach((unsubscribe) => unsubscribe());\n this.valueChangesUnsubscribe();\n this.triggerHook('onDestroy');\n }\n\n private renderField(\n containerRef: ViewContainerRef,\n f: FormlyFieldConfigCache,\n wrappers: FormlyFieldConfig['wrappers'] = [],\n ) {\n if (this.containerRef === containerRef) {\n this.resetRefs(this.field);\n this.containerRef.clear();\n wrappers = this.field?.wrappers;\n }\n\n if (wrappers?.length > 0) {\n const [wrapper, ...wps] = wrappers;\n const { component } = this.config.getWrapper(wrapper);\n\n const ref = containerRef.createComponent<FieldWrapper>(component);\n this.attachComponentRef(ref, f);\n observe<ViewContainerRef & { _lContainer: any }>(\n ref.instance,\n ['fieldComponent'],\n ({ currentValue, previousValue, firstChange }) => {\n if (currentValue) {\n if (previousValue && previousValue._lContainer === currentValue._lContainer) {\n return;\n }\n\n const viewRef = previousValue ? previousValue.detach() : null;\n if (viewRef && !viewRef.destroyed) {\n currentValue.insert(viewRef);\n } else {\n this.renderField(currentValue, f, wps);\n }\n\n !firstChange && ref.changeDetectorRef.detectChanges();\n }\n },\n );\n } else if (f?.type) {\n const inlineType = this.form?.templates?.find((ref) => ref.name === f.type);\n let ref: ComponentRef<any> | EmbeddedViewRef<any>;\n if (inlineType) {\n ref = containerRef.createEmbeddedView(inlineType.ref, { $implicit: f });\n } else {\n const { component } = this.config.getType(f.type, true);\n ref = containerRef.createComponent<FieldWrapper>(component as any);\n }\n this.attachComponentRef(ref, f);\n }\n }\n\n private triggerHook(name: keyof FormlyHookConfig, changes?: SimpleChanges) {\n if (name === 'onInit' || (name === 'onChanges' && changes.field && !changes.field.firstChange)) {\n this.valueChangesUnsubscribe();\n this.valueChangesUnsubscribe = this.fieldChanges(this.field);\n }\n\n if (this.field?.hooks?.[name]) {\n if (!changes || changes.field) {\n const r = this.field.hooks[name](this.field);\n if (isObservable(r) && ['onInit', 'afterContentInit', 'afterViewInit'].indexOf(name) !== -1) {\n const sub = r.subscribe();\n this.hooksObservers.push(() => sub.unsubscribe());\n }\n }\n }\n\n if (name === 'onChanges' && changes.field) {\n this.resetRefs(changes.field.previousValue);\n this.render();\n }\n }\n\n private attachComponentRef<T extends FieldType>(\n ref: ComponentRef<T> | EmbeddedViewRef<T>,\n field: FormlyFieldConfigCache,\n ) {\n this.componentRefs.push(ref);\n field._componentRefs.push(ref);\n if (ref instanceof ComponentRef) {\n Object.assign(ref.instance, { field });\n }\n }\n\n private render() {\n if (!this.field) {\n return;\n }\n\n // require Formly build\n if (!this.field.options) {\n this.detectFieldBuild = true;\n\n return;\n }\n\n this.detectFieldBuild = false;\n this.hostObservers.forEach((hostObserver) => hostObserver.unsubscribe());\n this.hostObservers = [\n observe<boolean>(this.field, ['hide'], ({ firstChange, currentValue }) => {\n const containerRef = this.containerRef;\n if (this.config.extras.lazyRender === false) {\n firstChange && this.renderField(containerRef, this.field);\n if (!firstChange || (firstChange && currentValue)) {\n this.elementRef &&\n this.renderer.setStyle(this.elementRef.nativeElement, 'display', currentValue ? 'none' : '');\n }\n } else {\n if (currentValue) {\n containerRef.clear();\n if (this.field.className) {\n this.renderer.removeAttribute(this.elementRef.nativeElement, 'class');\n }\n } else {\n this.renderField(containerRef, this.field);\n if (this.field.className) {\n this.renderer.setAttribute(this.elementRef.nativeElement, 'class', this.field.className);\n }\n }\n }\n\n !firstChange && this.field.options.detectChanges(this.field);\n }),\n observe<string>(this.field, ['className'], ({ firstChange, currentValue }) => {\n if (\n (!firstChange || (firstChange && currentValue)) &&\n (!this.config.extras.lazyRender || this.field.hide !== true)\n ) {\n this.elementRef && this.renderer.setAttribute(this.elementRef.nativeElement, 'class', currentValue);\n }\n }),\n ...['touched', 'pristine', 'status'].map((prop) =>\n observe<string>(\n this.field,\n ['formControl', prop],\n ({ firstChange }) => !firstChange && markFieldForCheck(this.field),\n ),\n ),\n ];\n }\n\n private resetRefs(field: FormlyFieldConfigCache) {\n if (field) {\n if (field._localFields) {\n field._localFields = [];\n } else {\n defineHiddenProp(this.field, '_localFields', []);\n }\n\n if (field._componentRefs) {\n field._componentRefs = field._componentRefs.filter((ref) => this.componentRefs.indexOf(ref) === -1);\n } else {\n defineHiddenProp(this.field, '_componentRefs', []);\n }\n }\n\n this.componentRefs = [];\n }\n\n private fieldChanges(field: FormlyFieldConfigCache | undefined) {\n if (!field) {\n return () => {};\n }\n\n const subscribes = [observeDeep(field, ['props'], () => field.options.detectChanges(field))];\n\n if (field.options) {\n subscribes.push(observeDeep(field.options, ['formState'], () => field.options.detectChanges(field)));\n }\n\n for (const key of Object.keys(field._expressions || {})) {\n const expressionObserver = observe<FormlyFieldConfigCache['_expressions']['key']>(\n field,\n ['_expressions', key],\n ({ currentValue, previousValue }) => {\n if (previousValue?.subscription) {\n previousValue.subscription.unsubscribe();\n previousValue.subscription = null;\n }\n if (isObservable(currentValue.value$)) {\n currentValue.subscription = currentValue.value$.subscribe();\n }\n },\n );\n subscribes.push(() => {\n if (field._expressions[key]?.subscription) {\n field._expressions[key].subscription.unsubscribe();\n }\n expressionObserver.unsubscribe();\n });\n }\n\n for (const path of [['focus'], ['template'], ['fieldGroupClassName'], ['validation', 'show']]) {\n const fieldObserver = observe(\n field,\n path,\n ({ firstChange }) => !firstChange && field.options.detectChanges(field),\n );\n subscribes.push(() => fieldObserver.unsubscribe());\n }\n\n if (field.formControl && !field.fieldGroup) {\n const control = field.formControl;\n let valueChanges = control.valueChanges.pipe(\n distinctUntilChanged((x, y) => {\n if (x !== y || Array.isArray(x) || isObject(x)) {\n return false;\n }\n\n return true;\n }),\n );\n\n if (control.value !== getFieldValue(field)) {\n valueChanges = valueChanges.pipe(startWith(control.value));\n }\n\n const { updateOn, debounce } = field.modelOptions;\n if ((!updateOn || updateOn === 'change') && debounce?.default > 0) {\n valueChanges = control.valueChanges.pipe(debounceTime(debounce.default));\n }\n\n const sub = valueChanges.subscribe((value) => {\n // workaround for https://github.com/angular/angular/issues/13792\n if (control._fields?.length > 1 && control instanceof FormControl) {\n control.patchValue(value, { emitEvent: false, onlySelf: true });\n }\n\n field.parsers?.forEach((parserFn) => (value = parserFn(value)));\n if (value !== field.formControl.value) {\n field.formControl.setValue(value);\n return;\n }\n\n if (hasKey(field)) {\n assignFieldValue(field, value);\n }\n field.options.fieldChanges.next({ value, field, type: 'valueChanges' });\n });\n\n subscribes.push(() => sub.unsubscribe());\n }\n\n let templateFieldsSubs: (() => void)[] = [];\n observe(field, ['_localFields'], ({ currentValue }) => {\n templateFieldsSubs.forEach((unsubscribe) => unsubscribe());\n templateFieldsSubs = (currentValue || []).map((f: FormlyFieldConfigCache) => this.fieldChanges(f));\n });\n\n return () => {\n subscribes.forEach((unsubscribe) => unsubscribe());\n templateFieldsSubs.forEach((unsubscribe) => unsubscribe());\n };\n }\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n DoCheck,\n OnChanges,\n Input,\n SimpleChanges,\n EventEmitter,\n Output,\n OnDestroy,\n NgZone,\n ContentChildren,\n QueryList,\n} from '@angular/core';\nimport { FormGroup, FormArray } from '@angular/forms';\nimport { FormlyFieldConfig, FormlyFormOptions, FormlyFieldConfigCache } from '../models';\nimport { FormlyFormBuilder } from '../services/formly.builder';\nimport { FormlyConfig } from '../services/formly.config';\nimport { clone, hasKey } from '../utils';\nimport { switchMap, filter, take } from 'rxjs/operators';\nimport { clearControl } from '../extensions/field-form/utils';\nimport { FormlyFieldTemplates, FormlyTemplate } from './formly.template';\n\n/**\n * The `<form-form>` component is the main container of the form,\n * which takes care of managing the form state\n * and delegates the rendering of each field to `<formly-field>` component.\n */\n@Component({\n selector: 'formly-form',\n template: '<formly-field [field]=\"field\"></formly-field>',\n providers: [FormlyFormBuilder, FormlyFieldTemplates],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class FormlyForm implements DoCheck, OnChanges, OnDestroy {\n /** The form instance which allow to track model value and validation status. */\n @Input()\n set form(form: FormGroup | FormArray) {\n this.field.form = form;\n }\n get form(): FormGroup | FormArray {\n return this.field.form;\n }\n\n /** The model to be represented by the form. */\n @Input()\n set model(model: any) {\n if (this.config.extras.immutable && this._modelChangeValue === model) {\n return;\n }\n\n this.setField({ model });\n }\n get model(): any {\n return this.field.model;\n }\n\n /** The field configurations for building the form. */\n @Input()\n set fields(fieldGroup: FormlyFieldConfig[]) {\n this.setField({ fieldGroup });\n }\n get fields(): FormlyFieldConfig[] {\n return this.field.fieldGroup;\n }\n\n /** Options for the form. */\n @Input()\n set options(options: FormlyFormOptions) {\n this.setField({ options });\n }\n get options(): FormlyFormOptions {\n return this.field.options;\n }\n\n /** Event that is emitted when the model value is changed */\n @Output() modelChange = new EventEmitter<any>();\n @ContentChildren(FormlyTemplate) set templates(templates: QueryList<FormlyTemplate>) {\n this.fieldTemplates.templates = templates;\n }\n\n field: FormlyFieldConfigCache = { type: 'formly-group' };\n private _modelChangeValue: any = {};\n private valueChangesUnsubscribe = () => {};\n\n constructor(\n private builder: FormlyFormBuilder,\n private config: FormlyConfig,\n private ngZone: NgZone,\n private fieldTemplates: FormlyFieldTemplates,\n ) {}\n\n ngDoCheck() {\n if (this.config.extras.checkExpressionOn === 'changeDetectionCheck') {\n this.checkExpressionChange();\n }\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes.fields && this.form) {\n clearControl(this.form);\n }\n\n if (changes.fields || changes.form || (changes.model && this._modelChangeValue !== changes.model.currentValue)) {\n this.valueChangesUnsubscribe();\n this.builder.build(this.field);\n this.valueChangesUnsubscribe = this.valueChanges();\n }\n }\n\n ngOnDestroy() {\n this.valueChangesUnsubscribe();\n }\n\n private checkExpressionChange() {\n this.field.options.checkExpressions?.(this.field);\n }\n\n private valueChanges() {\n this.valueChangesUnsubscribe();\n\n const sub = this.field.options.fieldChanges\n .pipe(\n filter(({ field, type }) => hasKey(field) && type === 'valueChanges'),\n switchMap(() => this.ngZone.onStable.asObservable().pipe(take(1))),\n )\n .subscribe(() =>\n this.ngZone.runGuarded(() => {\n // runGuarded is used to keep in sync the expression changes\n // https://github.com/ngx-formly/ngx-formly/issues/2095\n this.checkExpressionChange();\n this.modelChange.emit((this._modelChangeValue = clone(this.model)));\n }),\n );\n\n return () => sub.unsubscribe();\n }\n\n private setField(field: FormlyFieldConfigCache) {\n if (this.config.extras.immutable) {\n this.field = { ...this.field, ...clone(field) };\n } else {\n Object.keys(field).forEach((p) => ((this.field as any)[p] = (field as any)[p]));\n }\n }\n}\n","import {\n Directive,\n ElementRef,\n Input,\n OnChanges,\n SimpleChanges,\n Renderer2,\n DoCheck,\n Inject,\n OnDestroy,\n} from '@angular/core';\nimport { FormlyFieldConfig, FormlyFieldConfigCache } from '../models';\nimport { defineHiddenProp, FORMLY_VALIDATORS, observe, IObserver } from '../utils';\nimport { DOCUMENT } from '@angular/common';\n\n/**\n * Allow to link the `field` HTML attributes (`id`, `name` ...) and Event attributes (`focus`, `blur` ...) to an element in the DOM.\n */\n@Directive({\n selector: '[formlyAttributes]',\n host: {\n '(change)': 'onHostChange($event)',\n },\n})\nexport class FormlyAttributes implements OnChanges, DoCheck, OnDestroy {\n /** The field config. */\n @Input('formlyAttributes') field: FormlyFieldConfig;\n @Input() id: string;\n\n private document: Document;\n private uiAttributesCache: any = {};\n private uiAttributes: string[];\n private focusObserver: IObserver<boolean>;\n\n /**\n * HostBinding doesn't register listeners conditionally which may produce some perf issues.\n *\n * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1991\n */\n private uiEvents = {\n listeners: [] as Function[],\n events: ['click', 'keyup', 'keydown', 'keypress', 'focus', 'blur', 'change'],\n callback: (eventName: string, $event: any) => {\n switch (eventName) {\n case 'focus':\n return this.onFocus($event);\n case 'blur':\n return this.onBlur($event);\n case 'change':\n return this.onChange($event);\n default:\n return this.props[eventName](this.field, $event);\n }\n },\n };\n\n private get props() {\n return this.field.props || ({} as FormlyFieldConfigCache['props']);\n }\n\n private get fieldAttrElements(): ElementRef[] {\n return (this.field as FormlyFieldConfigCache)?.['_elementRefs'] || [];\n }\n\n constructor(private renderer: Renderer2, private elementRef: ElementRef, @Inject(DOCUMENT) _document: any) {\n this.document = _document;\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes.field) {\n this.field.name && this.setAttribute('name', this.field.name);\n this.uiEvents.listeners.forEach((listener) => listener());\n this.uiEvents.events.forEach((eventName) => {\n if (this.props?.[eventName] || ['focus', 'blur', 'change'].indexOf(eventName) !== -1) {\n this.uiEvents.listeners.push(\n this.renderer.listen(this.elementRef.nativeElement, eventName, (e) => this.uiEvents.callback(eventName, e)),\n );\n }\n });\n\n if (this.props?.attributes) {\n observe(this.field, ['props', 'attributes'], ({ currentValue, previousValue }) => {\n if (previousValue) {\n Object.keys(previousValue).forEach((attr) => this.removeAttribute(attr));\n }\n\n if (currentValue) {\n Object.keys(currentValue).forEach((attr) => {\n if (currentValue[attr] != null) {\n this.setAttribute(attr, currentValue[attr]);\n }\n });\n }\n });\n }\n\n this.detachElementRef(changes.field.previousValue);\n this.attachElementRef(changes.field.currentValue);\n if (this.fieldAttrElements.length === 1) {\n !this.id && this.field.id && this.setAttribute('id', this.field.id);\n this.focusObserver = observe<boolean>(this.field, ['focus'], ({ currentValue }) => {\n this.toggleFocus(currentValue);\n });\n }\n }\n\n if (changes.id) {\n this.setAttribute('id', this.id);\n }\n }\n\n /**\n * We need to re-evaluate all the attributes on every change detection cycle, because\n * by using a HostBinding we run into certain edge cases. This means that whatever logic\n * is in here has to be super lean or we risk seriously damaging or destroying the performance.\n *\n * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1317\n * Material issue: https://github.com/angular/components/issues/14024\n */\n ngDoCheck() {\n if (!this.uiAttributes) {\n const element = this.elementRef.nativeElement as HTMLElement;\n this.uiAttributes = [...FORMLY_VALIDATORS, 'tabindex', 'placeholder', 'readonly', 'disabled', 'step'].filter(\n (attr) => !element.hasAttribute || !element.hasAttribute(attr),\n );\n }\n\n for (let i = 0; i < this.uiAttributes.length; i++) {\n const attr = this.uiAttributes[i];\n const value = this.props[attr];\n if (\n this.uiAttributesCache[attr] !== value &&\n (!this.props.attributes || !this.props.attributes.hasOwnProperty(attr.toLowerCase()))\n ) {\n this.uiAttributesCache[attr] = value;\n if (value || value === 0) {\n this.setAttribute(attr, value === true ? attr : `${value}`);\n } else {\n this.removeAttribute(attr);\n }\n }\n }\n }\n\n ngOnDestroy() {\n this.uiEvents.listeners.forEach((listener) => listener());\n this.detachElementRef(this.field);\n this.focusObserver?.unsubscribe();\n }\n\n toggleFocus(value: boolean) {\n const element = this.fieldAttrElements ? this.fieldAttrElements[0] : null;\n if (!element || !element.nativeElement.focus) {\n return;\n }\n\n const isFocused =\n !!this.document.activeElement &&\n this.fieldAttrElements.some(\n ({ nativeElement }) =>\n this.document.activeElement === nativeElement || nativeElement.contains(this.document.activeElement),\n );\n\n if (value && !isFocused) {\n Promise.resolve().then(() => element.nativeElement.focus());\n } else if (!value && isFocused) {\n Promise.resolve().then(() => element.nativeElement.blur());\n }\n }\n\n onFocus($event: a