import { AbstractControlOptions , AsyncValidatorFn , FormBuilder , ValidatorFn } from '@angular/forms' ; import { ClassConstructor } from 'class-transformer' ; import 'reflect-metadata' ; import { DEFAULT_CLASS_TRANSFORM_OPTIONS , DEFAULT_CLASS_TRANSFORM_TO_PLAIN_OPTIONS , DEFAULT_CLASS_VALIDATOR_OPTIONS , } from '../constants/default' ; import { DynamicFormBuilderOptions } from '../types/dynamic-form-builder-options' ; import { DynamicFormGroupConfig , isAbstractControlOptions , isDynamicFormGroupConfig , isLegacyOrOpts , } from '../types/dynamic-form-group-config' ; import { FormModel } from '../types/form-model' ; import { DynamicFormGroup , getClassValidators } from './dynamic-form-group' ; const cloneDeep = require ( 'lodash.clonedeep' ) ; export class DynamicFormBuilder extends FormBuilder { protected emptyDynamicFormGroup = this . factoryDynamicFormGroup ( Object ) ; constructor ( protected options ? ) { super ( ) ; } group < TModel > ( factoryModel , controlsConfig ? , options ? ) { if ( ! controlsConfig && ! options ) { options = { } ; } if ( controlsConfig && ( isAbstractControlOptions ( controlsConfig ) || isLegacyOrOpts ( controlsConfig ) || isDynamicFormGroupConfig ( controlsConfig ) ) && Object . keys ( options || { } ) . length === 0 ) { return this . group ( factoryModel , undefined , controlsConfig ) ; } let extra = cloneDeep ( options ) as DynamicFormGroupConfig ; let validators = null ; let asyncValidators = null ; let updateOn ; if ( extra != null ) { if ( isAbstractControlOptions ( extra ) ) { validators = extra . validators != null ? extra . validators : null ; asyncValidators = extra . asyncValidators != null ? extra . asyncValidators : null ; updateOn = extra . updateOn != null ? extra . updateOn : undefined ; } if ( isLegacyOrOpts ( extra ) ) { validators = validators || [ ] ; if ( extra . validator != null ) { validators . push ( extra . validator ) ; } asyncValidators = asyncValidators || [ ] ; if ( extra . asyncValidator != null ) { asyncValidators . push ( extra . asyncValidator ) ; } } } else { extra = { } ; } if ( this . options ?. classValidatorOptions && ! extra . classValidatorOptions ) { extra . classValidatorOptions = this . options ?. classValidatorOptions ; } if ( this . options ?. classTransformOptions && ! extra . classTransformOptions ) { extra . classTransformOptions = this . options ?. classTransformOptions ; } if ( this . options ?. classTransformToPlainOptions && ! extra . classTransformToPlainOptions ) { extra . classTransformToPlainOptions = this . options ?. classTransformToPlainOptions ; } if ( this . options ?. validateAllFormFields !== undefined && extra . validateAllFormFields === undefined ) { extra . validateAllFormFields = this . options ?. validateAllFormFields ; } if ( ! extra . classValidatorOptions ) { extra . classValidatorOptions = DEFAULT_CLASS_VALIDATOR_OPTIONS ; } if ( ! extra . classTransformOptions ) { extra . classTransformOptions = DEFAULT_CLASS_TRANSFORM_OPTIONS ; } if ( ! extra . classTransformToPlainOptions ) { extra . classTransformToPlainOptions = DEFAULT_CLASS_TRANSFORM_TO_PLAIN_OPTIONS ; } if ( extra . validateAllFormFields === undefined ) { extra . validateAllFormFields = false ; } let newControlsConfig ; if ( controlsConfig !== undefined ) { newControlsConfig = controlsConfig as FormModel < TModel > ; } if ( controlsConfig === undefined ) { newControlsConfig = { ... this . createEmptyObject ( factoryModel , { __experimental__ : true } ) } ; if ( newControlsConfig !== undefined ) { Object . keys ( newControlsConfig ) . forEach ( ( key ) => { if ( canCreateGroup ( ) && newControlsConfig ) { newControlsConfig [ key ] = this . group ( newControlsConfig [ key ] . constructor , undefined , { classValidatorOptions : extra . classValidatorOptions , classTransformOptions : extra . classTransformOptions , classTransformToPlainOptions : extra . classTransformToPlainOptions , validateAllFormFields : extra . validateAllFormFields , asyncValidators , updateOn , validators , } ) ; } else { if ( canCreateArray ( ) && newControlsConfig ) { if ( newControlsConfig [ key ] [ 0 ] . constructor ) { newControlsConfig [ key ] = super . array ( newControlsConfig [ key ] . map ( ( newControlsConfigItem ) => this . group ( newControlsConfigItem . constructor , undefined , { classValidatorOptions : extra . classValidatorOptions , classTransformOptions : extra . classTransformOptions , classTransformToPlainOptions : extra . classTransformToPlainOptions , validateAllFormFields : extra . validateAllFormFields , asyncValidators , updateOn , validators , } ) ) ) ; } else { newControlsConfig [ key ] = super . array ( newControlsConfig [ key ] . map ( ( newControlsConfigItem ) => this . control ( newControlsConfigItem ) ) ) ; } } } function canCreateGroup ( ) { const candidate = newControlsConfig && newControlsConfig [ key ] ; return ( candidate && ! Array . isArray ( candidate ) && candidate . constructor && typeof candidate === 'object' && ( candidate . length === undefined || ( candidate . length !== undefined && Object . keys ( candidate ) . length === candidate . length ) ) ) ; } function canCreateArray ( ) { if ( Array . isArray ( newControlsConfig && newControlsConfig [ key ] ) === false ) { return false ; } const candidate = newControlsConfig && newControlsConfig [ key ] [ 0 ] ; return ( candidate . constructor && typeof candidate === 'object' && ( candidate . length === undefined || ( candidate . length !== undefined && Object . keys ( candidate ) . length === candidate . length ) ) ) ; } } ) ; } } validators = validators && validators . filter ( ( validator ) => validator ) ; asyncValidators = asyncValidators && asyncValidators . filter ( ( validator ) => validator ) ; let classValidators = getClassValidators < TModel > ( factoryModel , newControlsConfig , extra && extra . classValidatorOptions ) ; let formGroup = super . group ( classValidators , { ... ( asyncValidators || { } ) , ... ( updateOn || { } ) , ... ( validators || { } ) , } ) ; const dynamicFormGroup = this . factoryDynamicFormGroup < TModel > ( factoryModel , newControlsConfig , { asyncValidators , updateOn , validators , } , undefined , extra ) ; Object . keys ( formGroup . controls ) . forEach ( ( key ) => { dynamicFormGroup . addControl ( key , formGroup . controls [ key ] ) ; } ) ; dynamicFormGroup . subscribeToValueChanges ( undefined , extra && extra . classValidatorOptions ) ; classValidators = null ; formGroup = null ; if ( extra . validateAllFormFields ) { dynamicFormGroup . validate ( ) ; } return dynamicFormGroup ; } protected factoryFormBuilder ( ) { return new FormBuilder ( ) ; } public factoryDynamicFormGroup < TModel > ( factoryModel , fields ? , validatorOrOpts ? , asyncValidator ? , options ? ) { const formGroup = this . options ?. factoryDynamicFormGroup ? this . options . factoryDynamicFormGroup ( factoryModel , fields , validatorOrOpts , asyncValidator , options ) : new DynamicFormGroup < TModel > ( factoryModel , fields , validatorOrOpts , asyncValidator , options ) ; formGroup . dynamicFormBuilder = this ; formGroup . originalFormBuilder = this . options ?. factoryFormBuilder ? this . options ?. factoryFormBuilder ( ) : this . factoryFormBuilder ( ) ; return formGroup ; } /**
     * Recursively creates an empty object from the data provided
     */ protected createEmptyObject < TModel > ( factoryModel , data = { } ) { let modifed = false ; let object = factoryModel ? this . emptyDynamicFormGroup . plainToClass ( factoryModel , data ) : data ; let fields = Object . keys ( object ) ; let objectFieldNameLength ; let objectFieldName0 ; fields . forEach ( ( fieldName ) => { const object1 = object [ fieldName ] ; objectFieldNameLength = object1 && Array . isArray ( object1 ) ? object1 . length : undefined ; if ( objectFieldNameLength !== undefined ) { objectFieldName0 = object1 [ 0 ] ; if ( objectFieldNameLength === 1 && Object . keys ( objectFieldName0 ) . length > 0 && objectFieldName0 . constructor ) { object [ fieldName ] = [ this . createEmptyObject ( objectFieldName0 . constructor ) ] ; } if ( objectFieldNameLength === 0 ) { data [ fieldName ] = [ { } ] ; modifed = true ; } } else { data [ fieldName ] = undefined ; } } ) ; if ( modifed ) { object = null ; fields = null ; return this . createEmptyObject ( factoryModel , data ) ; } fields = null ; return object ; } }