import { AbstractControl , AbstractControlOptions , AsyncValidatorFn , FormArray , FormBuilder , FormControl , FormGroup , ValidatorFn , } from '@angular/forms' ; import { classToPlain , ClassTransformOptions , plainToClass } from 'class-transformer' ; import { ClassConstructor } from 'class-transformer' ; import { getMetadataStorage , validateSync , ValidationMetadata , ValidationTypes , Validator , ValidatorOptions , } from 'class-validator-multi-lang' ; import stringify from 'fast-safe-stringify' ; import 'reflect-metadata' ; import { BehaviorSubject , combineLatest , Subject , Subscription } from 'rxjs' ; import { distinctUntilChanged , take , tap } from 'rxjs/operators' ; import stringHash from 'string-hash' ; import { getValidatorMessagesStorage } from '../storages/class-validator-messages.storage' ; import { getValidatorTitlesStorage } from '../storages/class-validator-titles.storage' ; import { Dictionary } from '../types/dictionary' ; import { DynamicFormGroupConfig } from '../types/dynamic-form-group-config' ; import { DynamicFormGroupField } from '../types/dynamic-form-group-field' ; import { ErrorPropertyName } from '../types/error-property-name' ; import { FormModel } from '../types/form-model' ; import { ShortValidationErrors } from '../types/short-validation-errors' ; import { ValidatorFunctionType } from '../types/validator-function-type' ; import { foreverInvalid , FOREVER_INVALID_NAME } from '../validators/forever-invalid.validator' ; import { DynamicFormBuilder } from './dynamic-form-builder' ; import { DynamicFormControl } from './dynamic-form-control' ; import { mergeErrors , transformValidationErrors } from './dynamic-form-group.util' ; import { getOrSetEmptyObject } from './get-or-set-empty-object' ; import { hasToJSON } from './has-to-json' ; const cloneDeep = require ( 'lodash.clonedeep' ) ; const validator = new Validator ( ) ; export class DynamicFormGroup < TModel > extends FormGroup { public nativeValidateErrors = new BehaviorSubject < Dictionary > ( { } ) ; public customValidateErrors = new BehaviorSubject < ShortValidationErrors > ( { } ) ; public formErrors : ShortValidationErrors ; public formFields : Dictionary ; public objectChange = new Subject ( ) ; public valueChangesSubscription : Subscription ; public dynamicFormBuilder : DynamicFormBuilder ; public originalFormBuilder : FormBuilder ; protected _object : TModel ; protected _externalErrors : ShortValidationErrors ; protected _validatorOptions : ValidatorOptions ; protected _classTransformOptions : ClassTransformOptions ; protected _classTransformToPlainOptions : ClassTransformOptions ; protected _validateSubscription : Subscription | undefined ; protected _validateAllFormFields : boolean ; constructor ( public factoryModel , public fields ? , protected _validatorOrOpts ? , protected _asyncValidator ? , protected readonly options ? ) { super ( { } , _validatorOrOpts , _asyncValidator ) ; if ( options ?. classTransformOptions ) { this . _classTransformOptions = options . classTransformOptions ; } if ( options ?. classTransformToPlainOptions ) { this . _classTransformToPlainOptions = options . classTransformToPlainOptions ; } if ( options ?. classValidatorOptions ) { this . _validatorOptions = options . classValidatorOptions ; } if ( options ?. validateAllFormFields !== undefined ) { this . _validateAllFormFields = options . validateAllFormFields ; } this . formFields = this . onlyFields ( fields ) ; } subscribeToValueChanges ( externalErrors ? , validatorOptions ? ) { if ( ! this . valueChangesSubscription ) { this . valueChangesSubscription = this . valueChanges . pipe ( distinctUntilChanged ( ) , tap ( ( ) => this . validate ( externalErrors , validatorOptions ) ) ) . subscribe ( { error : ( err ) => { throw err ; } , } ) ; } } set externalErrors ( externalErrors ) { this . _externalErrors = externalErrors ; this . validate ( ) ; } get externalErrors ( ) : ShortValidationErrors { return this . _externalErrors ; } set classTransformOptions ( classTransformOptions ) { this . _classTransformOptions = classTransformOptions ; this . validate ( ) ; } get classTransformOptions ( ) : ClassTransformOptions { return this . _classTransformOptions ; } set classTransformToPlainOptions ( classTransformToPlainOptions ) { this . _classTransformToPlainOptions = classTransformToPlainOptions ; this . validate ( ) ; } get classTransformToPlainOptions ( ) : ClassTransformOptions { return this . _classTransformToPlainOptions ; } set validatorOptions ( validatorOptions ) { this . _validatorOptions = validatorOptions ; this . validate ( ) ; } get validatorOptions ( ) : ValidatorOptions { return this . _validatorOptions ; } set object ( object ) { this . setObject ( object ) ; } get object ( ) { return this . getObject ( ) ; } set json ( jsonData ) { this . setJSON ( jsonData ) ; } get json ( ) { return this . getJSON ( ) ; } validateStream ( externalErrors ? , validatorOptions ? ) { const argumentExternalErrors = externalErrors ? cloneDeep ( externalErrors ) : externalErrors ; const argumentValidatorOptions = validatorOptions ? cloneDeep ( validatorOptions ) : validatorOptions ; return combineLatest ( [ getValidatorMessagesStorage ( ) , getValidatorTitlesStorage ( ) ] ) . pipe ( tap ( ( [ messages , titles ] ) => { validatorOptions = cloneDeep ( this . _validatorOptions || argumentValidatorOptions ) ; externalErrors = cloneDeep ( this . _externalErrors || argumentExternalErrors ) ; if ( ! validatorOptions ) { validatorOptions = { } ; } if ( ! validatorOptions . messages ) { validatorOptions . messages = { } ; } if ( ! validatorOptions . titles ) { validatorOptions . titles = { } ; } if ( messages ) { validatorOptions . messages = { ... cloneDeep ( messages ) , ... validatorOptions . messages } ; } if ( titles ) { validatorOptions . titles = { ... cloneDeep ( titles ) , ... validatorOptions . titles } ; } const dataToValidate = this . object ; const validationErrors = getValidateErrors ( this , dataToValidate , validatorOptions ) ; if ( ! externalErrors ) { externalErrors = { } ; } const allErrors = mergeErrors ( externalErrors , validationErrors ) ; const allErrorsKeys = Object . keys ( allErrors ) ; this . markAsInvalidForExternalErrors ( externalErrors ) ; this . setCustomErrors ( allErrors ) ; let usedForeverInvalid = false ; if ( ! allErrorsKeys . find ( ( key ) => key !== FOREVER_INVALID_NAME ) && this . get ( FOREVER_INVALID_NAME ) ) { this . removeControl ( FOREVER_INVALID_NAME ) ; usedForeverInvalid = true ; } if ( this . valid && allErrorsKeys . length > 0 && ! this . get ( FOREVER_INVALID_NAME ) ) { this . addControl ( FOREVER_INVALID_NAME , new FormControl ( '' , [ foreverInvalid ] ) ) ; usedForeverInvalid = true ; } if ( usedForeverInvalid ) { this . updateValueAndValidity ( { onlySelf : true , emitEvent : false , } ) ; } if ( this . _validateAllFormFields ) { this . validateAllFormFields ( ) ; } } ) ) ; } validateAsync ( externalErrors ? , validatorOptions ? ) { return this . validateStream ( externalErrors , validatorOptions ) . pipe ( take ( 1 ) ) . toPromise ( ) ; } validate ( externalErrors ? , validatorOptions ? ) { if ( this . _validateSubscription ) { this . _validateSubscription . unsubscribe ( ) ; this . _validateSubscription = undefined ; } this . _validateSubscription = this . validateStream ( externalErrors , validatorOptions ) . subscribe ( { error : ( err ) => { throw err ; } , } ) ; } setCustomErrors ( allErrors , force = false ) { if ( force || stringify ( allErrors ) !== stringify ( this . customValidateErrors . value ) ) { this . formErrors = allErrors ; this . customValidateErrors . next ( this . formErrors ) ; } const nativeValidateErrors = this . collectErrors ( this ) ; if ( force || stringify ( nativeValidateErrors ) !== stringify ( this . nativeValidateErrors . value ) ) { this . nativeValidateErrors . next ( this . collectErrors ( this ) ) ; } } protected collectErrors ( control , isRoot = true ) { if ( control . controls ) { return { ... ( isRoot ? this . errors : { } ) , ... Object . entries ( control . controls ) . reduce ( ( acc , [ key , childControl ] ) => { const childErrors = this . collectErrors ( childControl , false ) ; if ( childErrors && Object . keys ( childErrors ) . length > 0 ) { acc = { ... acc , [ key ] : { ... ( acc && acc [ key ] ? acc [ key ] : { } ) , ... childErrors , } , } ; } return acc ; } , { } ) , } ; } else { return control . errors ; } } validateAllFormFields ( ) { let control ; let formArrayControlsLength ; Object . keys ( this . controls ) . forEach ( ( field ) => { control = this . get ( field ) ; if ( control instanceof FormControl ) { control . markAsTouched ( { onlySelf : true } ) ; } else if ( control instanceof DynamicFormGroup ) { control . validateAllFormFields ( ) ; } else if ( control instanceof FormArray ) { formArrayControlsLength = ( control as FormArray ) . controls . length ; for ( let i = 0 ; i < formArrayControlsLength ; i ++ ) { if ( ( control as FormArray ) . controls [ i ] instanceof FormControl ) { ( ( control as FormArray ) . controls [ i ] as FormControl ) . markAsTouched ( { onlySelf : true } ) ; } else if ( ( control as FormArray ) . controls [ i ] instanceof DynamicFormGroup ) { ( ( control as FormArray ) . controls [ i ] as DynamicFormGroup < any > ) . validateAllFormFields ( ) ; } } } } ) ; formArrayControlsLength = null ; control = null ; } resetValidateAllFormFields ( ) { this . markAsInvalidForExternalErrors ( { } ) ; let control ; Object . keys ( this . controls ) . forEach ( ( field ) => { control = this . get ( field ) ; if ( control instanceof FormControl ) { control . setErrors ( null , { emitEvent : false } ) ; control . markAsUntouched ( { onlySelf : true } ) ; control . markAsPristine ( { onlySelf : true } ) ; } else if ( control instanceof DynamicFormGroup ) { control . resetValidateAllFormFields ( ) ; } else if ( control instanceof FormArray ) { const formArrayControlsLength = ( control as FormArray ) . controls . length ; for ( let i = 0 ; i < formArrayControlsLength ; i ++ ) { if ( ( control as FormArray ) . controls [ i ] instanceof FormControl ) { ( ( control as FormArray ) . controls [ i ] as FormControl ) . setErrors ( null , { emitEvent : false } ) ; ( ( control as FormArray ) . controls [ i ] as FormControl ) . markAsUntouched ( { onlySelf : true } ) ; ( ( control as FormArray ) . controls [ i ] as FormControl ) . markAsPristine ( { onlySelf : true } ) ; } else if ( ( control as FormArray ) . controls [ i ] instanceof DynamicFormGroup ) { ( ( control as FormArray ) . controls [ i ] as DynamicFormGroup < any > ) . resetValidateAllFormFields ( ) ; } } } } ) ; control = null ; this . setCustomErrors ( { } ) ; } classToClass < TClassModel > ( object ) { if ( hasToJSON ( object ) ) { return new ( object as any ) . constructor ( ( object as any ) . toJSON ( ) ) ; } return cloneDeep ( object ) ; } plainToClass < TClassModel , Object > ( cls , plain ) { if ( hasToJSON ( getOrSetEmptyObject ( cls ) ) ) { return new cls ( plain ) ; } if ( Object . keys ( plain || { } ) . length === 0 ) { return this . classToClass ( getOrSetEmptyObject ( cls ) ) ; } if ( plain instanceof cls ) { return this . classToClass ( plain ) ; } if ( stringify ( plain ) === stringify ( getOrSetEmptyObject ( cls ) ) ) { return this . classToClass ( getOrSetEmptyObject ( cls ) ) ; } return plainToClass ( cls , plain , { ... this . _classTransformOptions } ) ; } classToPlain < TClassModel > ( object ) { if ( hasToJSON ( object ) ) { return ( object as any ) . toJSON ( ) ; } return classToPlain ( object , { ... this . _classTransformToPlainOptions } ) ; } deprecated setExternalErrorsAsync ( externalErrors ) { this . setExternalErrors ( externalErrors ) ; return Promise . resolve ( ) ; } setExternalErrors ( externalErrors ) { this . _externalErrors = externalErrors ; this . validate ( ) ; } getExternalErrors ( ) { return this . _externalErrors ; } deprecated clearExternalErrorsAsync ( ) { this . setExternalErrors ( { } ) ; return Promise . resolve ( ) ; } clearExternalErrors ( ) { this . setExternalErrors ( { } ) ; } setValidatorOptions ( validatorOptions ? ) { this . _validatorOptions = < ValidatorOptions > validatorOptions ; this . validate ( ) ; } getValidatorOptions ( ) { return this . _validatorOptions ; } protected onlyFields ( fields ? ) { const newFields = { } ; if ( fields !== undefined ) { Object . keys ( fields ) . forEach ( ( key ) => { if ( fields [ key ] instanceof DynamicFormGroup ) { newFields [ key ] = this . onlyFields ( ( fields [ key ] as DynamicFormGroup < any > ) . formFields ) ; } else { if ( fields [ key ] instanceof FormArray ) { if ( ( fields [ key ] as FormArray ) . controls [ 0 ] instanceof DynamicFormGroup ) { newFields [ key ] = this . onlyFields ( ( ( fields [ key ] as FormArray ) . controls [ 0 ] as DynamicFormGroup < any > ) . formFields ) ; } else { newFields [ key ] = ( fields [ key ] as FormArray ) . controls [ 0 ] . value ; } } else { newFields [ key ] = fields [ key ] ; } } } ) ; } return newFields ; } protected markAsInvalidForExternalErrors ( errors , controls ? ) { const currentControls = controls ? controls : this . controls ; Object . keys ( currentControls ) . forEach ( ( field ) => { const control = currentControls [ field ] ; if ( control instanceof FormControl ) { if ( errors && errors [ field ] ) { control . setErrors ( { externalError : true } ) ; } else { if ( control . errors && control . errors . externalError === true ) { control . setErrors ( null ) ; } } } else if ( control instanceof DynamicFormGroup ) { control . markAsInvalidForExternalErrors ( errors && errors [ field ] ? ( errors [ field ] as ShortValidationErrors ) : { } ) ; } else if ( control instanceof FormArray ) { const formArrayControlsLength = ( control as FormArray ) . length ; for ( let i = 0 ; i < formArrayControlsLength ; i ++ ) { if ( control [ i ] instanceof FormControl ) { if ( errors && errors [ i ] && errors [ i ] [ field ] ) { control [ i ] . setErrors ( { externalError : true } ) ; } else if ( control [ i ] . errors && control [ i ] . errors . externalError === true ) { control [ i ] . setErrors ( null ) ; } } else if ( control [ i ] instanceof DynamicFormGroup ) { control [ i ] . markAsInvalidForExternalErrors ( errors && errors [ i ] && errors [ i ] [ field ] ? ( errors [ i ] [ field ] as ShortValidationErrors ) : { } ) ; } } } } ) ; } /**
     * Recursively gets all values from the form controls and all sub form group and array controls and returns it as
     * an object
     */ protected getObject ( ) { const object = this . classToClass ( this . _object || getOrSetEmptyObject ( this . factoryModel ) || undefined ) ; if ( object !== undefined ) { Object . keys ( this . controls ) . filter ( ( name ) => name !== FOREVER_INVALID_NAME ) . forEach ( ( key ) => { if ( this . controls [ key ] instanceof DynamicFormGroup ) { object [ key ] = ( this . controls [ key ] as DynamicFormGroup < any > ) . object ; } else if ( this . controls [ key ] instanceof FormArray ) { object [ key ] = [ ] ; const formArrayControlsLength = ( this . controls [ key ] as FormArray ) . controls . length ; let value ; for ( let i = 0 ; i < formArrayControlsLength ; i ++ ) { if ( ( this . controls [ key ] as FormArray ) . controls [ i ] instanceof DynamicFormGroup ) { value = ( ( this . controls [ key ] as FormArray ) . controls [ i ] as DynamicFormGroup < any > ) . object ; } else { value = ( this . controls [ key ] as FormArray ) . controls [ i ] . value ; } if ( value && Object . keys ( value ) . length > 0 ) { object [ key ] . push ( value ) ; } } value = null ; } else { if ( this . controls [ key ] ) { object [ key ] = this . controls [ key ] . value ; } } } ) ; } return this . factoryModel ? this . plainToClass ( this . factoryModel , object ) : < TModel > object ; } param object protected setObject ( object ) { if ( object instanceof this . factoryModel ) { this . _object = this . classToClass ( object ) ; } else { this . _object = this . plainToClass ( this . factoryModel , object ) ; } let newObject ; let newValue ; Object . keys ( this . controls ) . filter ( ( name ) => name !== FOREVER_INVALID_NAME ) . forEach ( ( key ) => { if ( this . controls [ key ] instanceof DynamicFormGroup ) { if ( ( this . controls [ key ] as DynamicFormGroup < any > ) . _object !== this . _object [ key ] ) { ( this . controls [ key ] as DynamicFormGroup < any > ) . setObject ( this . _object ? this . _object [ key ] : { } ) ; } } else if ( this . controls [ key ] instanceof FormArray ) { let objectArray = this . _object ? this . _object [ key ] : [ ] ; let formArray = this . controls [ key ] as FormArray ; let isFormGroup = formArray . controls [ 0 ] instanceof DynamicFormGroup ; let firstFormGroup = formArray . controls [ 0 ] as DynamicFormGroup < any > ; let formControl = formArray . controls [ 0 ] as FormControl ; let objectArrayLength = objectArray . length ; let dynamicFormGroup ; let controlValue ; let newFormControl ; let classValidators ; let formGroup ; let formArrayLength = formArray . length ; for ( let i = 0 ; i < formArrayLength ; i ++ ) { if ( formArray . controls [ i ] instanceof DynamicFormGroup && ( formArray . controls [ i ] as DynamicFormGroup < any > ) . valueChangesSubscription ) { ( formArray . controls [ i ] as DynamicFormGroup < any > ) . valueChangesSubscription . unsubscribe ( ) ; } } while ( formArrayLength !== 0 ) { formArray . removeAt ( 0 ) ; formArrayLength -- ; } formArrayLength = null ; for ( let i = 0 ; i < objectArrayLength ; i ++ ) { if ( isFormGroup ) { dynamicFormGroup = this . dynamicFormBuilder . factoryDynamicFormGroup ( firstFormGroup . factoryModel , firstFormGroup . formFields ) ; dynamicFormGroup . setParent ( formArray ) ; formGroup = this . originalFormBuilder . group ( getClassValidators < TModel > ( firstFormGroup . factoryModel , firstFormGroup . formFields ) ) ; Object . keys ( formGroup . controls ) . forEach ( ( ctrlKey ) => { dynamicFormGroup . addControl ( ctrlKey , formGroup . controls [ ctrlKey ] ) ; } ) ; formArray . controls . push ( dynamicFormGroup ) ; } else { controlValue = this . _object && objectArray && objectArray [ i ] ? objectArray [ i ] : undefined ; newFormControl = new FormControl ( controlValue , formControl ? formControl . validator : undefined ) ; newFormControl . setParent ( formArray ) ; formArray . controls . push ( newFormControl ) ; } } let arrayLength = formArray . length ; for ( let i = 0 ; i < arrayLength ; i ++ ) { if ( isFormGroup ) { if ( formArray . controls [ i ] instanceof DynamicFormGroup ) { ( formArray . controls [ i ] as DynamicFormGroup < any > ) . setObject ( this . _object && objectArray && objectArray [ i ] ? objectArray [ i ] : { } ) ; } } } for ( let i = 0 ; i < arrayLength ; i ++ ) { if ( isFormGroup ) { if ( formArray . controls [ i ] instanceof DynamicFormGroup ) { ( formArray . controls [ i ] as DynamicFormGroup < any > ) . subscribeToValueChanges ( undefined , this . _validatorOptions ) ; } } } classValidators = null ; formGroup = null ; controlValue = null ; newFormControl = null ; dynamicFormGroup = null ; objectArrayLength = null ; arrayLength = null ; objectArray = null ; formArray = null ; isFormGroup = null ; firstFormGroup = null ; formControl = null ; } else { newObject = this . _object ? this . _object [ key ] : [ ] ; newValue = this . _object && newObject ? newObject : undefined ; if ( this . controls [ key ] && this . controls [ key ] . value !== newValue ) { this . controls [ key ] . setValue ( newValue , { emitEvent : false , onlySelf : true } ) ; } } } ) ; newValue = null ; newObject = null ; this . updateValueAndValidity ( ) ; this . objectChange . next ( this . _object ) ; if ( this . _validateAllFormFields ) { this . validateAllFormFields ( ) ; } } protected getJSON ( ) { return this . classToPlain ( this . getObject ( ) ) ; } protected setJSON ( jsonData ) { this . setObject ( this . plainToClass ( this . factoryModel , jsonData ) ) ; } } export function getClassValidators < TModel > ( factoryModel , fields ? , validatorOptions ? ) { const groups = validatorOptions ? validatorOptions . groups : undefined ; const strictGroups = ( validatorOptions && validatorOptions . strictGroups ) || false ; const always = ( validatorOptions && validatorOptions . always ) || false ; let allValidationMetadatas = getMetadataStorage ( ) . getTargetValidationMetadatas ( factoryModel , '' , always , strictGroups ) ; let validationGroupMetadatas = getMetadataStorage ( ) . getTargetValidationMetadatas ( factoryModel , '' , always , strictGroups , groups ) ; const formGroupFields = { } ; if ( fields ) { let conditionalValidations ; let allNestedValidations ; let nestedGroupValidations ; let fieldDefinition ; let dynamicValidate ; let customValidation ; let objectToValidate ; let nestedValidate ; Object . keys ( fields ) . filter ( ( key ) => key . indexOf ( '__' ) !== 0 ) . forEach ( ( fieldName ) => { conditionalValidations = [ ] ; validationGroupMetadatas . forEach ( ( validationMetadata ) => { if ( isPropertyValidatorOfType ( validationMetadata , fieldName , ValidationKeys . conditional . type ) ) { conditionalValidations . push ( validationMetadata ) ; } } ) ; allNestedValidations = [ ] ; allValidationMetadatas . forEach ( ( validationMetadata ) => { if ( isPropertyValidatorOfType ( validationMetadata , fieldName , ValidationKeys . nested . type ) ) { allNestedValidations . push ( validationMetadata ) ; } } ) ; nestedGroupValidations = [ ] ; validationGroupMetadatas . forEach ( ( validationMetadata ) => { if ( isPropertyValidatorOfType ( validationMetadata , fieldName , ValidationKeys . nested . type ) ) { nestedGroupValidations . push ( validationMetadata ) ; } } ) ; fieldDefinition = { data : formGroupFields [ fieldName ] || undefined , validationFunctions : [ ] , validationDefinitions : [ ] , } ; if ( fieldDefinition . data === undefined ) { fieldDefinition . data = fields [ fieldName ] ; } if ( Array . isArray ( fieldDefinition . data ) && fieldDefinition . data . length > 1 && fieldDefinition . data . find ( ( validationFunction , index ) => index > 0 && typeof validationFunction === 'function' ) ) { fieldDefinition . data . filter ( ( validationFunction , index ) => index > 0 && typeof validationFunction === 'function' ) . forEach ( ( validationFunction ) => fieldDefinition . validationFunctions . push ( { type : 'sync' , validator : validationFunction } ) ) ; fieldDefinition . data = fieldDefinition . data [ 0 ] ; } validationGroupMetadatas . forEach ( ( validationMetadata ) => { if ( validationMetadata . propertyName === fieldName && validationMetadata . type !== ValidationKeys . conditional . type ) { if ( validationMetadata . type !== ValidationKeys . nested . type ) { fieldDefinition . validationDefinitions . push ( validationMetadata ) ; } Object . keys ( ValidationTypes ) . forEach ( ( typeKey ) => { if ( ValidationTypes . hasOwnProperty ( typeKey ) ) { if ( checkWithAllNestedValidations ( allNestedValidations , nestedGroupValidations , fieldName ) ) { if ( isNestedValidate ( validationMetadata , typeKey ) ) { objectToValidate = fields [ fieldName ] instanceof DynamicFormGroup ? fields [ fieldName ] . object : undefined ; nestedValidate = createNestedValidate ( fieldName , objectToValidate , validationMetadata ) ; setFieldData ( fieldName , fieldDefinition , nestedValidate ) ; objectToValidate = null ; nestedValidate = null ; } } if ( isCustomValidate ( validationMetadata , typeKey ) ) { customValidation = createCustomValidation ( fieldName , validationMetadata ) ; setFieldData ( fieldName , fieldDefinition , customValidation ) ; customValidation = null ; } if ( isDynamicValidate ( validationMetadata , typeKey ) ) { dynamicValidate = createDynamicValidate ( fieldName , validationMetadata , conditionalValidations ) ; setFieldData ( fieldName , fieldDefinition , dynamicValidate ) ; dynamicValidate = null ; } } } ) ; } } ) ; if ( fieldDefinition . data instanceof DynamicFormGroup || fieldDefinition . data instanceof FormArray ) { formGroupFields [ fieldName ] = fieldDefinition . data ; } else { formGroupFields [ fieldName ] = new DynamicFormControl ( fieldName , fieldDefinition ) ; } } ) ; dynamicValidate = null ; customValidation = null ; objectToValidate = null ; nestedValidate = null ; conditionalValidations = null ; allNestedValidations = null ; nestedGroupValidations = null ; fieldDefinition = null ; } allValidationMetadatas = null ; validationGroupMetadatas = null ; return formGroupFields ; function createNestedValidate ( fieldName , objectToValidate , validationMetadata ) { return { type : 'sync' , validator : function ( control ) { let parent = control . parent ; let validateErrors = ( parent && parent . value && getValidateErrors ( parent , objectToValidate !== undefined ? objectToValidate : control . value , validatorOptions ) ) || { } ; let validateState = Object . keys ( validateErrors || { } ) . length === 0 ; const result = getIsValidResult ( validateState , validationMetadata , 'nestedValidate' ) ; parent = null ; validateErrors = null ; validateState = null ; return result ; } , } ; } function createDynamicValidate ( fieldName , validationMetadata , conditionalValidations ) { return { type : 'sync' , validator : function ( control ) { if ( ! control ) { return null ; } let validateErrors = setObjectValueAndGetValidationErrors ( fieldName , control , validatorOptions ) ; let validateState = getAllErrors ( validateErrors , fieldName ) . length === 0 ; const result = getIsValidResult ( validateState , validationMetadata , 'dynamicValidate' ) ; validateErrors = null ; validateState = null ; return result ; } , } ; } function createCustomValidation ( fieldName , validationMetadata ) { return { type : 'sync' , validator : function ( control ) { let validateErrors = setObjectValueAndGetValidationErrors ( fieldName , control , validatorOptions ) ; let validateState = validateErrors && validateErrors [ fieldName ] ? false : true ; const result = getIsValidResult ( validateState , validationMetadata , 'customValidation' ) ; validateErrors = null ; validateState = null ; return result ; } , } ; } function checkWithAllNestedValidations ( allNestedValidations , nestedValidations , key ) { const allNestedValidationsLength = allNestedValidations . length ; const nestedValidationsLength = nestedValidations . length ; return ( allNestedValidationsLength === nestedValidationsLength || ( ( ( fields && fields [ key ] instanceof DynamicFormGroup ) || ( fields && fields [ key ] instanceof FormArray ) ) && allNestedValidationsLength > 0 && nestedValidationsLength === 0 ) ) ; } function isDynamicValidate ( validationMetadata , typeKey ) { return validationMetadata . type === ValidationTypes [ typeKey ] && validator [ validationMetadata . type ] !== undefined ; } /**
     * marked with @Validate(...)
     * https://github.com/typestack/class-validator-multi-lang#custom-validation-classes
     */ function isCustomValidate ( validationMetadata , typeKey ) { return ( isNotPropertyValidation ( validationMetadata , typeKey ) && validationMetadata . type === ValidationKeys . custom . type && typeKey === ValidationKeys . custom . typeKey ) ; } /**
     * marked with @ValidateNested()
     * https://github.com/typestack/class-validator-multi-lang#validating-nested-objects
     */ function isNestedValidate ( validationMetadata , typeKey ) { return ( isNotPropertyValidation ( validationMetadata , typeKey ) && validationMetadata . type === ValidationKeys . nested . type && typeKey === ValidationKeys . nested . typeKey ) ; } function isNotPropertyValidation ( validationMetadata , typeKey ) { return validationMetadata . type === ValidationTypes [ typeKey ] && validator [ validationMetadata . type ] === undefined ; } function setFieldData ( fieldName , fieldDefinition , validationFunction ) { if ( fieldDefinition . data === undefined ) { fieldDefinition . data = fields && fields [ fieldName ] ; } fieldDefinition . validationFunctions . push ( validationFunction ) ; } function getAllErrors ( validateErrors , fieldName ) { return Object . keys ( validateErrors ) . filter ( ( key ) => validateErrors [ key ] [ fieldName ] || validateErrors [ fieldName ] ) ; } } function isPropertyValidatorOfType ( validationMetadata , fieldName , validationMetadataType ) { return validationMetadata . propertyName === fieldName && validationMetadata . type === validationMetadataType ; } function setObjectValueAndGetValidationErrors ( fieldName , control , validatorOptions ? ) { let parent = control . parent instanceof DynamicFormGroup ? ( control . parent as DynamicFormGroup < any > ) : control . parent ? control . parent : null ; let object = control . parent instanceof DynamicFormGroup ? ( control . parent as DynamicFormGroup < any > ) . object : control . parent ? control . parent . value : { } ; if ( object ) { object [ fieldName ] = control . value ; } const result = ( parent && parent . value && getValidateErrors ( parent , object , validatorOptions ) ) || { } ; parent = null ; object = null ; return result ; } function getValidateErrors < T > ( control , dataToValidate , validatorOptions ? ) { let validateKey = stringHash ( stringify ( { dataToValidate , validatorOptions } ) ) ; let ctrl = ( control as any ) || { } ; let validatedErrors ; if ( ctrl . __prevValidateKey === validateKey && ctrl . __prevValidateErrors !== undefined ) { validatedErrors = ctrl . __prevValidateErrors ; } else { validatedErrors = transformValidationErrors ( validateSync ( dataToValidate , validatorOptions ) ) ; ctrl . __prevValidateErrors = validatedErrors ; ctrl . __prevValidateKey = validateKey ; } validateKey = null ; ctrl = null ; return validatedErrors ; } function getIsValidResult ( isValid , validationMetadata , errorType ) { return isValid ? null : { [ errorType ] : { valid : false , type : validationMetadata . type , } , } ; } const ValidationKeys = { nested : { type : 'nestedValidation' , typeKey : 'NESTED_VALIDATION' , } , conditional : { type : 'conditionalValidation' , } , custom : { type : 'customValidation' , typeKey : 'CUSTOM_VALIDATION' , } , } ;