material-dynamic-forms
Version:
¡Crea formularios dinámicos, potentes y configurables en Angular usando Material Design! 🚀
1 lines • 96.8 kB
Source Map (JSON)
{"version":3,"file":"material-dynamic-forms.mjs","sources":["../../../projects/dynamic-form/src/lib/interfaces/fieldDynamicForm.interface.ts","../../../projects/dynamic-form/src/lib/functions/functions.ts","../../../projects/dynamic-form/src/lib/dynamic-form.service.ts","../../../projects/dynamic-form/src/lib/utils/date.adapter.ts","../../../projects/dynamic-form/src/lib/dynamic-form.component.ts","../../../projects/dynamic-form/src/lib/dynamic-form.component.html","../../../projects/dynamic-form/src/lib/material.module.ts","../../../projects/dynamic-form/src/lib/dynamic-form.module.ts","../../../projects/dynamic-form/src/public-api.ts","../../../projects/dynamic-form/src/material-dynamic-forms.ts"],"sourcesContent":["import { ValidatorFn } from '@angular/forms';\r\n\r\nexport interface IFieldDynamicForm {\r\n key?: string; // nombre del campo\r\n value?: any; // valor del campo\r\n valueTOShow?: any; // valor que se toma de referencia para el llenado de las options de los selects\r\n label: string; // texto que se muestra en el campo\r\n name: string; // nombre del campo para el formControlName\r\n type: // tipo de campo que se va a mostrar en el formulario\r\n | eDataType.input\r\n | eDataType.select\r\n | eDataType.date\r\n | eDataType.dateMultiple\r\n | eDataType.select_search\r\n | eDataType.tex_area\r\n | eDataType.button\r\n | eDataType.button_toggle\r\n | eDataType.select_chips\r\n | eDataType.select_autocomplete\r\n | eDataType.input_autocomplete\r\n | eDataType.checkbox;\r\n controlType?: // tipo de control que se va a mostrar en el formulario\r\n | 'button'\r\n | 'checkbox'\r\n | 'date'\r\n | 'email'\r\n | 'number'\r\n | 'tel'\r\n | 'text';\r\n typeModel?: eTypeMOdel.boolean | eTypeMOdel.number;\r\n placeholder?: string; // texto que se muestra en el campo\r\n options?: { // opciones que se van a mostrar en el select\r\n key: string | number | null;\r\n value: string;\r\n filterParam?: string;\r\n [key: string]: any;\r\n }[];\r\n selectedOption?: any; // valor seleccionado en el select\r\n icon?: string; // icono que se va a mostrar en el campo\r\n visible: boolean; // si el campo se va a mostrar o no\r\n active?: boolean; // si el campo se va a mostrar o no\r\n disabled?: boolean; // si el campo esta deshabilitado o no\r\n multiple?: boolean;\r\n validation?: ValidatorFn[]; // validaciones que se le van a aplicar al campo\r\n apiUrl?: string; // url de la api que se va a consumir para llenar el select\r\n dependency?: string; // campo del cual depende el campo actual\r\n filterParam?: string; // nombre de parametro a enviar en la api para el post\r\n dateDependency?: string; // campo del cual depende la fecha el campo actual\r\n startDateName?: string; // nombre del campo de fecha de inicio\r\n endDateName?: string; // nombre del campo de fecha de fin\r\n minDate?: Date; // fecha minima que se puede seleccionar\r\n maxDate?: Date; // fecha maxima que se puede seleccionar\r\n minDateFn?: (value: Date) => Date; // Función para calcular la fecha mínima\r\n maxDateFn?: (value: Date) => Date; // Función para calcular la fecha máxima\r\n dateFilterFn?: (d: Date | null) => boolean; // Función para filtrar las fechas\r\n urlParamDependency?: boolean; // cuando se deba enviar el value de la primera dependencia en la misma url dentro del primer {}\r\n urlQueryParam?: boolean; // cuando se deba enviar elos value de las dependencias como query parms\r\n urlEndParam?: boolean; // cuando se deba enviar el value de la primera dependencia en la misma url al final\r\n colspan?: number;\r\n background?: string; // color de fondo del campo\r\n valueDependency?: { // cuando se quiere habilitar un campo si el valor de otro campo es igual a un valor especifico\r\n dependency?: any;\r\n valueDep?: any;\r\n disabled?: boolean;\r\n validation?: ValidatorFn[];\r\n };\r\n valueExcludeDependency?: { // cuando se quiere habilitar un campo si el valor de otro campo es diferente a un valor especifico\r\n dependency?: any;\r\n valueDep?: any;\r\n disabled?: boolean;\r\n validation?: ValidatorFn[];\r\n };\r\n externalDependencies?: string[]; // Lista de nombres de dependencias externas\r\n allOption?: boolean; //Agrega el TODOS en los select\r\n subscribeMap?: Function;\r\n inputAutoComplete?: {\r\n suggestions: any[]; // Sugerencias para autocompletar\r\n showClear?: boolean; // Mostrar botón de limpiar\r\n onCompleteMethod?: (event: any) => void; // Método a llamar al completar\r\n onSelect?: (event: any) => void; // Método a llamar al seleccionar\r\n onClear?: (event: any) => void; // Método a llamar al limpiar\r\n }\r\n}\r\n\r\nexport enum eDataType {\r\n input,\r\n select,\r\n date,\r\n select_search,\r\n range,\r\n button,\r\n checkbox,\r\n dateMultiple,\r\n tex_area,\r\n button_toggle,\r\n select_chips,\r\n select_autocomplete,\r\n input_autocomplete,\r\n}\r\n\r\nexport enum eControlType {\r\n button,\r\n checkbox,\r\n date,\r\n email,\r\n number,\r\n tel,\r\n text,\r\n}\r\n\r\nexport enum eTypeMOdel {\r\n boolean,\r\n number,\r\n}\r\n\r\nexport interface IFieldGroup {\r\n title?: string;\r\n countColumns?: number;\r\n fields: IFieldDynamicForm[];\r\n isRangeValidator?: boolean;\r\n}\r\n","import {\r\n HttpErrorResponse,\r\n HttpParams,\r\n HttpStatusCode,\r\n} from '@angular/common/http';\r\nimport { ConfirmEventType, MessageService } from 'primeng/api';\r\nimport { throwError } from 'rxjs';\r\nimport { ObjectKeyValue } from '../interfaces';\r\n\r\nexport function handleError(error: HttpErrorResponse) {\r\n let errorData = {\r\n status: 0,\r\n message: '',\r\n };\r\n switch (error.status) {\r\n case HttpStatusCode.Unauthorized:\r\n removeItemStorage();\r\n return throwError(error.error.message);\r\n\r\n case HttpStatusCode.BadRequest:\r\n case HttpStatusCode.Conflict:\r\n return throwError(error.error.message);\r\n case HttpStatusCode.PaymentRequired:\r\n errorData.status = error.status;\r\n errorData.message = error.error.data.mensaje;\r\n return throwError(errorData);\r\n case HttpStatusCode.UnprocessableEntity:\r\n return throwError(error.error);\r\n case HttpStatusCode.TooEarly:\r\n return throwError(error.error.message.errors);\r\n case HttpStatusCode.InternalServerError:\r\n return throwError(error.error.previous.message);\r\n\r\n default:\r\n return throwError('Something bad happened; please try again later.');\r\n }\r\n}\r\n\r\nexport function removeNullValuesFromQueryParams(params: HttpParams) {\r\n const paramsKeysAux = params.keys();\r\n paramsKeysAux.forEach((key) => {\r\n const value = params.get(key);\r\n if (\r\n value === null ||\r\n value === undefined ||\r\n value === '' ||\r\n value === 'undefined'\r\n ) {\r\n params['map'].delete(key);\r\n }\r\n });\r\n return params;\r\n}\r\n\r\nexport const FunctionRejectData = (\r\n type: any,\r\n _messageService: MessageService\r\n) => {\r\n switch (type) {\r\n case ConfirmEventType.REJECT:\r\n _messageService.add({\r\n severity: 'error',\r\n summary: 'Rechazada',\r\n detail: 'Rechazada la acción',\r\n });\r\n break;\r\n case ConfirmEventType.CANCEL:\r\n _messageService.add({\r\n severity: 'warn',\r\n summary: 'Cancelado',\r\n detail: 'Cancelada la acción',\r\n });\r\n break;\r\n }\r\n};\r\n\r\nexport function filterObjectKeys<T extends object, K extends keyof T>(\r\n obj: T,\r\n keysToKeep: readonly K[]\r\n): Pick<T, K> {\r\n const newObj: Partial<T> = {};\r\n\r\n keysToKeep.forEach((key: K) => {\r\n if (key in obj) {\r\n newObj[key] = obj[key];\r\n }\r\n });\r\n\r\n return newObj as Pick<T, K>;\r\n}\r\n\r\nexport function getValueFromProperty(row: any, property: string): any {\r\n if (!row || !property) {\r\n return undefined;\r\n }\r\n const properties = property.split('.');\r\n // Itera sobre las propiedades para acceder al valor final\r\n let value = row;\r\n for (const prop of properties) {\r\n value = value[prop];\r\n if (value === undefined) {\r\n return undefined;\r\n }\r\n }\r\n return value;\r\n}\r\n\r\nexport function removeItemStorage() {\r\n localStorage.removeItem('token');\r\n localStorage.removeItem('refresh_token');\r\n localStorage.removeItem('session_id');\r\n localStorage.removeItem('remember');\r\n}\r\n\r\nexport const transformToObject = (\r\n objectSelect: ObjectKeyValue\r\n): { id: number; nombre: string } => {\r\n return {\r\n id: Number(objectSelect.key),\r\n nombre: objectSelect.value,\r\n };\r\n};\r\n","import { Injectable } from '@angular/core';\r\nimport {\r\n FormBuilder,\r\n FormGroup,\r\n Validators,\r\n AbstractControl,\r\n ValidatorFn,\r\n} from '@angular/forms';\r\nimport { HttpClient, HttpParams } from '@angular/common/http';\r\nimport {\r\n IFieldGroup,\r\n IFieldDynamicForm,\r\n eDataType,\r\n KeyValue,\r\n ObjectKeyValue,\r\n eTypeMOdel,\r\n} from './interfaces';\r\nimport { getValueFromProperty, transformToObject } from './functions/functions';\r\n\r\n@Injectable()\r\nexport class DynamicFormService {\r\n private baseUrl: string = '';\r\n\r\n // Agregamos esta variable para almacenar los valores externos\r\n private externalDependenciesValues: { [key: string]: any } = {};\r\n\r\n // Declarar formGroupControls como propiedad\r\n private formGroupControls: { [key: string]: AbstractControl } = {};\r\n\r\n // Variable para manejar el modo de edición\r\n private isEditMode: boolean = false;\r\n\r\n private isReadonly: boolean = false; // Add readonly flag\r\n\r\n constructor(private fb: FormBuilder, private http: HttpClient) { }\r\n\r\n public setBaseUrl(url: string): void {\r\n this.baseUrl = url;\r\n }\r\n\r\n // Método para establecer el modo de edición\r\n public setEditMode(isEdit: boolean): void {\r\n this.isEditMode = isEdit;\r\n }\r\n\r\n // Add method to set readonly\r\n public setReadonly(isReadonly: boolean): void {\r\n this.isReadonly = isReadonly;\r\n }\r\n\r\n // Permite setear/actualizar los valores externos desde el componente\r\n public setExternalDependenciesValues(values: { [key: string]: any }): void {\r\n this.externalDependenciesValues = {\r\n ...this.externalDependenciesValues,\r\n ...values,\r\n };\r\n }\r\n\r\n public removeExternalDependency(key: string): void {\r\n if (this.externalDependenciesValues.hasOwnProperty(key)) {\r\n delete this.externalDependenciesValues[key];\r\n }\r\n }\r\n\r\n functionInitComponent(\r\n fieldGroupsDynamicForm: IFieldGroup[],\r\n form: FormGroup\r\n ): FormGroup {\r\n this.formGroupControls = {};\r\n let isRangeValidator: boolean = false;\r\n\r\n const initializeControl = (field: IFieldDynamicForm) => {\r\n const initialValue =\r\n field.value !== undefined\r\n ? field.value\r\n : field.type === eDataType.select_chips\r\n ? []\r\n : '';\r\n const validators = this.isReadonly ? [] : field.validation || [];\r\n const isDisabled =\r\n this.isReadonly || !!field.dependency || field.disabled;\r\n return this.fb.control(\r\n { value: initialValue, disabled: isDisabled },\r\n validators\r\n );\r\n };\r\n\r\n const handleDependenciesValue = (\r\n field: IFieldDynamicForm,\r\n control: any\r\n ) => {\r\n if (!field.valueDependency && !field.valueExcludeDependency) {\r\n return;\r\n }\r\n\r\n const updateField = () => {\r\n const depValue = field.valueDependency\r\n ? this.formGroupControls[field.valueDependency.dependency] &&\r\n this.formGroupControls[field.valueDependency.dependency].value\r\n : field.valueExcludeDependency\r\n ? this.formGroupControls[field.valueExcludeDependency.dependency] &&\r\n this.formGroupControls[field.valueExcludeDependency.dependency]\r\n .value\r\n : undefined;\r\n const valueComparar = field.valueDependency\r\n ? field.valueDependency.valueDep\r\n : field.valueExcludeDependency\r\n ? field.valueExcludeDependency.valueDep\r\n : undefined;\r\n\r\n if (field.valueDependency && depValue === valueComparar) {\r\n control.enable();\r\n control.clearValidators();\r\n control.setValidators(field.valueDependency.validation ?? []);\r\n control.updateValueAndValidity();\r\n } else if (field.valueExcludeDependency && depValue !== valueComparar) {\r\n control.enable();\r\n control.clearValidators();\r\n control.setValidators(field.valueExcludeDependency.validation ?? []);\r\n control.updateValueAndValidity();\r\n } else {\r\n control.disable();\r\n control.reset();\r\n control.clearValidators();\r\n control.updateValueAndValidity();\r\n }\r\n };\r\n\r\n if (field.valueDependency) {\r\n this.formGroupControls[\r\n field.valueDependency.dependency\r\n ].valueChanges.subscribe(updateField);\r\n } else if (field.valueExcludeDependency) {\r\n this.formGroupControls[\r\n field.valueExcludeDependency.dependency\r\n ].valueChanges.subscribe(updateField);\r\n }\r\n\r\n updateField();\r\n };\r\n\r\n const handleDependencies = (field: IFieldDynamicForm, control: any) => {\r\n if (!field.dependency) {\r\n return;\r\n }\r\n\r\n const dependencyFields = field.dependency.split(',');\r\n dependencyFields.forEach((depField) => {\r\n if (!this.formGroupControls[depField]) {\r\n this.formGroupControls[depField] = this.fb.control(\r\n '',\r\n Validators.required\r\n );\r\n }\r\n });\r\n\r\n const updateField = () => {\r\n if (\r\n (field.valueDependency || field.valueExcludeDependency) &&\r\n control.disabled\r\n ) {\r\n return;\r\n }\r\n\r\n const allDependenciesFilled = dependencyFields.every(\r\n (depField) =>\r\n this.formGroupControls[depField] &&\r\n this.formGroupControls[depField]?.value\r\n );\r\n\r\n if (allDependenciesFilled) {\r\n const dependencyValue =\r\n this.formGroupControls[dependencyFields[0]].value;\r\n\r\n if (\r\n !field.valueDependency &&\r\n !field.valueExcludeDependency &&\r\n field.disabled\r\n ) {\r\n control.disable();\r\n } else {\r\n control.enable();\r\n }\r\n if (!this.isEditMode) control.reset();\r\n\r\n // Verificar si hay dependencias externas\r\n const hasExternalDeps =\r\n field.externalDependencies && field.externalDependencies.length > 0;\r\n\r\n // Aquí se unifica la lógica de obtención de datos\r\n if (field.apiUrl && dependencyValue) {\r\n this.fetchOptions(field, dependencyValue, hasExternalDeps);\r\n }\r\n } else {\r\n control.reset();\r\n field.options = [];\r\n if (!field.valueDependency && !field.valueExcludeDependency) {\r\n control.disable();\r\n }\r\n }\r\n };\r\n\r\n dependencyFields.forEach((depField) => {\r\n this.formGroupControls[depField].valueChanges.subscribe(updateField);\r\n });\r\n\r\n updateField();\r\n };\r\n\r\n const handleDynamicDateConstraints = (\r\n field: IFieldDynamicForm,\r\n control: any\r\n ) => {\r\n if (!field.dateDependency || (!field.minDateFn && !field.maxDateFn)) {\r\n return;\r\n }\r\n\r\n const dependencyField = this.formGroupControls[field.dateDependency];\r\n if (!dependencyField) {\r\n console.error(\r\n `El campo de dependencia ${field.dateDependency} no existe.`\r\n );\r\n return;\r\n }\r\n\r\n const updateConstraints = () => {\r\n const dependencyValue: Date | null = dependencyField.value\r\n ? new Date(dependencyField.value)\r\n : null;\r\n\r\n if (dependencyValue) {\r\n // Asegurarse de que la hora sea 00:00:00\r\n dependencyValue.setHours(0, 0, 0, 0);\r\n }\r\n\r\n if (field.minDateFn && dependencyValue) {\r\n const calculatedMinDate = field.minDateFn(dependencyValue);\r\n field.minDate = calculatedMinDate;\r\n control.setValidators([\r\n ...(control.validator ? [control.validator] : []),\r\n Validators.min(calculatedMinDate.getTime()),\r\n ]);\r\n }\r\n\r\n if (field.maxDateFn && dependencyValue) {\r\n const calculatedMaxDate = field.maxDateFn(dependencyValue);\r\n field.maxDate = calculatedMaxDate;\r\n control.setValidators([\r\n ...(control.validator ? [control.validator] : []),\r\n Validators.max(calculatedMaxDate.getTime()),\r\n ]);\r\n }\r\n\r\n control.updateValueAndValidity();\r\n };\r\n\r\n dependencyField.valueChanges.subscribe(updateConstraints);\r\n updateConstraints(); // Aplicar al inicializar\r\n\r\n // Forzar una actualización manual del valor del campo dependiente\r\n setTimeout(() => {\r\n dependencyField.updateValueAndValidity();\r\n }, 0);\r\n };\r\n\r\n fieldGroupsDynamicForm.forEach((group: IFieldGroup) => {\r\n group.fields.forEach((field: IFieldDynamicForm) => {\r\n if (field.visible) {\r\n // Inicializar control\r\n const control = initializeControl(field);\r\n\r\n // Agregar control al objeto de controles\r\n if (field.name) this.formGroupControls[field.name] = control;\r\n\r\n // Agregar opciones al campo select sin dependencias\r\n if (\r\n field.apiUrl &&\r\n !field.urlParamDependency &&\r\n !field.urlQueryParam &&\r\n !field.urlEndParam\r\n ) {\r\n this.fetchOptions(field, null, false);\r\n }\r\n if (!this.isReadonly) {\r\n // Manejar dependencias de values de campos internas\r\n if (field.valueDependency || field.valueExcludeDependency)\r\n handleDependenciesValue(field, control);\r\n\r\n // Manejar dependencias de campos\r\n if (field.dependency) handleDependencies(field, control);\r\n\r\n // Manejar dependencias de fechas\r\n if (field.type === eDataType.date)\r\n handleDynamicDateConstraints(field, control);\r\n }\r\n }\r\n });\r\n isRangeValidator = group.isRangeValidator ?? false;\r\n });\r\n\r\n form = this.fb.group(this.formGroupControls);\r\n if (isRangeValidator) form.setValidators(this.rangeValidator);\r\n if (this.isEditMode) {\r\n this.markControlsAsTouched(form);\r\n }\r\n\r\n if (this.isReadonly) {\r\n form.disable(); // Disable the entire form if readonly\r\n }\r\n\r\n return form;\r\n }\r\n\r\n markControlsAsTouched(form: FormGroup) {\r\n Object.keys(form.controls).forEach((key) => {\r\n const control = form.get(key);\r\n if (control) {\r\n control.markAsTouched();\r\n control.updateValueAndValidity(); // Actualiza los errores y validaciones\r\n }\r\n });\r\n }\r\n\r\n public handleExternalDependenciesService(\r\n field: IFieldDynamicForm,\r\n control: AbstractControl\r\n ): () => void {\r\n if (!control) {\r\n console.warn(`Control no encontrado para el campo ${field.name}`);\r\n return () => { };\r\n }\r\n\r\n const checkDependencies = () => {\r\n // 1) Verificar dependencias externas\r\n const depsExtOk = field.externalDependencies?.every(\r\n (dep) => this.externalDependenciesValues?.[dep] !== undefined\r\n );\r\n\r\n // 2) Verificar dependencias de formulario (field.dependency)\r\n let depsFormOk = true;\r\n if (field.dependency) {\r\n const dependencyFields = field.dependency.split(',');\r\n // Chequea que todos esos campos tengan algún valor\r\n depsFormOk = dependencyFields.every(\r\n (depField) => !!this.formGroupControls[depField]?.value\r\n );\r\n }\r\n\r\n // Si todo está OK, habilitar campo y llamar a la API\r\n if (depsExtOk && depsFormOk) {\r\n control.enable();\r\n if (field.apiUrl) {\r\n // Verificar si hay dependencias externas\r\n const hasExternalDeps =\r\n field.externalDependencies && field.externalDependencies.length > 0;\r\n // fetchOptions con \"useExternalDeps = true\" si hay dependencias externas\r\n this.fetchOptions(field, undefined, hasExternalDeps);\r\n }\r\n } else if (!this.isEditMode || !control.value) {\r\n // Si no está en modo edición o el control no tiene valor, deshabilita\r\n control.disable();\r\n control.reset();\r\n field.options = [];\r\n }\r\n // En modo edición y tiene valor, no hacer nada para mantener el valor\r\n };\r\n\r\n // Ejecuta inicialmente\r\n checkDependencies();\r\n\r\n // Retorna la función para que el componente la invoque cuando cambien las dependencias\r\n return checkDependencies;\r\n }\r\n\r\n rangeValidator: ValidatorFn = (\r\n group: AbstractControl\r\n ): { [key: string]: any } | null => {\r\n const desde = Number(group.get('desde')?.value);\r\n const hasta = Number(group.get('hasta')?.value);\r\n return desde <= hasta ? null : { rangeError: true };\r\n };\r\n\r\n functionGetFormValue(\r\n form: FormGroup,\r\n fieldGroupsDynamicForm: IFieldGroup[]\r\n ): KeyValue {\r\n let dataFilterSend: KeyValue = {} as KeyValue;\r\n\r\n fieldGroupsDynamicForm.forEach((group) => {\r\n group.fields.forEach((fieldDynamicForm) => {\r\n const control = form.get(fieldDynamicForm.name!);\r\n\r\n if (control) {\r\n if (control.value !== '' && control.value !== null) {\r\n fieldDynamicForm.active = true;\r\n\r\n switch (fieldDynamicForm.type) {\r\n case eDataType.select:\r\n fieldDynamicForm.value = control.value;\r\n if (control.value === -1) {\r\n fieldDynamicForm.options = [\r\n { key: -1, value: 'TODOS' },\r\n ...fieldDynamicForm.options!,\r\n ];\r\n }\r\n fieldDynamicForm.selectedOption =\r\n fieldDynamicForm.options?.find(\r\n (option) => option.key === control.value\r\n );\r\n fieldDynamicForm.filterParam!\r\n ? (dataFilterSend[fieldDynamicForm.filterParam!] =\r\n fieldDynamicForm.selectedOption\r\n ? (fieldDynamicForm.selectedOption[\r\n 'key'\r\n ] as ObjectKeyValue)\r\n : null)\r\n : (dataFilterSend[fieldDynamicForm.name!] =\r\n fieldDynamicForm.selectedOption\r\n ? transformToObject(\r\n fieldDynamicForm.selectedOption as ObjectKeyValue\r\n )\r\n : null);\r\n break;\r\n case eDataType.date:\r\n fieldDynamicForm.value = control.value || null;\r\n dataFilterSend[fieldDynamicForm.name!] = fieldDynamicForm.value;\r\n break;\r\n default:\r\n fieldDynamicForm.value =\r\n fieldDynamicForm.typeModel === eTypeMOdel.number\r\n ? Number(control.value)\r\n : control.value;\r\n //fieldDynamicForm.value = control.value;\r\n dataFilterSend[fieldDynamicForm.name!] = fieldDynamicForm.value;\r\n break;\r\n }\r\n } else {\r\n fieldDynamicForm.active = false;\r\n fieldDynamicForm.value = null;\r\n dataFilterSend[fieldDynamicForm.name!] = null;\r\n }\r\n }\r\n });\r\n });\r\n\r\n return dataFilterSend;\r\n }\r\n\r\n private buildUrlAndParams(\r\n field: IFieldDynamicForm,\r\n dependencyValue?: any,\r\n useExternalDeps: boolean = false\r\n ): { url: string; params: HttpParams } {\r\n let url = `${this.baseUrl}${field.apiUrl}`;\r\n let params = new HttpParams();\r\n\r\n // 1. urlParamDependency (ej: /api/{id})\r\n if (field.urlParamDependency && dependencyValue) {\r\n url = url.replace(/\\{.*?\\}/g, dependencyValue);\r\n }\r\n // 2. urlQueryParam (ej: ?depField=value)\r\n else if (field.urlQueryParam && field.dependency) {\r\n const dependencyFields = field.dependency.split(',');\r\n dependencyFields.forEach((depField) => {\r\n params = params.set(depField, this.formGroupControls[depField].value);\r\n });\r\n }\r\n // 3. urlEndParam (ej: /api/id)\r\n else if (field.urlEndParam && dependencyValue) {\r\n url = `${url}/${dependencyValue}`;\r\n }\r\n\r\n // 4. Agregar dependencias externas si se indica\r\n if (useExternalDeps && field.externalDependencies) {\r\n field.externalDependencies.forEach((dep) => {\r\n if (this.externalDependenciesValues[dep] !== undefined) {\r\n params = params.append(dep, this.externalDependenciesValues[dep]);\r\n }\r\n });\r\n }\r\n\r\n return { url, params };\r\n }\r\n\r\n private fetchOptions(\r\n field: IFieldDynamicForm,\r\n dependencyValue?: any,\r\n useExternalDeps: boolean = false\r\n ): void {\r\n if (!field.apiUrl) {\r\n return;\r\n }\r\n\r\n const { url, params } = this.buildUrlAndParams(\r\n field,\r\n dependencyValue,\r\n useExternalDeps\r\n );\r\n\r\n this.http.get(url, { params }).subscribe((res: any) => {\r\n field.options = field.subscribeMap\r\n ? field.subscribeMap(res).map((item: any) => ({\r\n key: item.id,\r\n value: field.valueTOShow\r\n ? getValueFromProperty(item, field.valueTOShow)\r\n : item.nombre || item.name,\r\n data: item.data,\r\n }))\r\n : res.data.map((item: any) => ({\r\n key: item.id,\r\n value: field.valueTOShow\r\n ? item[field.valueTOShow]\r\n : item.nombre || item.name,\r\n data: item,\r\n }));\r\n field.options?.sort((a, b) =>\r\n (a.value || '').localeCompare(b.value || '')\r\n );\r\n });\r\n }\r\n}\r\n","import { NativeDateAdapter } from '@angular/material/core';\r\n\r\nconst CONST_TYPE_STRING = 'string';\r\nconst CONST_TYPE_NUMBER = 'number';\r\nconst CONST_TYPE_NUMERIC = 'numeric';\r\nconst CONST_TYPE_LONG = 'long';\r\nconst CONST_DISPLAY_FORMAT_INPUT = 'input';\r\nconst CONST_DISPLAY_FORMAT_INPUTMONTH = 'inputMonth';\r\n\r\nexport class AppDateAdapter extends NativeDateAdapter {\r\n override parse(value: any): Date | null {\r\n if (typeof value === CONST_TYPE_STRING && value.indexOf('/') > -1) {\r\n const str = value.split('/');\r\n const year = Number(str[2]);\r\n const month = Number(str[1]) - 1;\r\n const date = Number(str[0]);\r\n return new Date(year, month, date);\r\n }\r\n const timestamp = typeof value === CONST_TYPE_NUMBER ? value : Date.parse(value);\r\n return isNaN(timestamp) ? null : new Date(timestamp);\r\n }\r\n override format(date: Date, displayFormat: string): string {\r\n if (displayFormat === CONST_DISPLAY_FORMAT_INPUT) {\r\n let day = date.getDate();\r\n let month = date.getMonth() + 1;\r\n let year = date.getFullYear();\r\n return this._to2digit(day) + '/' + this._to2digit(month) + '/' + year;\r\n } else if (displayFormat === CONST_DISPLAY_FORMAT_INPUTMONTH) {\r\n let month = date.getMonth() + 1;\r\n let year = date.getFullYear();\r\n return this._to2digit(month) + '/' + year;\r\n } else {\r\n return date.toDateString();\r\n }\r\n }\r\n\r\n private _to2digit(n: number) {\r\n return ('00' + n).slice(-2);\r\n }\r\n}\r\n\r\nexport const APP_DATE_FORMATS = {\r\n parse: {\r\n dateInput: { month: 'short', year: CONST_TYPE_NUMERIC, day: CONST_TYPE_NUMERIC },\r\n },\r\n display: {\r\n dateInput: CONST_DISPLAY_FORMAT_INPUT,\r\n monthYearLabel: CONST_DISPLAY_FORMAT_INPUTMONTH,\r\n dateA11yLabel: { year: CONST_TYPE_NUMERIC, month: CONST_TYPE_LONG, day: CONST_TYPE_NUMERIC },\r\n monthYearA11yLabel: { year: CONST_TYPE_NUMERIC, month: CONST_TYPE_LONG },\r\n },\r\n};\r\n","import {\r\n Component,\r\n EventEmitter,\r\n Inject,\r\n Input,\r\n OnChanges,\r\n OnDestroy,\r\n OnInit,\r\n Output,\r\n SimpleChanges\r\n} from '@angular/core';\r\nimport {\r\n eDataType,\r\n IFieldDynamicForm,\r\n IFieldGroup,\r\n} from './interfaces/fieldDynamicForm.interface';\r\nimport {\r\n FormGroup,\r\n} from '@angular/forms';\r\nimport { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';\r\nimport { KeyValue } from './interfaces';\r\nimport {\r\n DateAdapter,\r\n MAT_DATE_FORMATS,\r\n MAT_DATE_LOCALE,\r\n} from '@angular/material/core';\r\nimport { DynamicFormService } from './dynamic-form.service';\r\nimport { APP_DATE_FORMATS, AppDateAdapter } from './utils/date.adapter';\r\n\r\n@Component({\r\n selector: 'lib-dynamic-form',\r\n templateUrl: './dynamic-form.component.html',\r\n styleUrls: ['./dynamic-form.component.scss'],\r\n providers: [\r\n DynamicFormService,\r\n {\r\n provide: DateAdapter,\r\n useClass: AppDateAdapter,\r\n },\r\n {\r\n provide: MAT_DATE_FORMATS,\r\n useValue: APP_DATE_FORMATS,\r\n },\r\n {\r\n provide: MAT_DATE_LOCALE,\r\n useValue: 'es-ES',\r\n },\r\n ],\r\n})\r\nexport class DynamicFormComponent implements OnInit, OnChanges, OnDestroy {\r\n @Input() fieldGroups!: IFieldGroup[];\r\n @Input() isModal: boolean = false;\r\n @Input() baseUrl!: string;\r\n @Input() resetFormTrigger!: boolean;\r\n @Input() isEdit: boolean = false;\r\n @Input() isReadonlyForm: boolean = false;\r\n\r\n @Output() changeValueSelected = new EventEmitter<{\r\n form: IFieldDynamicForm;\r\n value: any;\r\n }>();\r\n @Output() formValidityChange = new EventEmitter<boolean>();\r\n @Output() externalDependenciesValuesChanged = new EventEmitter<void>();\r\n\r\n form!: FormGroup;\r\n eDataType = eDataType;\r\n title: string = '';\r\n titleButton: string = 'Guardar';\r\n value!: string;\r\n selectedOption: any = null;\r\n\r\n constructor(\r\n @Inject(MAT_DIALOG_DATA) private data: any,\r\n private modal: MatDialogRef<any>,\r\n private dynamicFormService: DynamicFormService\r\n ) { }\r\n\r\n ngOnInit(): void {\r\n this.inizializarForm();\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.form.reset();\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges) {\r\n // Detecta si el valor de resetFormTrigger cambia\r\n if (\r\n changes['resetFormTrigger'] &&\r\n changes['resetFormTrigger'].currentValue\r\n ) {\r\n this.form.reset(); // Resetea el formulario\r\n }\r\n // Detecta si el valor de baseUrl cambia\r\n if (\r\n changes['baseUrl'] &&\r\n changes['baseUrl'].currentValue\r\n ) {\r\n this.dynamicFormService.setBaseUrl(this.baseUrl);\r\n }\r\n // Detecta si el valor de isEdit cambia\r\n if (\r\n changes['isEdit'] &&\r\n changes['isEdit'].currentValue\r\n ) {\r\n this.dynamicFormService.setEditMode(this.isEdit);\r\n }\r\n }\r\n\r\n inizializarForm() {\r\n if (this.isReadonlyForm) {\r\n this.dynamicFormService.setReadonly(true);\r\n }\r\n // En el caso que se esta utilizando un formulario desde una modal MatDialog\r\n if (this.data?.dataForm) {\r\n this.fieldGroups = this.data.dataForm.fieldGroups;\r\n this.isModal = true;\r\n this.title = this.data.title;\r\n this.titleButton = this.data.dataForm.titleButtonAcceptForm;\r\n if (this.data.dataForm.baseUrl) {\r\n this.dynamicFormService.setBaseUrl(this.data.dataForm.baseUrl);\r\n this.baseUrl = this.data.dataForm.baseUrl;\r\n }\r\n if (this.data.dataForm.form) {\r\n this.form = this.data.dataForm.form;\r\n }\r\n if (this.data.dataForm.edit) {\r\n this.isEdit = this.data.dataForm.edit;\r\n this.dynamicFormService.setEditMode(this.data.dataForm.edit);\r\n }\r\n }\r\n\r\n // Inicializa el formulario\r\n if (this.fieldGroups) {\r\n this.form = this.dynamicFormService.functionInitComponent(\r\n this.fieldGroups,\r\n this.form\r\n );\r\n\r\n // Manejar campos con dependencias externas\r\n this.fieldGroups.forEach((group) => {\r\n group.fields.forEach((field) => {\r\n if (field.externalDependencies) {\r\n this.handleExternalDependencies(field);\r\n }\r\n });\r\n });\r\n }\r\n\r\n // Emito la validacion actual del form\r\n this.formValidityChange.emit(this.form.valid);\r\n\r\n // Escucha los cambios de estado del formulario y emite el resultado\r\n this.form.statusChanges.subscribe(() => {\r\n this.formValidityChange.emit(this.form.valid);\r\n });\r\n }\r\n\r\n /**\r\n * Método para manejar dependencias externas.\r\n */\r\n handleExternalDependencies(field: IFieldDynamicForm) {\r\n const control = this.form.get(field.name);\r\n // Llamamos al servicio\r\n const checkDependencies = this.dynamicFormService.handleExternalDependenciesService(\r\n field,\r\n control!\r\n );\r\n\r\n // Suscribimos el EventEmitter para volver a verificar las dependencias\r\n // cuando cambien los valores externos\r\n this.externalDependenciesValuesChanged.subscribe(() => {\r\n checkDependencies?.();\r\n });\r\n }\r\n\r\n // Método para actualizar las dependencias externas\r\n updateExternalDependencies(values: { [key: string]: any }) {\r\n // Actualizas la variable en el servicio\r\n this.dynamicFormService.setExternalDependenciesValues(values);\r\n\r\n // Emite el evento si deseas que otros sitios en el componente reaccionen\r\n this.externalDependenciesValuesChanged.emit();\r\n }\r\n\r\n // Método para actualizar el valor de un campo del formulario\r\n updateFormField(fieldName: string, value: any): void {\r\n if (this.form && this.form.controls[fieldName]) {\r\n this.form.controls[fieldName].setValue(value);\r\n }\r\n }\r\n\r\n /**\r\n * Función pública para deshabilitar un campo específico\r\n * @param fieldName Nombre del campo a deshabilitar\r\n */\r\n public disableField(fieldName: string): void {\r\n const control = this.form.get(fieldName);\r\n if (control) {\r\n control.disable();\r\n } else {\r\n console.warn(`El campo con nombre \"${fieldName}\" no existe en el formulario.`);\r\n }\r\n }\r\n\r\n /**\r\n * Función pública para habilitar un campo específico\r\n * @param fieldName Nombre del campo a habilitar\r\n */\r\n public enableField(fieldName: string): void {\r\n const control = this.form.get(fieldName);\r\n if (control) {\r\n control.enable();\r\n } else {\r\n console.warn(`El campo con nombre \"${fieldName}\" no existe en el formulario.`);\r\n }\r\n }\r\n\r\n public removeExternalDependency(key: string): void {\r\n this.dynamicFormService.removeExternalDependency(key);\r\n this.externalDependenciesValuesChanged.emit();\r\n }\r\n\r\n public refreshDependentFields(): void {\r\n // Emit the event to refresh dependencies\r\n this.externalDependenciesValuesChanged.emit();\r\n }\r\n\r\n public simulateFieldChange(fieldName: string): void {\r\n const control = this.form.get(fieldName);\r\n if (control) {\r\n const currentValue = control.value;\r\n control.setValue(currentValue);\r\n }\r\n }\r\n\r\n public getFormFieldValue(fieldName: string): any {\r\n return this.form.get(fieldName)?.value;\r\n }\r\n\r\n onSubmit() {\r\n let listadoFormGet: KeyValue = this.dynamicFormService.functionGetFormValue(\r\n this.form,\r\n this.fieldGroups\r\n );\r\n this.modal.close(listadoFormGet);\r\n }\r\n\r\n emitEvent(nameForm: IFieldDynamicForm, event?: any) {\r\n let value: any;\r\n value = this.form.get(nameForm.name!)?.value;\r\n\r\n if (event) {\r\n value = event.value;\r\n }\r\n this.changeValueSelected.emit({ form: nameForm, value: value });\r\n }\r\n\r\n getForm(): FormGroup<any> {\r\n return this.form;\r\n }\r\n\r\n getErrorMessage(fieldName: string): string | null {\r\n const control = this.form.get(fieldName);\r\n\r\n if (control?.hasError('required')) {\r\n return 'Este campo es obligatorio';\r\n }\r\n if (control?.hasError('min')) {\r\n return `El valor debe ser mayor o igual a ${control.getError('min').min}`;\r\n }\r\n if (control?.hasError('max')) {\r\n return `El valor debe ser menor o igual a ${control.getError('max').max}`;\r\n }\r\n if (control?.hasError('email')) {\r\n return 'Debe ser un correo electrónico válido';\r\n }\r\n if (control?.hasError('pattern')) {\r\n return 'El formato no es válido';\r\n }\r\n if (control?.hasError('minlength')) {\r\n return `Debe tener al menos ${control.getError('minlength').requiredLength} caracteres`;\r\n }\r\n if (control?.hasError('maxlength')) {\r\n return `Debe tener como máximo ${control.getError('maxlength').requiredLength} caracteres`;\r\n }\r\n\r\n // Agrega más validaciones según sea necesario\r\n return null;\r\n }\r\n\r\n getGridColumns(columnCount: number): string {\r\n return `repeat(${columnCount || 1}, 1fr)`;\r\n }\r\n\r\n getColSpanClass(colspan: number | undefined): string {\r\n return colspan ? `col-span-${colspan}` : 'col-span-1';\r\n }\r\n}\r\n","<form [formGroup]=\"form\" class=\"my-2\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"flex items-center\" mat-dialog-title *ngIf=\"isModal\">\r\n <h2 class=\"headline m-0 flex-auto\">{{ title }}</h2>\r\n </div>\r\n <mat-divider *ngIf=\"isModal\" class=\"-mx-6 text-border\"></mat-divider>\r\n\r\n <mat-dialog-content class=\"flex flex-col\">\r\n <div *ngFor=\"let groups of fieldGroups\">\r\n <div mat-dialog-title *ngIf=\"groups.title\">\r\n <h6 class=\"title-group m-0 flex-auto\">{{ groups.title }}</h6>\r\n </div>\r\n <div\r\n class=\"grid-container\"\r\n [style.gridTemplateColumns]=\"getGridColumns(groups.countColumns || 1)\"\r\n >\r\n <div\r\n *ngFor=\"let field of groups.fields\"\r\n [class]=\"getColSpanClass(field.colspan)\"\r\n >\r\n <div [ngSwitch]=\"field.type\" *ngIf=\"field.visible\">\r\n <!-- INPUT -->\r\n <mat-form-field\r\n *ngSwitchCase=\"eDataType.input\"\r\n appearance=\"outline\"\r\n class=\"w-full\"\r\n [attr.style]=\"\r\n field.background\r\n ? '--dynamic-background-color: ' + field.background\r\n : 'transparent'\r\n \"\r\n >\r\n <mat-label>{{ field.label }}</mat-label>\r\n <input\r\n [type]=\"field.controlType!\"\r\n matInput\r\n [placeholder]=\"field.placeholder!\"\r\n [formControlName]=\"field.name!\"\r\n [value]=\"field.value ? field.value : ''\"\r\n (blur)=\"emitEvent(field)\"\r\n />\r\n <mat-icon\r\n [svgIcon]=\"field.icon!\"\r\n class=\"icon-sm\"\r\n matSuffix\r\n ></mat-icon>\r\n\r\n <!-- MENSAJES DE ERROR -->\r\n <mat-error\r\n *ngIf=\"\r\n form.controls[field.name]?.invalid &&\r\n (form.controls[field.name]?.touched ||\r\n form.controls[field.name]?.dirty)\r\n \"\r\n >\r\n {{ getErrorMessage(field.name) }}\r\n </mat-error>\r\n </mat-form-field>\r\n\r\n <!-- SELECT -->\r\n <mat-form-field\r\n *ngSwitchCase=\"eDataType.select\"\r\n appearance=\"outline\"\r\n class=\"w-full\"\r\n [attr.style]=\"\r\n field.background\r\n ? '--dynamic-background-color: ' + field.background\r\n : 'transparent'\r\n \"\r\n >\r\n <mat-label>{{ field.label }}</mat-label>\r\n <mat-select\r\n [formControlName]=\"field.name!\"\r\n (selectionChange)=\"emitEvent(field, $event)\"\r\n >\r\n <mat-option *ngIf=\"field.allOption\" [value]=\"-1\"\r\n >TODOS</mat-option\r\n >\r\n <mat-option\r\n *ngFor=\"let option of field.options\"\r\n [value]=\"option.key\"\r\n >\r\n {{ option.value }}\r\n </mat-option>\r\n </mat-select>\r\n\r\n <!-- MENSAJES DE ERROR -->\r\n <mat-error\r\n *ngIf=\"\r\n form.controls[field.name]?.invalid &&\r\n (form.controls[field.name]?.touched ||\r\n form.controls[field.name]?.dirty)\r\n \"\r\n >\r\n {{ getErrorMessage(field.name) }}\r\n </mat-error>\r\n </mat-form-field>\r\n\r\n <!-- SELECT WITH AUTOCOMPLETE -->\r\n <div\r\n *ngSwitchCase=\"eDataType.select_autocomplete\"\r\n style=\"margin-bottom: 15px\"\r\n >\r\n <h2 *ngIf=\"field.label\" class=\"label-custom\">\r\n {{ field.label }}\r\n </h2>\r\n <p-dropdown\r\n [options]=\"field.options!\"\r\n [formControlName]=\"field.name\"\r\n [filter]=\"true\"\r\n [showClear]=\"true\"\r\n [placeholder]=\"field.placeholder || 'Seleccione'\"\r\n (onChange)=\"emitEvent(field, $event)\"\r\n optionLabel=\"value\"\r\n optionValue=\"key\"\r\n appendTo=\"body\"\r\n filterBy=\"value\"\r\n >\r\n </p-dropdown>\r\n <!-- MENSAJES DE ERROR -->\r\n <mat-error\r\n *ngIf=\"\r\n form.controls[field.name]?.invalid &&\r\n (form.controls[field.name]?.touched ||\r\n form.controls[field.name]?.dirty)\r\n \"\r\n >\r\n {{ getErrorMessage(field.name) }}\r\n </mat-error>\r\n </div>\r\n\r\n <!-- INPUT AUTOCOMPLETE -->\r\n <div\r\n *ngSwitchCase=\"eDataType.input_autocomplete\"\r\n style=\"margin-bottom: 15px\"\r\n >\r\n <h2 *ngIf=\"field.label\" class=\"label-custom\">\r\n {{ field.label }}\r\n </h2>\r\n <span class=\"p-input-icon-right w-full mr-2\">\r\n <i class=\"pi pi-search\"></i>\r\n <p-autoComplete\r\n [formControlName]=\"field.name\"\r\n [placeholder]=\"field.placeholder || ''\"\r\n [suggestions]=\"field.inputAutoComplete?.suggestions || []\"\r\n [showClear]=\"field.inputAutoComplete?.showClear || true\"\r\n (completeMethod)=\"\r\n field.inputAutoComplete?.onCompleteMethod($event)\r\n \"\r\n (onClear)=\"field.inputAutoComplete?.onClear($event)\"\r\n (onSelect)=\"field.inputAutoComplete?.onSelect($event)\"\r\n field=\"value\"\r\n optionValue=\"key\"\r\n styleClass=\"w-full\"\r\n inputStyleClass=\"w-full p-inputtext\"\r\n appendTo=\"body\"\r\n >\r\n </p-autoComplete>\r\n </span>\r\n <!-- MENSAJES DE ERROR -->\r\n <mat-error\r\n *ngIf=\"\r\n form.controls[field.name]?.invalid &&\r\n (form.controls[field.name]?.touched ||\r\n form.controls[field.name]?.dirty)\r\n \"\r\n >\r\n {{ getErrorMessage(field.name) }}\r\n </mat-error>\r\n </div>\r\n\r\n <!-- SELECT-CHIPS -->\r\n <div *ngSwitchCase=\"eDataType.select_chips\">\r\n <h2 class=\"label-custom\">{{ field.label }}</h2>\r\n <p-multiSelect\r\n [options]=\"field.options!\"\r\n [formControlName]=\"field.name\"\r\n [defaultLabel]=\"field.placeholder || ''\"\r\n (onChange)=\"emitEvent(field, $event)\"\r\n optionLabel=\"value\"\r\n optionValue=\"key\"\r\n display=\"chip\"\r\n appendTo=\"body\"\r\n ></p-multiSelect>\r\n <!-- MENSAJES DE ERROR -->\r\n <mat-error\r\n *ngIf=\"\r\n form.controls[field.name]?.invalid &&\r\n (form.controls[field.name]?.touched ||\r\n form.controls[field.name]?.dirty)\r\n \"\r\n >\r\n {{ getErrorMessage(field.name) }}\r\n </mat-error>\r\n </div>\r\n\r\n <!-- DATE -->\r\n <ng-container *ngSwitchCase=\"eDataType.date\">\r\n <ng-container\r\n *ngIf=\"!field.multiple; then singleDate; else rangeDate\"\r\n >\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- DATE-SINGLE -->\r\n <ng-template #singleDate>\r\n <mat-form-field\r\n appearance=\"outline\"\r\n class=\"w-full\"\r\n [attr.style]=\"\r\n field.background\r\n ? '--dynamic-background-color: ' + field.background\r\n : 'transparent'\r\n \"\r\n >\r\n <mat-label style=\"font-size: 12px\">{{ field.label }}</mat-label>\r\n <input\r\n [matDatepicker]=\"datepickerRef\"\r\n [formControlName]=\"field.name!\"\r\n matInput\r\n (dateChange)=\"emitEvent(field, $event)\"\r\n [min]=\"field.minDate || null\"\r\n [max]=\"field.maxDate || null\"\r\n [matDatepickerFilter]=\"field.dateFilterFn!\"\r\n />\r\n <mat-datepicker-toggle\r\n [for]=\"datepickerRef\"\r\n class=\"block\"\r\n matSuffix\r\n ></mat-datepicker-toggle>\r\n <mat-datepicker\r\n #datepickerRef\r\n class=\"end-position\"\r\n ></mat-datepicker>\r\n\r\n <!-- MENSAJES DE ERROR -->\r\n <mat-error\r\n *ngIf=\"\r\n form.controls[field.name]?.invalid &&\r\n (form.controls[field.name]?.touched ||\r\n form.controls[field.name]?.dirty)\r\n \"\r\n >\r\n {{ getErrorMessage(field.name) }}\r\n </mat-error>\r\n </mat-form-field>\r\n </ng-template>\r\n\r\n <!-- DATE-RANGE -->\r\n <ng-template #rangeDate>\r\n <mat-form-field\r\n appearance=\"outline\"\r\n class=\"w-full\"\r\n [attr.style]=\"\r\n field.background\r\n ? '--dynamic-background-color: ' + field.background\r\n : 'transparent'\r\n \"\r\n >\r\n <mat-label>{{ field.label }}</mat-label>\r\n <mat-date-range-input [rangePicker]=\"picker\">\r\n <input\r\n matStartDate\r\n placeholder=\"Fecha Inicial\"\r\n [formControlName]=\"field.name + 'start'\"\r\n (dateChange)=\"emitEvent(field, $event)\"\r\n />\r\n <input\r\n matEndDate\r\n placeholder=\"Fecha Final\"\r\n [formControlName]=\"field.name + 'end'\"\r\n />\r\n </mat-date-range-input>\r\n\r\n <mat-datepicker-toggle\r\n matIconSuffix\r\n [for]=\"picker\"\r\n ></mat-datepicker-toggle>\r\n <mat-date-range-picker #picker></mat-date-range-picker>\r\n\r\n <!-- MENSAJES DE ERROR -->\r\n <mat-error\r\n *ngIf=\"\r\n form.controls[field.name]?.invalid &&\r\n (form.controls[field.name]?.touched ||\r\n form.controls[field.name]?.dirty)\r\n \"\r\n >\r\n {{ getErrorMessage(field.name) }}\r\n