UNPKG

@ngx-formly/core

Version:

Formly is a dynamic (JSON powered) form library for Angular that bring unmatched maintainability to your application's forms.

1,188 lines (1,179 loc) 102 kB
import * as i0 from '@angular/core'; import { Type, TemplateRef, ComponentRef, ChangeDetectorRef, InjectionToken, Injectable, Optional, Directive, Input, ViewContainerRef, Component, ViewChild, EventEmitter, ChangeDetectionStrategy, Output, ContentChildren, Inject, ViewChildren, NgModule } from '@angular/core'; import * as i2 from '@angular/forms'; import { AbstractControl, FormGroup, FormArray, FormControl, NgControl, Validators } from '@angular/forms'; import { isObservable, merge, of, Observable, Subject } from 'rxjs'; import { distinctUntilChanged, startWith, debounceTime, filter, switchMap, take, tap, map } from 'rxjs/operators'; import * as i2$1 from '@angular/common'; import { DOCUMENT, CommonModule } from '@angular/common'; import * as i1 from '@angular/platform-browser'; import { __rest } from 'tslib'; function disableTreeValidityCall(form, callback) { const _updateTreeValidity = form._updateTreeValidity.bind(form); form._updateTreeValidity = () => { }; callback(); form._updateTreeValidity = _updateTreeValidity; } function getFieldId(formId, field, index) { if (field.id) { return field.id; } let type = field.type; if (!type && field.template) { type = 'template'; } if (type instanceof Type) { type = type.prototype.constructor.name; } return [formId, type, field.key, index].join('_'); } function hasKey(field) { return !isNil(field.key) && field.key !== ''; } function getKeyPath(field) { var _a; if (!hasKey(field)) { return []; } /* We store the keyPath in the field for performance reasons. This function will be called frequently. */ if (((_a = field._keyPath) === null || _a === void 0 ? void 0 : _a.key) !== field.key) { let path = []; if (typeof field.key === 'string') { const key = field.key.indexOf('[') === -1 ? field.key : field.key.replace(/\[(\w+)\]/g, '.$1'); path = key.indexOf('.') !== -1 ? key.split('.') : [key]; } else if (Array.isArray(field.key)) { path = field.key.slice(0); } else { path = [`${field.key}`]; } defineHiddenProp(field, '_keyPath', { key: field.key, path }); } return field._keyPath.path.slice(0); } const FORMLY_VALIDATORS = ['required', 'pattern', 'minLength', 'maxLength', 'min', 'max']; function assignFieldValue(field, value) { let paths = getKeyPath(field); if (paths.length === 0) { return; } let root = field; while (root.parent) { root = root.parent; paths = [...getKeyPath(root), ...paths]; } if (value === undefined && field.resetOnHide) { const k = paths.pop(); const m = paths.reduce((model, path) => model[path] || {}, root.model); delete m[k]; return; } assignModelValue(root.model, paths, value); } function assignModelValue(model, paths, value) { for (let i = 0; i < paths.length - 1; i++) { const path = paths[i]; if (!model[path] || !isObject(model[path])) { model[path] = /^\d+$/.test(paths[i + 1]) ? [] : {}; } model = model[path]; } model[paths[paths.length - 1]] = clone(value); } function getFieldValue(field) { let model = field.parent ? field.parent.model : field.model; for (const path of getKeyPath(field)) { if (!model) { return model; } model = model[path]; } return model; } function reverseDeepMerge(dest, ...args) { args.forEach((src) => { for (const srcArg in src) { if (isNil(dest[srcArg]) || isBlankString(dest[srcArg])) { dest[srcArg] = clone(src[srcArg]); } else if (objAndSameType(dest[srcArg], src[srcArg])) { reverseDeepMerge(dest[srcArg], src[srcArg]); } } }); return dest; } // check a value is null or undefined function isNil(value) { return value == null; } function isUndefined(value) { return value === undefined; } function isBlankString(value) { return value === ''; } function isFunction(value) { return typeof value === 'function'; } function objAndSameType(obj1, obj2) { return (isObject(obj1) && isObject(obj2) && Object.getPrototypeOf(obj1) === Object.getPrototypeOf(obj2) && !(Array.isArray(obj1) || Array.isArray(obj2))); } function isObject(x) { return x != null && typeof x === 'object'; } function isPromise(obj) { return !!obj && typeof obj.then === 'function'; } function clone(value) { if (!isObject(value) || isObservable(value) || value instanceof TemplateRef || /* instanceof SafeHtmlImpl */ value.changingThisBreaksApplicationSecurity || ['RegExp', 'FileList', 'File', 'Blob'].indexOf(value.constructor.name) !== -1) { return value; } if (value instanceof Set) { return new Set(value); } if (value instanceof Map) { return new Map(value); } // https://github.com/moment/moment/blob/master/moment.js#L252 if (value._isAMomentObject && isFunction(value.clone)) { return value.clone(); } if (value instanceof AbstractControl) { return null; } if (value instanceof Date) { return new Date(value.getTime()); } if (Array.isArray(value)) { return value.slice(0).map((v) => clone(v)); } // best way to clone a js object maybe // https://stackoverflow.com/questions/41474986/how-to-clone-a-javascript-es6-class-instance const proto = Object.getPrototypeOf(value); let c = Object.create(proto); c = Object.setPrototypeOf(c, proto); // need to make a deep copy so we dont use Object.assign // also Object.assign wont copy property descriptor exactly return Object.keys(value).reduce((newVal, prop) => { const propDesc = Object.getOwnPropertyDescriptor(value, prop); if (propDesc.get) { Object.defineProperty(newVal, prop, propDesc); } else { newVal[prop] = clone(value[prop]); } return newVal; }, c); } function defineHiddenProp(field, prop, defaultValue) { Object.defineProperty(field, prop, { enumerable: false, writable: true, configurable: true }); field[prop] = defaultValue; } function observeDeep(source, paths, setFn) { let observers = []; const unsubscribe = () => { observers.forEach((observer) => observer()); observers = []; }; const observer = observe(source, paths, ({ firstChange, currentValue }) => { !firstChange && setFn(); unsubscribe(); if (isObject(currentValue) && currentValue.constructor.name === 'Object') { Object.keys(currentValue).forEach((prop) => { observers.push(observeDeep(source, [...paths, prop], setFn)); }); } }); return () => { observer.unsubscribe(); unsubscribe(); }; } function observe(o, paths, setFn) { if (!o._observers) { defineHiddenProp(o, '_observers', {}); } let target = o; for (let i = 0; i < paths.length - 1; i++) { if (!target[paths[i]] || !isObject(target[paths[i]])) { target[paths[i]] = /^\d+$/.test(paths[i + 1]) ? [] : {}; } target = target[paths[i]]; } const key = paths[paths.length - 1]; const prop = paths.join('.'); if (!o._observers[prop]) { o._observers[prop] = { value: target[key], onChange: [] }; } const state = o._observers[prop]; if (target[key] !== state.value) { state.value = target[key]; } if (setFn && state.onChange.indexOf(setFn) === -1) { state.onChange.push(setFn); setFn({ currentValue: state.value, firstChange: true }); if (state.onChange.length >= 1 && isObject(target)) { const { enumerable } = Object.getOwnPropertyDescriptor(target, key) || { enumerable: true }; Object.defineProperty(target, key, { enumerable, configurable: true, get: () => state.value, set: (currentValue) => { if (currentValue !== state.value) { const previousValue = state.value; state.value = currentValue; state.onChange.forEach((changeFn) => changeFn({ previousValue, currentValue, firstChange: false })); } }, }); } } return { setValue(currentValue, emitEvent = true) { if (currentValue === state.value) { return; } const previousValue = state.value; state.value = currentValue; state.onChange.forEach((changeFn) => { if (changeFn !== setFn && emitEvent) { changeFn({ previousValue, currentValue, firstChange: false }); } }); }, unsubscribe() { state.onChange = state.onChange.filter((changeFn) => changeFn !== setFn); if (state.onChange.length === 0) { delete o._observers[prop]; } }, }; } function getField(f, key) { key = (Array.isArray(key) ? key.join('.') : key); if (!f.fieldGroup) { return undefined; } for (let i = 0, len = f.fieldGroup.length; i < len; i++) { const c = f.fieldGroup[i]; const k = (Array.isArray(c.key) ? c.key.join('.') : c.key); if (k === key) { return c; } if (c.fieldGroup && (isNil(k) || key.indexOf(`${k}.`) === 0)) { const field = getField(c, isNil(k) ? key : key.slice(k.length + 1)); if (field) { return field; } } } return undefined; } function markFieldForCheck(field) { var _a; (_a = field._componentRefs) === null || _a === void 0 ? void 0 : _a.forEach((ref) => { // NOTE: we cannot use ref.changeDetectorRef, see https://github.com/ngx-formly/ngx-formly/issues/2191 if (ref instanceof ComponentRef) { const changeDetectorRef = ref.injector.get(ChangeDetectorRef); changeDetectorRef.markForCheck(); } else { ref.markForCheck(); } }); } /** * An InjectionToken for registering additional formly config options (types, wrappers ...). */ const FORMLY_CONFIG = new InjectionToken('FORMLY_CONFIG'); /** * Maintains list of formly config options. This can be used to register new field type. */ class FormlyConfig { constructor() { this.types = {}; this.validators = {}; this.wrappers = {}; this.messages = {}; this.extras = { checkExpressionOn: 'modelChange', lazyRender: true, resetFieldOnHide: true, renderFormlyFieldElement: true, showError(field) { var _a, _b, _c, _d; return (((_a = field.formControl) === null || _a === void 0 ? void 0 : _a.invalid) && (((_b = field.formControl) === null || _b === void 0 ? void 0 : _b.touched) || ((_c = field.options.parentForm) === null || _c === void 0 ? void 0 : _c.submitted) || !!((_d = field.field.validation) === null || _d === void 0 ? void 0 : _d.show))); }, }; this.extensions = {}; this.presets = {}; this.extensionsByPriority = {}; } addConfig(config) { if (config.types) { config.types.forEach((type) => this.setType(type)); } if (config.validators) { config.validators.forEach((validator) => this.setValidator(validator)); } if (config.wrappers) { config.wrappers.forEach((wrapper) => this.setWrapper(wrapper)); } if (config.validationMessages) { config.validationMessages.forEach((validation) => this.addValidatorMessage(validation.name, validation.message)); } if (config.extensions) { this.setSortedExtensions(config.extensions); } if (config.extras) { this.extras = Object.assign(Object.assign({}, this.extras), config.extras); } if (config.presets) { this.presets = Object.assign(Object.assign({}, this.presets), config.presets.reduce((acc, curr) => (Object.assign(Object.assign({}, acc), { [curr.name]: curr.config })), {})); } } /** * Allows you to specify a custom type which you can use in your field configuration. * You can pass an object of options, or an array of objects of options. */ setType(options) { if (Array.isArray(options)) { options.forEach((option) => this.setType(option)); } else { if (!this.types[options.name]) { this.types[options.name] = { name: options.name }; } ['component', 'extends', 'defaultOptions', 'wrappers'].forEach((prop) => { if (options.hasOwnProperty(prop)) { this.types[options.name][prop] = options[prop]; } }); } } getType(name, throwIfNotFound = false) { if (name instanceof Type) { return { component: name, name: name.prototype.constructor.name }; } if (!this.types[name]) { if (throwIfNotFound) { throw new Error(`[Formly Error] The type "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`); } return null; } this.mergeExtendedType(name); return this.types[name]; } /** @ignore */ getMergedField(field = {}) { var _a; const type = this.getType(field.type); if (!type) { return; } if (type.defaultOptions) { reverseDeepMerge(field, type.defaultOptions); } const extendDefaults = type.extends && this.getType(type.extends).defaultOptions; if (extendDefaults) { reverseDeepMerge(field, extendDefaults); } if (field === null || field === void 0 ? void 0 : field.optionsTypes) { field.optionsTypes.forEach((option) => { const defaultOptions = this.getType(option).defaultOptions; if (defaultOptions) { reverseDeepMerge(field, defaultOptions); } }); } const componentRef = this.resolveFieldTypeRef(field); if ((_a = componentRef === null || componentRef === void 0 ? void 0 : componentRef.instance) === null || _a === void 0 ? void 0 : _a.defaultOptions) { reverseDeepMerge(field, componentRef.instance.defaultOptions); } if (!field.wrappers && type.wrappers) { field.wrappers = [...type.wrappers]; } } /** @ignore @internal */ resolveFieldTypeRef(field = {}) { const type = this.getType(field.type); if (!type) { return null; } if (!type.component || type._componentRef) { return type._componentRef; } const { _viewContainerRef, _injector } = field.options; if (!_viewContainerRef || !_injector) { return null; } const componentRef = _viewContainerRef.createComponent(type.component, { injector: _injector }); defineHiddenProp(type, '_componentRef', componentRef); try { componentRef.destroy(); } catch (e) { console.error(`An error occurred while destroying the Formly component type "${field.type}"`, e); } return type._componentRef; } setWrapper(options) { this.wrappers[options.name] = options; if (options.types) { options.types.forEach((type) => { this.setTypeWrapper(type, options.name); }); } } getWrapper(name) { if (name instanceof Type) { return { component: name, name: name.prototype.constructor.name }; } if (!this.wrappers[name]) { throw new Error(`[Formly Error] The wrapper "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`); } return this.wrappers[name]; } /** @ignore */ setTypeWrapper(type, name) { if (!this.types[type]) { this.types[type] = {}; } if (!this.types[type].wrappers) { this.types[type].wrappers = []; } if (this.types[type].wrappers.indexOf(name) === -1) { this.types[type].wrappers.push(name); } } setValidator(options) { this.validators[options.name] = options; } getValidator(name) { if (!this.validators[name]) { throw new Error(`[Formly Error] The validator "${name}" could not be found. Please make sure that is registered through the FormlyModule declaration.`); } return this.validators[name]; } addValidatorMessage(name, message) { this.messages[name] = message; if (typeof ngDevMode === 'undefined' || ngDevMode) { const deprecated = { minlength: 'minLength', maxlength: 'maxLength' }; if (deprecated[name]) { console.warn(`Formly deprecation: passing validation messages key '${name}' is deprecated since v6.0, use '${deprecated[name]}' instead.`); this.messages[deprecated[name]] = message; } } } getValidatorMessage(name) { return this.messages[name]; } setSortedExtensions(extensionOptions) { // insert new extensions, grouped by priority extensionOptions.forEach((extensionOption) => { var _a; const priority = (_a = extensionOption.priority) !== null && _a !== void 0 ? _a : 1; this.extensionsByPriority[priority] = Object.assign(Object.assign({}, this.extensionsByPriority[priority]), { [extensionOption.name]: extensionOption.extension }); }); // flatten extensions object with sorted keys this.extensions = Object.keys(this.extensionsByPriority) .map(Number) .sort((a, b) => a - b) .reduce((acc, prio) => (Object.assign(Object.assign({}, acc), this.extensionsByPriority[prio])), {}); } mergeExtendedType(name) { if (!this.types[name].extends) { return; } const extendedType = this.getType(this.types[name].extends); if (!this.types[name].component) { this.types[name].component = extendedType.component; } if (!this.types[name].wrappers) { this.types[name].wrappers = extendedType.wrappers; } } } FormlyConfig.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); FormlyConfig.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyConfig, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyConfig, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class FormlyFormBuilder { constructor(config, injector, viewContainerRef, parentForm) { this.config = config; this.injector = injector; this.viewContainerRef = viewContainerRef; this.parentForm = parentForm; } buildForm(form, fieldGroup = [], model, options) { this.build({ fieldGroup, model, form, options }); } build(field) { if (!this.config.extensions.core) { throw new Error('NgxFormly: missing `forRoot()` call. use `forRoot()` when registering the `FormlyModule`.'); } if (!field.parent) { this._setOptions(field); } disableTreeValidityCall(field.form, () => { var _a, _b; this._build(field); if (!field.parent) { const options = field.options; (_a = options.checkExpressions) === null || _a === void 0 ? void 0 : _a.call(options, field, true); (_b = options._detectChanges) === null || _b === void 0 ? void 0 : _b.call(options, field); } }); } _build(field) { var _a; if (!field) { return; } const extensions = Object.values(this.config.extensions); extensions.forEach((extension) => { var _a; return (_a = extension.prePopulate) === null || _a === void 0 ? void 0 : _a.call(extension, field); }); extensions.forEach((extension) => { var _a; return (_a = extension.onPopulate) === null || _a === void 0 ? void 0 : _a.call(extension, field); }); (_a = field.fieldGroup) === null || _a === void 0 ? void 0 : _a.forEach((f) => this._build(f)); extensions.forEach((extension) => { var _a; return (_a = extension.postPopulate) === null || _a === void 0 ? void 0 : _a.call(extension, field); }); } _setOptions(field) { field.form = field.form || new FormGroup({}); field.model = field.model || {}; field.options = field.options || {}; const options = field.options; if (!options._viewContainerRef) { defineHiddenProp(options, '_viewContainerRef', this.viewContainerRef); } if (!options._injector) { defineHiddenProp(options, '_injector', this.injector); } if (!options.build) { options._buildForm = () => { console.warn(`Formly: 'options._buildForm' is deprecated since v6.0, use 'options.build' instead.`); this.build(field); }; options.build = (f = field) => { this.build(f); return f; }; } if (!options.parentForm && this.parentForm) { defineHiddenProp(options, 'parentForm', this.parentForm); observe(options, ['parentForm', 'submitted'], ({ firstChange }) => { if (!firstChange) { options.detectChanges(field); } }); } } } 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 }); FormlyFormBuilder.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFormBuilder, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFormBuilder, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return [{ type: FormlyConfig }, { type: i0.Injector }, { type: i0.ViewContainerRef, decorators: [{ type: Optional }] }, { type: i2.FormGroupDirective, decorators: [{ type: Optional }] }]; } }); function unregisterControl(field, emitEvent = false) { const control = field.formControl; const fieldIndex = control._fields ? control._fields.indexOf(field) : -1; if (fieldIndex !== -1) { control._fields.splice(fieldIndex, 1); } const form = control.parent; if (!form) { return; } const opts = { emitEvent }; if (form instanceof FormArray) { const key = form.controls.findIndex((c) => c === control); if (key !== -1) { form.removeAt(key, opts); } } else if (form instanceof FormGroup) { const paths = getKeyPath(field); const key = paths[paths.length - 1]; if (form.get([key]) === control) { form.removeControl(key, opts); } } control.setParent(null); } function findControl(field) { var _a; if (field.formControl) { return field.formControl; } if (field.shareFormControl === false) { return null; } return (_a = field.form) === null || _a === void 0 ? void 0 : _a.get(getKeyPath(field)); } function registerControl(field, control, emitEvent = false) { control = control || field.formControl; if (!control._fields) { defineHiddenProp(control, '_fields', []); } if (control._fields.indexOf(field) === -1) { control._fields.push(field); } if (!field.formControl && control) { defineHiddenProp(field, 'formControl', control); control.setValidators(null); control.setAsyncValidators(null); field.props.disabled = !!field.props.disabled; const disabledObserver = observe(field, ['props', 'disabled'], ({ firstChange, currentValue }) => { if (!firstChange) { currentValue ? field.formControl.disable() : field.formControl.enable(); } }); if (control instanceof FormControl) { control.registerOnDisabledChange(disabledObserver.setValue); } } if (!field.form || !hasKey(field)) { return; } let form = field.form; const paths = getKeyPath(field); const value = getFieldValue(field); if (!(isNil(control.value) && isNil(value)) && control.value !== value && control instanceof FormControl) { control.patchValue(value); } for (let i = 0; i < paths.length - 1; i++) { const path = paths[i]; if (!form.get([path])) { form.setControl(path, new FormGroup({}), { emitEvent }); } form = form.get([path]); } const key = paths[paths.length - 1]; if (!field._hide && form.get([key]) !== control) { form.setControl(key, control, { emitEvent }); } } function updateValidity(c, onlySelf = false) { const status = c.status; const value = c.value; c.updateValueAndValidity({ emitEvent: false, onlySelf }); if (status !== c.status) { c.statusChanges.emit(c.status); } if (value !== c.value) { c.valueChanges.emit(c.value); } } function clearControl(form) { form === null || form === void 0 ? true : delete form._fields; form.setValidators(null); form.setAsyncValidators(null); if (form instanceof FormGroup || form instanceof FormArray) { Object.values(form.controls).forEach((c) => clearControl(c)); } } class FormlyTemplate { constructor(ref) { this.ref = ref; } ngOnChanges() { this.name = this.name || 'formly-group'; } } FormlyTemplate.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplate, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); FormlyTemplate.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.12", type: FormlyTemplate, selector: "[formlyTemplate]", inputs: { name: ["formlyTemplate", "name"] }, usesOnChanges: true, ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyTemplate, decorators: [{ type: Directive, args: [{ selector: '[formlyTemplate]' }] }], ctorParameters: function () { return [{ type: i0.TemplateRef }]; }, propDecorators: { name: [{ type: Input, args: ['formlyTemplate'] }] } }); // workarround for https://github.com/angular/angular/issues/43227#issuecomment-904173738 class FormlyFieldTemplates { } FormlyFieldTemplates.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); FormlyFieldTemplates.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyFieldTemplates, decorators: [{ type: Injectable }] }); /** * The `<formly-field>` component is used to render the UI widget (layout + type) of a given `field`. */ class FormlyField { constructor(config, renderer, _elementRef, hostContainerRef, form) { this.config = config; this.renderer = renderer; this._elementRef = _elementRef; this.hostContainerRef = hostContainerRef; this.form = form; this.hostObservers = []; this.componentRefs = []; this.hooksObservers = []; this.detectFieldBuild = false; this.valueChangesUnsubscribe = () => { }; } get containerRef() { return this.config.extras.renderFormlyFieldElement ? this.viewContainerRef : this.hostContainerRef; } get elementRef() { var _a; if (this.config.extras.renderFormlyFieldElement) { return this._elementRef; } if (((_a = this.componentRefs) === null || _a === void 0 ? void 0 : _a[0]) instanceof ComponentRef) { return this.componentRefs[0].location; } return null; } ngAfterContentInit() { this.triggerHook('afterContentInit'); } ngAfterViewInit() { this.triggerHook('afterViewInit'); } ngDoCheck() { if (this.detectFieldBuild && this.field && this.field.options) { this.render(); } } ngOnInit() { this.triggerHook('onInit'); } ngOnChanges(changes) { this.triggerHook('onChanges', changes); } ngOnDestroy() { this.resetRefs(this.field); this.hostObservers.forEach((hostObserver) => hostObserver.unsubscribe()); this.hooksObservers.forEach((unsubscribe) => unsubscribe()); this.valueChangesUnsubscribe(); this.triggerHook('onDestroy'); } renderField(containerRef, f, wrappers = []) { var _a, _b, _c; if (this.containerRef === containerRef) { this.resetRefs(this.field); this.containerRef.clear(); wrappers = (_a = this.field) === null || _a === void 0 ? void 0 : _a.wrappers; } if ((wrappers === null || wrappers === void 0 ? void 0 : wrappers.length) > 0) { const [wrapper, ...wps] = wrappers; const { component } = this.config.getWrapper(wrapper); const ref = containerRef.createComponent(component); this.attachComponentRef(ref, f); observe(ref.instance, ['fieldComponent'], ({ currentValue, previousValue, firstChange }) => { if (currentValue) { if (previousValue && previousValue._lContainer === currentValue._lContainer) { return; } const viewRef = previousValue ? previousValue.detach() : null; if (viewRef && !viewRef.destroyed) { currentValue.insert(viewRef); } else { this.renderField(currentValue, f, wps); } !firstChange && ref.changeDetectorRef.detectChanges(); } }); } else if (f === null || f === void 0 ? void 0 : f.type) { const inlineType = (_c = (_b = this.form) === null || _b === void 0 ? void 0 : _b.templates) === null || _c === void 0 ? void 0 : _c.find((ref) => ref.name === f.type); let ref; if (inlineType) { ref = containerRef.createEmbeddedView(inlineType.ref, { $implicit: f }); } else { const { component } = this.config.getType(f.type, true); ref = containerRef.createComponent(component); } this.attachComponentRef(ref, f); } } triggerHook(name, changes) { var _a, _b; if (name === 'onInit' || (name === 'onChanges' && changes.field && !changes.field.firstChange)) { this.valueChangesUnsubscribe(); this.valueChangesUnsubscribe = this.fieldChanges(this.field); } if ((_b = (_a = this.field) === null || _a === void 0 ? void 0 : _a.hooks) === null || _b === void 0 ? void 0 : _b[name]) { if (!changes || changes.field) { const r = this.field.hooks[name](this.field); if (isObservable(r) && ['onInit', 'afterContentInit', 'afterViewInit'].indexOf(name) !== -1) { const sub = r.subscribe(); this.hooksObservers.push(() => sub.unsubscribe()); } } } if (name === 'onChanges' && changes.field) { this.resetRefs(changes.field.previousValue); this.render(); } } attachComponentRef(ref, field) { this.componentRefs.push(ref); field._componentRefs.push(ref); if (ref instanceof ComponentRef) { Object.assign(ref.instance, { field }); } } render() { if (!this.field) { return; } // require Formly build if (!this.field.options) { this.detectFieldBuild = true; return; } this.detectFieldBuild = false; this.hostObservers.forEach((hostObserver) => hostObserver.unsubscribe()); this.hostObservers = [ observe(this.field, ['hide'], ({ firstChange, currentValue }) => { const containerRef = this.containerRef; if (this.config.extras.lazyRender === false) { firstChange && this.renderField(containerRef, this.field); if (!firstChange || (firstChange && currentValue)) { this.elementRef && this.renderer.setStyle(this.elementRef.nativeElement, 'display', currentValue ? 'none' : ''); } } else { if (currentValue) { containerRef.clear(); if (this.field.className) { this.renderer.removeAttribute(this.elementRef.nativeElement, 'class'); } } else { this.renderField(containerRef, this.field); if (this.field.className) { this.renderer.setAttribute(this.elementRef.nativeElement, 'class', this.field.className); } } } !firstChange && this.field.options.detectChanges(this.field); }), observe(this.field, ['className'], ({ firstChange, currentValue }) => { if ((!firstChange || (firstChange && currentValue)) && (!this.config.extras.lazyRender || this.field.hide !== true)) { this.elementRef && this.renderer.setAttribute(this.elementRef.nativeElement, 'class', currentValue); } }), ...['touched', 'pristine', 'status'].map((prop) => observe(this.field, ['formControl', prop], ({ firstChange }) => !firstChange && markFieldForCheck(this.field))), ]; } resetRefs(field) { if (field) { if (field._localFields) { field._localFields = []; } else { defineHiddenProp(this.field, '_localFields', []); } if (field._componentRefs) { field._componentRefs = field._componentRefs.filter((ref) => this.componentRefs.indexOf(ref) === -1); } else { defineHiddenProp(this.field, '_componentRefs', []); } } this.componentRefs = []; } fieldChanges(field) { if (!field) { return () => { }; } const subscribes = [observeDeep(field, ['props'], () => field.options.detectChanges(field))]; if (field.options) { subscribes.push(observeDeep(field.options, ['formState'], () => field.options.detectChanges(field))); } for (const key of Object.keys(field._expressions || {})) { const expressionObserver = observe(field, ['_expressions', key], ({ currentValue, previousValue }) => { if (previousValue === null || previousValue === void 0 ? void 0 : previousValue.subscription) { previousValue.subscription.unsubscribe(); previousValue.subscription = null; } if (isObservable(currentValue.value$)) { currentValue.subscription = currentValue.value$.subscribe(); } }); subscribes.push(() => { var _a; if ((_a = field._expressions[key]) === null || _a === void 0 ? void 0 : _a.subscription) { field._expressions[key].subscription.unsubscribe(); } expressionObserver.unsubscribe(); }); } for (const path of [['focus'], ['template'], ['fieldGroupClassName'], ['validation', 'show']]) { const fieldObserver = observe(field, path, ({ firstChange }) => !firstChange && field.options.detectChanges(field)); subscribes.push(() => fieldObserver.unsubscribe()); } if (field.formControl && !field.fieldGroup) { const control = field.formControl; let valueChanges = control.valueChanges.pipe(distinctUntilChanged((x, y) => { if (x !== y || Array.isArray(x) || isObject(x)) { return false; } return true; })); if (control.value !== getFieldValue(field)) { valueChanges = valueChanges.pipe(startWith(control.value)); } const { updateOn, debounce } = field.modelOptions; if ((!updateOn || updateOn === 'change') && (debounce === null || debounce === void 0 ? void 0 : debounce.default) > 0) { valueChanges = control.valueChanges.pipe(debounceTime(debounce.default)); } const sub = valueChanges.subscribe((value) => { var _a, _b; // workaround for https://github.com/angular/angular/issues/13792 if (((_a = control._fields) === null || _a === void 0 ? void 0 : _a.length) > 1 && control instanceof FormControl) { control.patchValue(value, { emitEvent: false, onlySelf: true }); } (_b = field.parsers) === null || _b === void 0 ? void 0 : _b.forEach((parserFn) => (value = parserFn(value))); if (value !== field.formControl.value) { field.formControl.setValue(value); return; } if (hasKey(field)) { assignFieldValue(field, value); } field.options.fieldChanges.next({ value, field, type: 'valueChanges' }); }); subscribes.push(() => sub.unsubscribe()); } let templateFieldsSubs = []; observe(field, ['_localFields'], ({ currentValue }) => { templateFieldsSubs.forEach((unsubscribe) => unsubscribe()); templateFieldsSubs = (currentValue || []).map((f) => this.fieldChanges(f)); }); return () => { subscribes.forEach((unsubscribe) => unsubscribe()); templateFieldsSubs.forEach((unsubscribe) => unsubscribe()); }; } } 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 }); 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"] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyField, decorators: [{ type: Component, args: [{ selector: 'formly-field', template: '<ng-template #container></ng-template>', styles: [":host:empty{display:none}\n"] }] }], ctorParameters: function () { return [{ type: FormlyConfig }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.ViewContainerRef }, { type: FormlyFieldTemplates, decorators: [{ type: Optional }] }]; }, propDecorators: { field: [{ type: Input }], viewContainerRef: [{ type: ViewChild, args: ['container', { read: ViewContainerRef, static: true }] }] } }); /** * The `<form-form>` component is the main container of the form, * which takes care of managing the form state * and delegates the rendering of each field to `<formly-field>` component. */ class FormlyForm { constructor(builder, config, ngZone, fieldTemplates) { this.builder = builder; this.config = config; this.ngZone = ngZone; this.fieldTemplates = fieldTemplates; /** Event that is emitted when the model value is changed */ this.modelChange = new EventEmitter(); this.field = { type: 'formly-group' }; this._modelChangeValue = {}; this.valueChangesUnsubscribe = () => { }; } /** The form instance which allow to track model value and validation status. */ set form(form) { this.field.form = form; } get form() { return this.field.form; } /** The model to be represented by the form. */ set model(model) { if (this.config.extras.immutable && this._modelChangeValue === model) { return; } this.setField({ model }); } get model() { return this.field.model; } /** The field configurations for building the form. */ set fields(fieldGroup) { this.setField({ fieldGroup }); } get fields() { return this.field.fieldGroup; } /** Options for the form. */ set options(options) { this.setField({ options }); } get options() { return this.field.options; } set templates(templates) { this.fieldTemplates.templates = templates; } ngDoCheck() { if (this.config.extras.checkExpressionOn === 'changeDetectionCheck') { this.checkExpressionChange(); } } ngOnChanges(changes) { if (changes.fields && this.form) { clearControl(this.form); } if (changes.fields || changes.form || (changes.model && this._modelChangeValue !== changes.model.currentValue)) { this.valueChangesUnsubscribe(); this.builder.build(this.field); this.valueChangesUnsubscribe = this.valueChanges(); } } ngOnDestroy() { this.valueChangesUnsubscribe(); } checkExpressionChange() { var _a, _b; (_b = (_a = this.field.options).checkExpressions) === null || _b === void 0 ? void 0 : _b.call(_a, this.field); } valueChanges() { this.valueChangesUnsubscribe(); const sub = this.field.options.fieldChanges .pipe(filter(({ field, type }) => hasKey(field) && type === 'valueChanges'), switchMap(() => this.ngZone.onStable.asObservable().pipe(take(1)))) .subscribe(() => this.ngZone.runGuarded(() => { // runGuarded is used to keep in sync the expression changes // https://github.com/ngx-formly/ngx-formly/issues/2095 this.checkExpressionChange(); this.modelChange.emit((this._modelChangeValue = clone(this.model))); })); return () => sub.unsubscribe(); } setField(field) { if (this.config.extras.immutable) { this.field = Object.assign(Object.assign({}, this.field), clone(field)); } else { Object.keys(field).forEach((p) => (this.field[p] = field[p])); } } } 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 }); 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 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: FormlyForm, decorators: [{ type: Component, args: [{ selector: 'formly-form', template: '<formly-field [field]="field"></formly-field>', providers: [FormlyFormBuilder, FormlyFieldTemplates], changeDetection: ChangeDetectionStrategy.OnPush, }] }], ctorParameters: function () { return [{ type: FormlyFormBuilder }, { type: FormlyConfig }, { type: i0.NgZone }, { type: FormlyFieldTemplates }]; }, propDecorators: { form: [{ type: Input }], model: [{ type: Input }], fields: [{ type: Input }], options: [{ type: Input }], modelChange: [{ type: Output }], templates: [{ type: ContentChildren, args: [FormlyTemplate] }] } }); /** * Allow to link the `field` HTML attributes (`id`, `name` ...) and Event attributes (`focus`, `blur` ...) to an element in the DOM. */ class FormlyAttributes { constructor(renderer, elementRef, _document) { this.renderer = renderer; this.elementRef = elementRef; this.uiAttributesCache = {}; /** * HostBinding doesn't register listeners conditionally which may produce some perf issues. * * Formly issue: https://github.com/ngx-formly/ngx-formly/issues/1991 */ this.uiEvents = { listeners: [], events: ['click', 'keyup', 'keydown', 'keypress', 'focus', 'blur', 'change'], callback: (eventName, $event) => { switch (eventName) { case 'focus': return this.onFocus($event); case 'blur': return this.onBlur($event); case 'change': return this.onChange($event); default: return this.props[eventName](this.field, $event); } }, }; this.document = _document; } get props() { return this.field.props || {}; } get fieldAttrElements() { var _a; return ((_a = this.field) === null || _a === void 0 ? void 0 : _a['_elementRefs']) || []; } ngOnChanges(changes) { var _a; if (changes.field) { this.field.name && this.setAttribute('name', this.field.name); this.uiEvents.listeners.forEach((listener) => listener()); this.uiEvents.events.forEach((eventName) => { var _a; if (((_a = this.props) === null || _a === void 0 ? void 0 : _a[eventName]) || ['focus', 'blur', 'change'].indexOf(eventName) !== -1) { this.uiEvents.listeners.push(this.renderer.listen(this.elementRef.nativeElement, eventName, (e) => this.uiEvents.callback(eventName, e))); } }); if ((_a = this.props) === null || _a === void 0 ? void 0 : _a.attributes) { observe(this.field, ['props', 'attributes'], ({ currentValue, previousValue }) => { if (previousValue) { Object.keys(previousValue).forEach((attr) => this.removeAttribute(attr)); } if (currentValue) { Object.keys(currentValue).forEach((attr) => { if (currentValue[attr] != null) { this.setAttribute(attr, currentValue[attr]); } }); } }); } this.detachElementRef(changes.field.previousValue); this.attachElementRef(changes.field.currentValue); if (this.fieldAttrElements.length === 1) { !this.id && this.field.id && this.setAttribute('id', this.field.id); this.focusObserver = observe(this.field, ['focus'], ({ currentValue }) => { this.toggleFocus(currentValue); }); } }