UNPKG

wfw-ngx-formly

Version:

ngx-formly is an Angular 2 module which has a Components to help customize and render JavaScript/JSON configured forms. The formly-form Component and the FormlyConfig service are very powerful and bring unmatched maintainability to your application's form

1,933 lines 62.9 kB
import { Observable, Subject } from 'rxjs';
import { Injectable, Inject, InjectionToken, Component, Input, Optional, EventEmitter, Output, SkipSelf, ViewContainerRef, ViewChild, ComponentFactoryResolver, Directive, HostListener, ElementRef, Renderer2, NgModule, ANALYZE_FOR_ENTRY_COMPONENTS } from '@angular/core';
import { FormGroup, FormArray, FormControl, AbstractControl, Validators, NgForm, FormGroupDirective, ReactiveFormsModule } from '@angular/forms';
import { debounceTime, map, tap } from 'rxjs/operators';
import { CommonModule } from '@angular/common';

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/**
 * @param {?} formId
 * @param {?} field
 * @param {?} index
 * @return {?}
 */
function getFieldId(formId, field, index) {
    if (field.id)
        return field.id;
    let /** @type {?} */ type = field.type;
    if (!type && field.template)
        type = 'template';
    return [formId, type, field.key, index].join('_');
}
/**
 * @param {?} field
 * @return {?}
 */
function getKeyPath(field) {
    /* We store the keyPath in the field for performance reasons. This function will be called frequently. */
    if (!(/** @type {?} */ (field))['_formlyKeyPath'] || (/** @type {?} */ (field))['_formlyKeyPath'].key !== field.key) {
        let /** @type {?} */ keyPath = [];
        if (field.key) {
            /* Also allow for an array key, hence the type check  */
            let /** @type {?} */ pathElements = typeof field.key === 'string' ? field.key.split('.') : field.key;
            for (let /** @type {?} */ pathElement of pathElements) {
                if (typeof pathElement === 'string') {
                    /* replace paths of the form names[2] by names.2, cfr. angular formly */
                    pathElement = pathElement.replace(/\[(\w+)\]/g, '.$1');
                    keyPath = keyPath.concat(pathElement.split('.'));
                }
                else {
                    keyPath.push(pathElement);
                }
            }
            for (let /** @type {?} */ i = 0; i < keyPath.length; i++) {
                let /** @type {?} */ pathElement = keyPath[i];
                if (typeof pathElement === 'string' && stringIsInteger(pathElement)) {
                    keyPath[i] = parseInt(pathElement);
                }
            }
        }
        (/** @type {?} */ (field))['_formlyKeyPath'] = {
            key: field.key,
            path: keyPath,
        };
    }
    return (/** @type {?} */ (field))['_formlyKeyPath'].path.slice(0);
}
/**
 * @param {?} str
 * @return {?}
 */
function stringIsInteger(str) {
    return !isNullOrUndefined(str) && /^\d+$/.test(str);
}
const FORMLY_VALIDATORS = ['required', 'pattern', 'minLength', 'maxLength', 'min', 'max'];
/**
 * @param {?} model
 * @param {?} field
 * @param {?} constructEmptyObjects
 * @return {?}
 */
function getFieldModel(model, field, constructEmptyObjects) {
    let /** @type {?} */ keyPath = getKeyPath(field);
    let /** @type {?} */ value = model;
    for (let /** @type {?} */ i = 0; i < keyPath.length; i++) {
        let /** @type {?} */ path = keyPath[i];
        let /** @type {?} */ pathValue = value[path];
        if (isNullOrUndefined(pathValue) && constructEmptyObjects) {
            if (i < keyPath.length - 1) {
                /* TODO? : It would be much nicer if we could construct object instances of the correct class, for instance by using factories. */
                value[path] = typeof keyPath[i + 1] === 'number' ? [] : {};
            }
            else if (field.fieldGroup && !field.fieldArray) {
                value[path] = {};
            }
            else if (field.fieldArray) {
                value[path] = [];
            }
        }
        value = value[path];
        if (!value) {
            break;
        }
    }
    return value;
}
/**
 * @param {?} fields
 * @param {?} model
 * @return {?}
 */
function assignModelToFields(fields, model) {
    fields.forEach((field, index) => {
        if (!isUndefined(field.defaultValue) && isUndefined(getValueForKey(model, field.key))) {
            assignModelValue(model, field.key, field.defaultValue);
        }
        (/** @type {?} */ (field)).model = model;
        if (field.key && (field.fieldGroup || field.fieldArray)) {
            (/** @type {?} */ (field)).model = getFieldModel(model, field, true);
        }
        if (field.fieldGroup) {
            assignModelToFields(field.fieldGroup, field.model);
        }
    });
}
/**
 * @param {?} model
 * @param {?} path
 * @param {?} value
 * @return {?}
 */
function assignModelValue(model, path, value) {
    if (typeof path === 'string') {
        path = getKeyPath({ key: path });
    }
    if (path.length > 1) {
        const /** @type {?} */ e = path.shift();
        if (!model[e] || !isObject(model[e])) {
            model[e] = typeof path[0] === 'string' ? {} : [];
        }
        assignModelValue(model[e], path, value);
    }
    else {
        model[path[0]] = value;
    }
}
/**
 * @param {?} model
 * @param {?} path
 * @return {?}
 */
function getValueForKey(model, path) {
    if (typeof path === 'string') {
        path = getKeyPath({ key: path });
    }
    if (path.length > 1) {
        const /** @type {?} */ e = path.shift();
        if (!model[e]) {
            model[e] = typeof path[0] === 'string' ? {} : [];
        }
        return getValueForKey(model[e], path);
    }
    else {
        return model[path[0]];
    }
}
/**
 * @param {?} controlKey
 * @param {?} actualKey
 * @return {?}
 */

/**
 * @param {?} dest
 * @param {...?} args
 * @return {?}
 */
function reverseDeepMerge(dest, ...args) {
    args.forEach(src => {
        for (let /** @type {?} */ srcArg in src) {
            if (isNullOrUndefined(dest[srcArg]) || isBlankString(dest[srcArg])) {
                if (isFunction(src[srcArg])) {
                    dest[srcArg] = src[srcArg];
                }
                else {
                    dest[srcArg] = clone(src[srcArg]);
                }
            }
            else if (objAndSameType(dest[srcArg], src[srcArg])) {
                reverseDeepMerge(dest[srcArg], src[srcArg]);
            }
        }
    });
    return dest;
}
/**
 * @param {?} value
 * @return {?}
 */
function isNullOrUndefined(value) {
    return value === undefined || value === null;
}
/**
 * @param {?} value
 * @return {?}
 */
function isUndefined(value) {
    return value === undefined;
}
/**
 * @param {?} value
 * @return {?}
 */
function isBlankString(value) {
    return value === '';
}
/**
 * @param {?} value
 * @return {?}
 */
function isFunction(value) {
    return typeof (value) === 'function';
}
/**
 * @param {?} obj1
 * @param {?} obj2
 * @return {?}
 */
function objAndSameType(obj1, obj2) {
    return isObject(obj1) && isObject(obj2) &&
        Object.getPrototypeOf(obj1) === Object.getPrototypeOf(obj2);
}
/**
 * @param {?} x
 * @return {?}
 */
function isObject(x) {
    return x != null && typeof x === 'object';
}
/**
 * @param {?} value
 * @return {?}
 */
function clone(value) {
    if (!isObject(value) || value instanceof RegExp || value instanceof Observable) {
        return value;
    }
    if (Object.prototype.toString.call(value) === '[object Date]') {
        return new Date(value.getTime());
    }
    if (Array.isArray(value)) {
        return value.slice(0).map(v => clone(v));
    }
    value = Object.assign({}, value);
    Object.keys(value).forEach(k => value[k] = clone(value[k]));
    return value;
}
/**
 * @param {?} expression
 * @param {?} argNames
 * @return {?}
 */
function evalStringExpression(expression, argNames) {
    try {
        return Function.bind.apply(Function, [void 0].concat(argNames.concat(`return ${expression};`)))();
    }
    catch (/** @type {?} */ error) {
        console.error(error);
    }
}
/**
 * @param {?} expression
 * @param {?} argNames
 * @return {?}
 */
function evalExpressionValueSetter(expression, argNames) {
    try {
        return Function.bind
            .apply(Function, [void 0].concat(argNames.concat(`${expression} = expressionValue;`)))();
    }
    catch (/** @type {?} */ error) {
        console.error(error);
    }
}
/**
 * @param {?} expression
 * @param {?} thisArg
 * @param {?} argVal
 * @return {?}
 */
function evalExpression(expression, thisArg, argVal) {
    if (expression instanceof Function) {
        return expression.apply(thisArg, argVal);
    }
    else {
        return expression ? true : false;
    }
}

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
const FORMLY_CONFIG_TOKEN = new InjectionToken('FORMLY_CONFIG_TOKEN');
/**
 * Maintains list of formly field directive types. This can be used to register new field templates.
 */
class FormlyConfig {
    /**
     * @param {?=} configs
     */
    constructor(configs = []) {
        this.types = {};
        this.validators = {};
        this.wrappers = {};
        this.messages = {};
        this.templateManipulators = {
            preWrapper: [],
            postWrapper: [],
        };
        this.extras = {
            fieldTransform: undefined,
            showError: function (field) {
                return field.formControl && field.formControl.invalid && (field.formControl.touched || (field.options.parentForm && field.options.parentForm.submitted) || (field.field.validation && field.field.validation.show));
            },
        };
        configs.forEach(config => this.addConfig(config));
    }
    /**
     * @param {?} config
     * @return {?}
     */
    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.manipulators) {
            config.manipulators.forEach(manipulator => this.setManipulator(manipulator));
        }
        if (config.validationMessages) {
            config.validationMessages.forEach(validation => this.addValidatorMessage(validation.name, validation.message));
        }
        if (config.extras) {
            this.extras = Object.assign({}, this.extras, config.extras);
        }
    }
    /**
     * @param {?} options
     * @return {?}
     */
    setType(options) {
        if (Array.isArray(options)) {
            options.forEach((option) => this.setType(option));
        }
        else {
            if (!this.types[options.name]) {
                this.types[options.name] = /** @type {?} */ ({});
            }
            this.types[options.name].component = options.component;
            this.types[options.name].name = options.name;
            this.types[options.name].extends = options.extends;
            this.types[options.name].defaultOptions = options.defaultOptions;
            if (options.wrappers) {
                options.wrappers.forEach((wrapper) => this.setTypeWrapper(options.name, wrapper));
            }
        }
    }
    /**
     * @param {?} name
     * @return {?}
     */
    getType(name) {
        if (!this.types[name]) {
            throw new Error(`[Formly Error] There is no type by the name of "${name}"`);
        }
        this.mergeExtendedType(name);
        return this.types[name];
    }
    /**
     * @param {?=} field
     * @return {?}
     */
    getMergedField(field = {}) {
        let /** @type {?} */ name = field.type;
        if (!this.types[name]) {
            throw new Error(`[Formly Error] There is no type by the name of "${name}"`);
        }
        this.mergeExtendedType(name);
        if (this.types[name].defaultOptions) {
            reverseDeepMerge(field, this.types[name].defaultOptions);
        }
        let /** @type {?} */ extendDefaults = this.types[name].extends && this.getType(this.types[name].extends).defaultOptions;
        if (extendDefaults) {
            reverseDeepMerge(field, extendDefaults);
        }
        if (field && field.optionsTypes) {
            field.optionsTypes.forEach(option => {
                let /** @type {?} */ defaultOptions = this.getType(option).defaultOptions;
                if (defaultOptions) {
                    reverseDeepMerge(field, defaultOptions);
                }
            });
        }
        if (!field.component) {
            field.component = this.types[name].component;
        }
        if (!field.wrappers) {
            field.wrappers = this.types[name].wrappers;
        }
    }
    /**
     * @param {?} options
     * @return {?}
     */
    setWrapper(options) {
        this.wrappers[options.name] = options;
        if (options.types) {
            options.types.forEach((type) => {
                this.setTypeWrapper(type, options.name);
            });
        }
    }
    /**
     * @param {?} name
     * @return {?}
     */
    getWrapper(name) {
        if (!this.wrappers[name]) {
            throw new Error(`[Formly Error] There is no wrapper by the name of "${name}"`);
        }
        return this.wrappers[name];
    }
    /**
     * @param {?} type
     * @param {?} name
     * @return {?}
     */
    setTypeWrapper(type, name) {
        if (!this.types[type]) {
            this.types[type] = /** @type {?} */ ({});
        }
        if (!this.types[type].wrappers) {
            this.types[type].wrappers = /** @type {?} */ ([]);
        }
        this.types[type].wrappers.push(name);
    }
    /**
     * @param {?} options
     * @return {?}
     */
    setValidator(options) {
        this.validators[options.name] = options;
    }
    /**
     * @param {?} name
     * @return {?}
     */
    getValidator(name) {
        if (!this.validators[name]) {
            throw new Error(`[Formly Error] There is no validator by the name of "${name}"`);
        }
        return this.validators[name];
    }
    /**
     * @param {?} name
     * @param {?} message
     * @return {?}
     */
    addValidatorMessage(name, message) {
        this.messages[name] = message;
    }
    /**
     * @param {?} name
     * @return {?}
     */
    getValidatorMessage(name) {
        return this.messages[name];
    }
    /**
     * @param {?} manipulator
     * @return {?}
     */
    setManipulator(manipulator) {
        new manipulator.class()[manipulator.method](this);
    }
    /**
     * @param {?} name
     * @return {?}
     */
    mergeExtendedType(name) {
        if (!this.types[name].extends) {
            return;
        }
        const /** @type {?} */ 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.decorators = [
    { type: Injectable },
];
/** @nocollapse */
FormlyConfig.ctorParameters = () => [
    { type: Array, decorators: [{ type: Inject, args: [FORMLY_CONFIG_TOKEN,] },] },
];
/**
 * @record
 */

/**
 * @record
 */

/**
 * @record
 */

/**
 * @record
 */

/**
 * @record
 */

/**
 * @record
 */

/**
 * @record
 */

/**
 * @record
 */

/**
 * @record
 */

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/**
 * \@internal
 */
class FormlyFormExpression {
    /**
     * @param {?} form
     * @param {?=} fields
     * @param {?=} model
     * @param {?=} options
     * @return {?}
     */
    checkFields(form, fields = [], model, options) {
        this._checkFields(form, fields, model, options);
    }
    /**
     * @param {?} form
     * @param {?=} fields
     * @param {?=} model
     * @param {?=} options
     * @return {?}
     */
    _checkFields(form, fields = [], model, options) {
        fields.forEach(field => {
            this.checkFieldExpressionChange(form, field, this.getParentModel(model, field), options);
            this.checkFieldVisibilityChange(form, field, this.getParentModel(model, field), options);
            if (field.fieldGroup && field.fieldGroup.length > 0) {
                this._checkFields(field.formControl ? /** @type {?} */ (field.formControl) : form, field.fieldGroup, this.getParentModel(model, field), options);
            }
        });
    }
    /**
     * @param {?} form
     * @param {?} field
     * @param {?} model
     * @param {?} options
     * @return {?}
     */
    checkFieldExpressionChange(form, field, model, options) {
        if (!field || !field.expressionProperties) {
            return;
        }
        const /** @type {?} */ expressionProperties = field.expressionProperties;
        const /** @type {?} */ validators = FORMLY_VALIDATORS.map(v => `templateOptions.${v}`);
        for (const /** @type {?} */ key in expressionProperties) {
            const /** @type {?} */ expressionValue = evalExpression(expressionProperties[key].expression, { field }, [model, options.formState]);
            if (expressionProperties[key].expressionValue !== expressionValue
                && (!isObject(expressionValue) || JSON.stringify(expressionValue) !== JSON.stringify(expressionProperties[key].expressionValue))) {
                expressionProperties[key].expressionValue = expressionValue;
                evalExpression(expressionProperties[key].expressionValueSetter, { field }, [expressionValue, model, field]);
                if (key.indexOf('model.') === 0) {
                    const /** @type {?} */ path = key.replace(/^model\./, ''), /** @type {?} */
                    control = field.key && key === path ? field.formControl : form.get(path);
                    if (control
                        && !(isNullOrUndefined(control.value) && isNullOrUndefined(expressionValue))
                        && control.value !== expressionValue) {
                        control.patchValue(expressionValue);
                    }
                }
                if (validators.indexOf(key) !== -1 && field.formControl) {
                    field.formControl.updateValueAndValidity({ emitEvent: false });
                }
            }
        }
    }
    /**
     * @param {?} form
     * @param {?} field
     * @param {?} model
     * @param {?} options
     * @return {?}
     */
    checkFieldVisibilityChange(form, field, model, options) {
        if (!field || isNullOrUndefined(field.hideExpression)) {
            return;
        }
        const /** @type {?} */ hideExpressionResult = !!evalExpression(field.hideExpression, { field }, [model, options.formState]);
        if (hideExpressionResult !== field.hide) {
            // toggle hide
            field.hide = hideExpressionResult;
            field.templateOptions.hidden = hideExpressionResult;
            if (field.formControl && field.key) {
                const /** @type {?} */ parent = this.fieldParentFormControl(form, field);
                if (parent) {
                    if (hideExpressionResult === true && parent.get(/** @type {?} */ (this.fieldKey(field)))) {
                        this.removeFieldControl(parent, field);
                    }
                    else if (hideExpressionResult === false && !parent.get(/** @type {?} */ (this.fieldKey(field)))) {
                        this.addFieldControl(parent, field, model);
                    }
                }
            }
            if (options.fieldChanges) {
                options.fieldChanges.next(/** @type {?} */ ({ field: field, type: 'hidden', value: hideExpressionResult }));
            }
        }
    }
    /**
     * @param {?} parent
     * @param {?} field
     * @param {?} model
     * @return {?}
     */
    addFieldControl(parent, field, model) {
        const /** @type {?} */ fieldModel = this.getFieldModel(model, field);
        if (!(isNullOrUndefined(field.formControl.value) && isNullOrUndefined(fieldModel))
            && field.formControl.value !== fieldModel) {
            field.formControl.patchValue(fieldModel, { emitEvent: false });
        }
        if (parent instanceof FormArray) {
            parent.push(field.formControl);
        }
        else if (parent instanceof FormGroup) {
            parent.addControl(/** @type {?} */ (this.fieldKey(field)), field.formControl);
        }
    }
    /**
     * @param {?} model
     * @param {?} field
     * @return {?}
     */
    getFieldModel(model, field) {
        if (field.fieldGroup || field.fieldArray) {
            return model;
        }
        return getFieldModel(model, field, false);
    }
    /**
     * @param {?} model
     * @param {?} field
     * @return {?}
     */
    getParentModel(model, field) {
        if (field.key && (field.fieldGroup || field.fieldArray)) {
            return getFieldModel(model, field, true);
        }
        return model;
    }
    /**
     * @param {?} parent
     * @param {?} field
     * @return {?}
     */
    removeFieldControl(parent, field) {
        if (parent instanceof FormArray) {
            parent.removeAt(/** @type {?} */ (this.fieldKey(field)));
        }
        else if (parent instanceof FormGroup) {
            parent.removeControl(/** @type {?} */ (this.fieldKey(field)));
        }
    }
    /**
     * @param {?} form
     * @param {?} field
     * @return {?}
     */
    fieldParentFormControl(form, field) {
        const /** @type {?} */ paths = getKeyPath(field);
        paths.pop(); // remove last path
        return /** @type {?} */ ((paths.length > 0 ? form.get(paths) : form));
    }
    /**
     * @param {?} field
     * @return {?}
     */
    fieldKey(field) {
        return getKeyPath(field).pop();
    }
}
FormlyFormExpression.decorators = [
    { type: Injectable },
];

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
class FormlyFormBuilder {
    /**
     * @param {?} formlyConfig
     * @param {?} formlyFormExpression
     */
    constructor(formlyConfig, formlyFormExpression) {
        this.formlyConfig = formlyConfig;
        this.formlyFormExpression = formlyFormExpression;
        this.formId = 0;
    }
    /**
     * @param {?} form
     * @param {?=} fields
     * @param {?=} model
     * @param {?=} options
     * @return {?}
     */
    buildForm(form, fields = [], model, options) {
        let /** @type {?} */ fieldTransforms = (options && options.fieldTransform) || this.formlyConfig.extras.fieldTransform;
        if (!Array.isArray(fieldTransforms)) {
            fieldTransforms = [fieldTransforms];
        }
        fieldTransforms.forEach(fieldTransform => {
            if (fieldTransform) {
                fields = fieldTransform(fields, model, form, options);
                if (!fields) {
                    throw new Error('fieldTransform must return an array of fields');
                }
            }
        });
        assignModelToFields(fields, model);
        this._buildForm(form, fields, options);
        this.formlyFormExpression.checkFields(form, fields, model, options);
    }
    /**
     * @param {?} form
     * @param {?=} fields
     * @param {?=} options
     * @return {?}
     */
    _buildForm(form, fields = [], options) {
        this.formId++;
        this.registerFormControls(form, fields, options);
    }
    /**
     * @param {?} form
     * @param {?} fields
     * @param {?} options
     * @return {?}
     */
    registerFormControls(form, fields, options) {
        fields.forEach((field, index) => {
            field.id = getFieldId(`formly_${this.formId}`, field, index);
            this.initFieldOptions(field);
            this.initFieldExpression(field, options);
            this.initFieldValidation(field);
            this.initFieldWrappers(field);
            this.initFieldAsyncValidation(field);
            if (field.key && field.type) {
                const /** @type {?} */ paths = getKeyPath({ key: field.key });
                let /** @type {?} */ rootForm = form, /** @type {?} */ rootModel = field.model;
                paths.forEach((path, index) => {
                    // FormGroup/FormArray only allow string value for path
                    const /** @type {?} */ formPath = path.toString();
                    // is last item
                    if (index === paths.length - 1) {
                        this.addFormControl(rootForm, field, rootModel, formPath);
                        if (field.fieldArray) {
                            field.fieldGroup = [];
                            field.model.forEach((m, i) => field.fieldGroup.push(Object.assign({}, clone(field.fieldArray), { key: `${i}` })));
                            assignModelToFields(field.fieldGroup, rootModel);
                        }
                    }
                    else {
                        let /** @type {?} */ nestedForm = /** @type {?} */ (rootForm.get(formPath));
                        if (!nestedForm) {
                            nestedForm = new FormGroup({});
                            this.addControl(rootForm, formPath, nestedForm);
                        }
                        if (!rootModel[path]) {
                            rootModel[path] = typeof path === 'string' ? {} : [];
                        }
                        rootForm = nestedForm;
                        rootModel = rootModel[path];
                    }
                });
            }
            if (field.fieldGroup) {
                if (!field.type) {
                    field.type = 'formly-group';
                }
                if (field.key) {
                    this.addFormControl(form, field, { [field.key]: field.fieldArray ? [] : {} }, field.key);
                    this._buildForm(/** @type {?} */ (field.formControl), field.fieldGroup, options);
                }
                else {
                    // if `hideExpression` is set in that case we have to deal
                    // with toggle FormControl for each field in fieldGroup separately
                    if (field.hideExpression) {
                        field.fieldGroup.forEach(f => {
                            let /** @type {?} */ hideExpression = f.hideExpression || (() => false);
                            if (typeof hideExpression === 'string') {
                                hideExpression = evalStringExpression(hideExpression, ['model', 'formState']);
                            }
                            f.hideExpression = (model, formState) => field.hide || hideExpression(model, formState);
                        });
                    }
                    this._buildForm(form, field.fieldGroup, options);
                }
            }
        });
    }
    /**
     * @param {?} field
     * @param {?} options
     * @return {?}
     */
    initFieldExpression(field, options) {
        if (field.expressionProperties) {
            for (const /** @type {?} */ key in /** @type {?} */ (field.expressionProperties)) {
                if (typeof field.expressionProperties[key] === 'string' || isFunction(field.expressionProperties[key])) {
                    // cache built expression
                    field.expressionProperties[key] = {
                        expression: isFunction(field.expressionProperties[key]) ? field.expressionProperties[key] : evalStringExpression(field.expressionProperties[key], ['model', 'formState']),
                        expressionValueSetter: evalExpressionValueSetter(`field.${key}`, ['expressionValue', 'model', 'field']),
                    };
                }
            }
        }
        if (field.hideExpression) {
            // delete hide value in order to force re-evaluate it in FormlyFormExpression.
            delete field.hide;
            if (typeof field.hideExpression === 'string') {
                // cache built expression
                field.hideExpression = evalStringExpression(field.hideExpression, ['model', 'formState']);
            }
        }
    }
    /**
     * @param {?} field
     * @return {?}
     */
    initFieldOptions(field) {
        field.templateOptions = field.templateOptions || {};
        if (field.type) {
            this.formlyConfig.getMergedField(field);
            if (field.key) {
                field.templateOptions = Object.assign({
                    label: '',
                    placeholder: '',
                    focus: false,
                }, field.templateOptions);
            }
        }
    }
    /**
     * @param {?} field
     * @return {?}
     */
    initFieldAsyncValidation(field) {
        const /** @type {?} */ validators = [];
        if (field.asyncValidators) {
            for (const /** @type {?} */ validatorName in field.asyncValidators) {
                if (validatorName !== 'validation') {
                    let /** @type {?} */ validator = field.asyncValidators[validatorName];
                    if (isObject(validator)) {
                        validator = validator.expression;
                    }
                    validators.push((control) => new Promise((resolve) => {
                        return validator(control, field).then((result) => {
                            resolve(result ? null : { [validatorName]: true });
                        });
                    }));
                }
            }
        }
        if (field.asyncValidators && Array.isArray(field.asyncValidators.validation)) {
            field.asyncValidators.validation
                .forEach((validator) => validators.push(this.wrapNgValidatorFn(field, validator)));
        }
        if (validators.length) {
            if (field.asyncValidators && !Array.isArray(field.asyncValidators.validation)) {
                field.asyncValidators.validation = Validators.composeAsync([field.asyncValidators.validation, ...validators]);
            }
            else {
                field.asyncValidators = {
                    validation: Validators.composeAsync(validators),
                };
            }
        }
    }
    /**
     * @param {?} field
     * @return {?}
     */
    initFieldValidation(field) {
        const /** @type {?} */ validators = [];
        FORMLY_VALIDATORS
            .filter(opt => (field.templateOptions && field.templateOptions.hasOwnProperty(opt))
            || (field.expressionProperties && field.expressionProperties[`templateOptions.${opt}`]))
            .forEach((opt) => {
            validators.push((control) => {
                if (field.templateOptions[opt] === false) {
                    return null;
                }
                return this.getValidation(opt, field.templateOptions[opt])(control);
            });
        });
        if (field.validators) {
            for (const /** @type {?} */ validatorName in field.validators) {
                if (validatorName !== 'validation') {
                    let /** @type {?} */ validator = field.validators[validatorName];
                    if (isObject(validator)) {
                        validator = validator.expression;
                    }
                    validators.push((control) => validator(control, field) ? null : { [validatorName]: true });
                }
            }
        }
        if (field.validators && Array.isArray(field.validators.validation)) {
            field.validators.validation
                .forEach((validator) => validators.push(this.wrapNgValidatorFn(field, validator)));
        }
        if (validators.length) {
            if (field.validators && !Array.isArray(field.validators.validation)) {
                field.validators.validation = Validators.compose([field.validators.validation, ...validators]);
            }
            else {
                field.validators = {
                    validation: Validators.compose(validators),
                };
            }
        }
    }
    /**
     * @param {?} form
     * @param {?} field
     * @param {?} model
     * @param {?} path
     * @return {?}
     */
    addFormControl(form, field, model, path) {
        let /** @type {?} */ control;
        const /** @type {?} */ validators = field.validators ? field.validators.validation : undefined, /** @type {?} */
        asyncValidators = field.asyncValidators ? field.asyncValidators.validation : undefined, /** @type {?} */
        updateOn = field.modelOptions && field.modelOptions.updateOn ?
            field.modelOptions.updateOn : undefined;
        const /** @type {?} */ abstractControlOptions = /** @type {?} */ ({
            validators,
            asyncValidators,
            updateOn,
        });
        if (field.formControl instanceof AbstractControl || form.get(path)) {
            control = field.formControl || form.get(path);
            if (!(isNullOrUndefined(control.value) && isNullOrUndefined(model[path]))
                && control.value !== model[path]
                && control instanceof FormControl) {
                control.patchValue(model[path]);
            }
        }
        else if (field.component && field.component.createControl) {
            control = field.component.createControl(model[path], field);
        }
        else if (field.fieldGroup && field.key && field.key === path && !field.fieldArray) {
            control = new FormGroup(model[path], abstractControlOptions);
        }
        else if (field.fieldArray && field.key && field.key === path) {
            control = new FormArray([], abstractControlOptions);
        }
        else {
            control = new FormControl(model[path], abstractControlOptions);
        }
        if (field.templateOptions.disabled) {
            control.disable();
        }
        // Replace decorated property with a getter that returns the observable.
        // https://github.com/angular-redux/store/blob/master/src/decorators/select.ts#L79-L85
        if (delete field.templateOptions.disabled) {
            Object.defineProperty(field.templateOptions, 'disabled', {
                get: (function () { return !this.formControl.enabled; }).bind(field),
                set: (function (value) {
                    if (this.expressionProperties && this.expressionProperties.hasOwnProperty('templateOptions.disabled')) {
                        this.expressionProperties['templateOptions.disabled'].expressionValue = value;
                    }
                    value ? this.formControl.disable() : this.formControl.enable();
                }).bind(field),
                enumerable: true,
                configurable: true,
            });
        }
        this.addControl(form, path, control, field);
    }
    /**
     * @param {?} form
     * @param {?} key
     * @param {?} formControl
     * @param {?=} field
     * @return {?}
     */
    addControl(form, key, formControl, field) {
        if (field) {
            field.formControl = formControl;
        }
        if (form instanceof FormArray) {
            if (form.at(/** @type {?} */ (key)) !== formControl) {
                form.setControl(/** @type {?} */ (key), formControl);
            }
        }
        else {
            if (form.get(/** @type {?} */ (key)) !== formControl) {
                form.setControl(/** @type {?} */ (key), formControl);
            }
        }
    }
    /**
     * @param {?} opt
     * @param {?} value
     * @return {?}
     */
    getValidation(opt, value) {
        switch (opt) {
            case 'required':
                return Validators.required;
            case 'pattern':
                return Validators.pattern(value);
            case 'minLength':
                return Validators.minLength(value);
            case 'maxLength':
                return Validators.maxLength(value);
            case 'min':
                return Validators.min(value);
            case 'max':
                return Validators.max(value);
        }
    }
    /**
     * @param {?} field
     * @param {?} validator
     * @return {?}
     */
    wrapNgValidatorFn(field, validator) {
        validator = typeof validator === 'string'
            ? this.formlyConfig.getValidator(validator).validation
            : validator;
        return (control) => (/** @type {?} */ (validator))(control, field);
    }
    /**
     * @param {?} field
     * @return {?}
     */
    initFieldWrappers(field) {
        const /** @type {?} */ templateManipulators = {
            preWrapper: [],
            postWrapper: [],
        };
        if (field.templateOptions) {
            this.mergeTemplateManipulators(templateManipulators, field.templateOptions.templateManipulators);
        }
        this.mergeTemplateManipulators(templateManipulators, this.formlyConfig.templateManipulators);
        const /** @type {?} */ preWrappers = templateManipulators.preWrapper.map(m => m(field)).filter(type => type), /** @type {?} */
        postWrappers = templateManipulators.postWrapper.map(m => m(field)).filter(type => type);
        if (!field.wrappers) {
            field.wrappers = [];
        }
        field.wrappers = [...preWrappers, ...(field.wrappers || []), ...postWrappers];
    }
    /**
     * @param {?} source
     * @param {?} target
     * @return {?}
     */
    mergeTemplateManipulators(source, target) {
        target = target || {};
        if (target.preWrapper) {
            source.preWrapper = source.preWrapper.concat(target.preWrapper);
        }
        if (target.postWrapper) {
            source.postWrapper = source.postWrapper.concat(target.postWrapper);
        }
        return source;
    }
}
FormlyFormBuilder.decorators = [
    { type: Injectable },
];
/** @nocollapse */
FormlyFormBuilder.ctorParameters = () => [
    { type: FormlyConfig, },
    { type: FormlyFormExpression, },
];

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
class FormlyForm {
    /**
     * @param {?} formlyBuilder
     * @param {?} formlyExpression
     * @param {?} formlyConfig
     * @param {?} parentForm
     * @param {?} parentFormGroup
     * @param {?} parentFormlyForm
     */
    constructor(formlyBuilder, formlyExpression, formlyConfig, parentForm, parentFormGroup, parentFormlyForm) {
        this.formlyBuilder = formlyBuilder;
        this.formlyExpression = formlyExpression;
        this.formlyConfig = formlyConfig;
        this.parentForm = parentForm;
        this.parentFormGroup = parentFormGroup;
        this.parentFormlyForm = parentFormlyForm;
        this.model = {};
        this.form = new FormGroup({});
        this.fields = [];
        this.modelChange = new EventEmitter();
        this.modelWillChange = new EventEmitter();
        this.modelDidChange = new EventEmitter();
        /**
         * \@internal
         */
        this.isRoot = true;
        this.modelChangeSubs = [];
    }
    /**
     * @return {?}
     */
    ngDoCheck() {
        this.checkExpressionChange();
    }
    /**
     * @param {?} changes
     * @return {?}
     */
    ngOnChanges(changes) {
        if (!this.fields || this.fields.length === 0 || !this.isRoot) {
            return;
        }
        if (changes["fields"] || changes["form"]) {
            this.modelWillChange.emit();
            this.model = this.model || {};
            this.form = this.form || (new FormGroup({}));
            this.setOptions();
            this.clearModelSubscriptions();
            (/** @type {?} */ (this.options)).components = [];
            this.formlyBuilder.buildForm(this.form, this.fields, this.model, this.options);
            this.trackModelChanges(this.fields);
            this.updateInitialValue();
            this.modelDidChange.emit();
        }
        else if (changes["model"]) {
            this.modelWillChange.emit();
            this.patchModel(this.model);
            this.modelDidChange.emit();
        }
    }
    /**
     * @return {?}
     */
    ngOnDestroy() {
        this.clearModelSubscriptions();
    }
    /**
     * @param {?} event
     * @return {?}
     */
    changeModel(event) {
        assignModelValue(this.model, event.key, event.value);
        this.modelChange.emit(this.model);
        this.checkExpressionChange();
    }
    /**
     * @return {?}
     */
    setOptions() {
        this.options = this.options || {};
        this.options.formState = this.options.formState || {};
        if (!this.options.showError) {
            this.options.showError = this.formlyConfig.extras.showError;
        }
        if (!this.options.fieldChanges) {
            this.options.fieldChanges = new Subject();
        }
        if (!this.options.resetModel) {
            this.options.resetModel = this.resetModel.bind(this);
        }
        if (!this.options.parentForm) {
            this.options.parentForm = this.parentFormGroup || this.parentForm;
        }
        if (!this.options.updateInitialValue) {
            this.options.updateInitialValue = this.updateInitialValue.bind(this);
        }
        if (!(/** @type {?} */ (this.options)).resetTrackModelChanges) {
            (/** @type {?} */ (this.options)).resetTrackModelChanges = () => {
                this.clearModelSubscriptions();
                this.trackModelChanges(this.fields);
            };
        }
    }
    /**
     * @return {?}
     */
    checkExpressionChange() {
        if (this.isRoot) {
            this.formlyExpression.checkFields(this.form, this.fields, this.model, this.options);
        }
    }
    /**
     * @param {?} fields
     * @param {?=} rootKey
     * @return {?}
     */
    trackModelChanges(fields, rootKey = []) {
        fields.forEach(field => {
            if (field.key && field.type && !field.fieldGroup && !field.fieldArray) {
                const /** @type {?} */ valueChanges = field.formControl.valueChanges.pipe(field.modelOptions && field.modelOptions.debounce && field.modelOptions.debounce.default
                    ? debounceTime(field.modelOptions.debounce.default)
                    : tap(() => { }), map(value => {
                    if (field.parsers && field.parsers.length > 0) {
                        field.parsers.forEach(parserFn => value = parserFn(value));
                    }
                    return value;
                }), tap(value => this.changeModel({ key: [...rootKey, field.key].join('.'), value })));
                this.modelChangeSubs.push(valueChanges.subscribe());
            }
            if (field.fieldGroup && field.fieldGroup.length > 0) {
                this.trackModelChanges(field.fieldGroup, field.key ? [...rootKey, field.key] : rootKey);
            }
        });
    }
    /**
     * @return {?}
     */
    clearModelSubscriptions() {
        this.modelChangeSubs.forEach(sub => sub.unsubscribe());
        this.modelChangeSubs = [];
    }
    /**
     * @param {?} model
     * @return {?}
     */
    patchModel(model) {
        this.clearModelSubscriptions();
        this.resetFieldArray(this.fields, model);
        this.initializeFormValue(this.form);
        (/** @type {?} */ (this.form)).patchValue(model, { onlySelf: true });
        this.trackModelChanges(this.fields);
    }
    /**
     * @param {?=} model
     * @return {?}
     */
    resetModel(model) {
        this.modelWillChange.emit();
        (/** @type {?} */ (this.options)).components.forEach(component => {
            if (component.onBeforePatchValue) {
                component.onBeforePatchValue();
            }
        });
        model = isNullOrUndefined(model) ? this.initialModel : model;
        this.resetFieldArray(this.fields, model);
        // we should call `NgForm::resetForm` to ensure changing `submitted` state after resetting form
        // but only when the current component is a root one.
        if (!this.parentFormlyForm && this.options.parentForm && this.options.parentForm.control === this.form) {
            this.options.parentForm.resetForm(model);
        }
        else {
            this.form.reset(model);
        }
        this.modelDidChange.emit();
    }
    /**
     * @param {?} fields
     * @param {?} newModel
     * @return {?}
     */
    resetFieldArray(fields, newModel) {
        fields.forEach(field => {
            if ((field.fieldGroup && field.fieldGroup.length > 0) || field.fieldArray) {
                const /** @type {?} */ newFieldModel = getFieldModel(newModel, field, true);
                if (field.fieldArray) {
                    field.fieldGroup = field.fieldGroup || [];
                    field.fieldGroup.length = 0;
                    if (field.model !== newFieldModel && field.model) {
                        field.model.length = 0;
                    }
                    const /** @type {?} */ formControl = /** @type {?} */ (field.formControl);
                    while (formControl.length !== 0) {
                        formControl.removeAt(0);
                    }
                    newFieldModel.forEach((m, i) => {
                        field.model[i] = m;
                        field.fieldGroup.push(Object.assign({}, clone(field.fieldArray), { key: `${i}` }));
                        this.formlyBuilder.buildForm(formControl, [field.fieldGroup[i]], newFieldModel, this.options);
                    });
                }
                else {
                    this.resetFieldArray(field.fieldGroup, newFieldModel);
                }
            }
            else if (field.key && field.type) {
                field.formControl.reset(getFieldModel(newModel, field, false));
            }
        });
    }
    /**
     * @param {?} control
     * @return {?}
     */
    initializeFormValue(control) {
        if (control instanceof FormControl) {
            control.setValue(null);
        }
        else if (control instanceof FormGroup) {
            Object.keys(control.controls).forEach(k => this.initializeFormValue(control.controls[k]));
        }
        else if (control instanceof FormArray) {
            control.controls.forEach(c => this.initializeFormValue(c));
        }
    }
    /**
     * @return {?}
     */
    updateInitialValue() {
        this.initialModel = reverseDeepMerge({}, this.model);
    }
}
FormlyForm.decorators = [
    { type: Component, args: [{
                selector: 'formly-form',
                template: `
    <formly-field *ngFor="let field of fields"
      [model]="field.model" [form]="form"
      [field]="field"
      [ngClass]="field.className"
      [options]="options">
    </formly-field>
    <ng-content></ng-content>
  `,
            },] },
];
/** @nocollapse */
FormlyForm.ctorParameters = () => [
    { type: FormlyFormBuilder, },
    { type: FormlyFormExpression, },
    { type: FormlyConfig, },
    { type: NgForm, decorators: [{ type: Optional },] },
    { type: FormGroupDirective, decorators: [{ type: Optional },] },
    { type: FormlyForm, decorators: [{ type: Optional }, { type: SkipSelf },] },
];
FormlyForm.propDecorators = {
    "model": [{ type: Input },],
    "form": [{ type: Input },],
    "fields": [{ type: Input },],
    "options": [{ type: Input },],
    "modelChange": [{ type: Output },],
    "modelWillChange": [{ type: Output },],
    "modelDidChange": [{ type: Output },],
    "isRoot": [{ type: Input },],
};

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
class FormlyField {
    /**
     * @param {?} formlyConfig
     * @param {?} componentFactoryResolver
     */
    constructor(formlyConfig, componentFactoryResolver) {
        this.formlyConfig = formlyConfig;
        this.componentFactoryResolver = componentFactoryResolver;
        this.options = {};
        this.modelChange = new EventEmitter();
        this.componentRefs = [];
    }
    /**
     * @return {?}
     */
    ngAfterContentInit() {
        this.lifeCycleHooks(this.lifecycle.afterContentInit);
    }
    /**
     * @return {?}
     */
    ngAfterContentChecked() {
        this.lifeCycleHooks(this.lifecycle.afterContentChecked);
    }
    /**
     * @return {?}
     */
    ngAfterViewInit() {
        this.lifeCycleHooks(this.lifecycle.afterViewInit);
    }
    /**
     * @return {?}
     */
    ngAfterViewChecked() {
        this.lifeCycleHooks(this.lifecycle.afterViewChecked);
    }
    /**
     * @return {?}
     */
    ngDoCheck() {
        this.lifeCycleHooks(this.lifecycle.doCheck);
    }
    /**
     * @return {?}
     */
    ngOnInit() {
        if (!this.field.template) {
            this.createFieldComponent();
        }
        this.lifeCycleHooks(this.lifecycle.onInit);
    }
    /**
     * @param {?} changes
     * @return {?}
     */
    ngOnChanges(changes) {
        this.lifeCycleHooks(this.lifecycle.onChanges);
        this.componentRefs.forEach(ref => {
            Object.assign(ref.instance, {
                model: this.model,
                form: this.form,
                field: this.field,
                options: this.options,
            });
        });
    }
    /**
     * @return {?}
     */
    ngOnDestroy() {
        this.lifeCycleHooks(this.lifecycle.onDestroy);
        this.componentRefs.forEach(componentRef => componentRef.destroy());
        this.componentRefs = [];
    }
    /**
     * @return {?}
     */
    createFieldComponent() {
        const /** @type {?} */ type = this.formlyConfig.getType(this.field.type);
        let /** @type {?} */ fieldComponent = this.fieldComponent;
        (this.field.wrappers || []).forEach(wrapperName => {
            const /** @type {?} */ wrapperRef = this.createComponent(fieldComponent, this.formlyConfig.getWrapper(wrapperName).component);
            fieldComponent = wrapperRef.instance.fieldComponent;
        });
        return this.createComponent(fieldComponent, type.component);
    }
    /**
     * @param {?} fieldComponent
     * @param {?} component
     * @return {?}
     */
    createComponent(fieldComponent, component) {
        let /** @type {?} */ componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
        let /** @type {?} */ ref = /** @type {?} */ (fieldComponent.createComponent(componentFactory));
        Object.assign(ref.instance, {
            model: this.model,
            form: this.form,
            field: this.field,
            options: this.options,
        });
        const /** @type {?} */ optionsComponents = (/** @type {?} */ (this.options)).components;
        if (optionsComponents) {
            optionsComponents.push(ref.instance);
        }
        this.componentRefs.push(ref);
        return ref;
    }
    /**
     * @return {?}
     */
    get lifecycle() {
        return this.field.lifecycle || {};
    }
    /**
     * @param {?} callback
     * @return {?}
     */
    lifeCycleHooks(callback) {
        if (callback) {
            callback(this.form, this.field, this.model, this.options);
        }
    }
}
FormlyField.decorators = [
    { type: Component, args: [{
                selector: 'formly-field',
                template: `
    <ng-template #fieldComponent></ng-template>
    <div *ngIf="field.template && !field.fieldGroup" [innerHtml]="field.template"></div>
  `,
                host: {
                    '[style.display]': 'field.hide ? "none":""',
                },
            },] },
];
/** @nocollapse */
FormlyField.ctorParameters = () => [
    { type: FormlyConfig, },
    { type: ComponentFactoryResolver, },
];
FormlyField.propDecorators = {
    "model": [{ type: Input },],
    "form": [{ type: Input },],
    "field": [{ type: Input },],
    "options": [{ type: Input },],
    "modelChange": [{ type: Output },],
    "fieldComponent": [{ type: ViewChild, args: ['fieldComponent', { read: ViewContainerRef },] },],
};

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
class FormlyAttributes {
    /**
     * @param {?} renderer
     * @param {?} elementRef
     */
    constructor(renderer, elementRef) {
        this.renderer = renderer;
        this.elementRef = elementRef;
        this.attributes = ['id', 'name', 'placeholder', 'tabindex', 'step', 'readonly'];
        this.statements = ['change', 'keydown', 'keyup', 'keypress', 'click', 'focus', 'blur'];
    }
    /**
     * @return {?}
     */
    onFocus() {
        this.field.focus = true;
    }
    /**
     * @return {?}
     */
    onBlur() {
        this.field.focus = false;
    }
    /**
     * @param {?} changes
     * @return {?}
     */
    ngOnChanges(changes) {
        if (changes["field"]) {
            const /** @type {?} */ fieldChanges = changes["field"];
            this.attributes
                .filter(attr => this.canApplyRender(fieldChanges, attr))
                .forEach(attr => this.renderer.setAttribute(this.elementRef.nativeElement, attr, this.getPropValue(this.field, attr)));
            if (this.field.templateOptions && this.field.templateOptions.attributes) {
                const /** @type {?} */ attributes = this.field.templateOptions.attributes;
                Object.keys(attributes).forEach(name => this.renderer.setAttribute(this.elementRef.nativeElement, name, /** @type {?} */ (attributes[name])));
            }
            this.statements
                .filter(statement => this.canApplyRender(fieldChanges, statement))
                .forEach(statement => this.renderer.listen(this.elementRef.nativeElement, statement, this.getStatementValue(statement)));
            if ((fieldChanges.previousValue || {}).focus !== (fieldChanges.currentValue || {}).focus && this.elementRef.nativeElement.focus) {
                this.elementRef.nativeElement[this.field.focus ? 'focus' : 'blur']();
            }
        }
    }
    /**
     * @param {?} field
     * @param {?} prop
     * @return {?}
     */
    getPropValue(field, prop) {
        field = field || {};
        if (field.templateOptions && field.templateOptions[prop]) {
            return field.templateOptions[prop];
        }
        return (/** @type {?} */ (field))[prop] || '';
    }
    /**
     * @param {?} statement
     * @return {?}
     */
    getStatementValue(statement) {
        const /** @type {?} */ fn = this.field.templateOptions[statement];
        return (event) => fn(this.field, event);
    }
    /**
     * @param {?} fieldChange
     * @param {?} prop
     * @return {?}
     */
    canApplyRender(fieldChange, prop) {
        const /** @type {?} */ currentValue = this.getPropValue(this.field, prop), /** @type {?} */
        previousValue = this.getPropValue(fieldChange.previousValue, prop);
        if (previousValue !== currentValue) {
            if (this.statements.indexOf(prop) !== -1) {
                return typeof currentValue === 'function';
            }
            return true;
        }
        return false;
    }
}
FormlyAttributes.decorators = [
    { type: Directive, args: [{
                selector: '[formlyAttributes]',
            },] },
];
/** @nocollapse */
FormlyAttributes.ctorParameters = () => [
    { type: Renderer2, },
    { type: ElementRef, },
];
FormlyAttributes.propDecorators = {
    "field": [{ type: Input, args: ['formlyAttributes',] },],
    "onFocus": [{ type: HostListener, args: ['focus',] },],
    "onBlur": [{ type: HostListener, args: ['blur',] },],
};

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/**
 * @abstract
 */
class Field {
    /**
     * @return {?}
     */
    get key() { return this.field.key; }
    /**
     * @return {?}
     */
    get formControl() { return this.field.formControl; }
    /**
     * @return {?}
     */
    get to() { return this.field.templateOptions; }
    /**
     * @return {?}
     */
    get showError() { return this.options.showError(this); }
    /**
     * @return {?}
     */
    get id() { return this.field.id; }
    /**
     * @return {?}
     */
    get formState() { return this.options.formState || {}; }
    /**
     * @return {?}
     */
    onBeforePatchValue() { }
}
Field.propDecorators = {
    "form": [{ type: Input },],
    "field": [{ type: Input },],
    "model": [{ type: Input },],
    "options": [{ type: Input },],
};

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/**
 * @abstract
 */
class FieldType extends Field {
    /**
     * @return {?}
     */
    ngOnInit() { }
    /**
     * @param {?} changes
     * @return {?}
     */
    ngOnChanges(changes) { }
    /**
     * @return {?}
     */
    ngDoCheck() { }
    /**
     * @return {?}
     */
    ngAfterContentInit() { }
    /**
     * @return {?}
     */
    ngAfterContentChecked() { }
    /**
     * @return {?}
     */
    ngAfterViewInit() { }
    /**
     * @return {?}
     */
    ngAfterViewChecked() { }
    /**
     * @return {?}
     */
    ngOnDestroy() { }
}

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/**
 * @abstract
 */
class FieldArrayType extends FieldType {
    /**
     * @param {?} builder
     */
    constructor(builder) {
        super();
        this.builder = builder;
    }
    /**
     * @param {?=} i
     * @param {?=} initialModel
     * @return {?}
     */
    add(i, initialModel) {
        i = isNullOrUndefined(i) ? this.field.fieldGroup.length : i;
        this.model.splice(i, 0, initialModel ? clone(initialModel) : undefined);
        this.field.fieldGroup.splice(i, 0, Object.assign({}, clone(this.field.fieldArray)));
        this.field.fieldGroup.forEach((field, index) => {
            field.key = `${index}`;
        });
        const /** @type {?} */ form = new FormArray([]);
        this.builder.buildForm(form, [this.field.fieldGroup[i]], this.model, this.options);
        this.formControl.insert(i, form.at(0));
        (/** @type {?} */ (this.options)).resetTrackModelChanges();
    }
    /**
     * @param {?} i
     * @return {?}
     */
    remove(i) {
        this.formControl.removeAt(i);
        this.field.fieldGroup.splice(i, 1);
        this.field.fieldGroup.forEach((f, index) => f.key = `${index}`);
        this.model.splice(i, 1);
        (/** @type {?} */ (this.options)).resetTrackModelChanges();
    }
}

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/**
 * @abstract
 */
class FieldWrapper extends Field {
}

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
class FormlyGroup extends FieldType {
}
FormlyGroup.decorators = [
    { type: Component, args: [{
                selector: 'formly-group',
                template: `
    <formly-form
      [fields]="field.fieldGroup"
      [isRoot]="false"
      [model]="model"
      [form]="field.formControl || form"
      [options]="options"
      [ngClass]="field.fieldGroupClassName">
      <ng-content></ng-content>
    </formly-form>
  `,
            },] },
];

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
class FormlyValidationMessage {
    /**
     * @param {?} formlyConfig
     */
    constructor(formlyConfig) {
        this.formlyConfig = formlyConfig;
    }
    /**
     * @param {?} control
     * @return {?}
     */
    set fieldForm(control) {
        console.warn(`formly-validation-message: Passing 'fieldForm' input is deprecated and it will be removed in the 4.0 version.`);
    }
    /**
     * @return {?}
     */
    get errorMessage() {
        const /** @type {?} */ fieldForm = this.field.formControl;
        for (let /** @type {?} */ error in fieldForm.errors) {
            if (fieldForm.errors.hasOwnProperty(error)) {
                let /** @type {?} */ message = this.formlyConfig.getValidatorMessage(error);
                if (this.field.validation && this.field.validation.messages && this.field.validation.messages[error]) {
                    message = this.field.validation.messages[error];
                }
                if (this.field.validators && this.field.validators[error] && this.field.validators[error].message) {
                    message = this.field.validators[error].message;
                }
                if (this.field.asyncValidators && this.field.asyncValidators[error] && this.field.asyncValidators[error].message) {
                    message = this.field.asyncValidators[error].message;
                }
                if (typeof message === 'function') {
                    return message(fieldForm.errors[error], this.field);
                }
                return message;
            }
        }
    }
}
FormlyValidationMessage.decorators = [
    { type: Component, args: [{
                selector: 'formly-validation-message',
                template: `{{ errorMessage }}`,
            },] },
];
/** @nocollapse */
FormlyValidationMessage.ctorParameters = () => [
    { type: FormlyConfig, },
];
FormlyValidationMessage.propDecorators = {
    "field": [{ type: Input },],
    "fieldForm": [{ type: Input },],
};

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
class FormlyModule {
    /**
     * @param {?=} config
     * @return {?}
     */
    static forRoot(config = {}) {
        return {
            ngModule: FormlyModule,
            providers: [
                FormlyFormBuilder,
                FormlyFormExpression,
                FormlyConfig,
                { provide: FORMLY_CONFIG_TOKEN, useValue: { types: [{ name: 'formly-group', component: FormlyGroup }] }, multi: true },
                { provide: FORMLY_CONFIG_TOKEN, useValue: config, multi: true },
                { provide: ANALYZE_FOR_ENTRY_COMPONENTS, useValue: config, multi: true },
            ],
        };
    }
    /**
     * @param {?=} config
     * @return {?}
     */
    static forChild(config = {}) {
        return {
            ngModule: FormlyModule,
            providers: [
                { provide: FORMLY_CONFIG_TOKEN, useValue: config, multi: true },
                { provide: ANALYZE_FOR_ENTRY_COMPONENTS, useValue: config, multi: true },
            ],
        };
    }
}
FormlyModule.decorators = [
    { type: NgModule, args: [{
                declarations: [FormlyForm, FormlyField, FormlyAttributes, FormlyGroup, FormlyValidationMessage],
                entryComponents: [FormlyGroup],
                exports: [FormlyForm, FormlyField, FormlyAttributes, FormlyGroup, FormlyValidationMessage],
                imports: [
                    CommonModule,
                    ReactiveFormsModule,
                ],
            },] },
];

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/*
 * Public API Surface of core
 */

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/**
 * Generated bundle index. Do not edit.
 */

export { FormlyForm, FormlyField, FormlyAttributes, FormlyConfig, FormlyFormBuilder, Field, FieldType, FieldArrayType, FieldWrapper, FormlyModule, FormlyGroup as ɵc, FORMLY_CONFIG_TOKEN as ɵa, FormlyFormExpression as ɵb, FormlyValidationMessage as ɵd };
//# sourceMappingURL=wfw-ngx-formly.js.map