# All files / src/app/shared/forms cd-validators.ts

97.44% Statements 76/78
93.75% Branches 30/32
100% Functions 22/22
97.18% Lines 69/71

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    27327x

    27x
    27x
    27x

    27x
    177x

    27x

    27x

    53x

    2x

    27x

    16x
    16x

    5x

    6x

    5x

    51x

    23x

    11x

    27x

    11x

    2x

    27x
    187x

    523x

    479x

    40x

    40x

    625x
    625x
    6294x

    48x

    6246x

    292x

    539x
    538x

    292x

    292x

    670x

    269x

    45x

    356x

    292x
    134x
    25x

    27x
    17x
    139x
    139x

    7x

    132x

    2x
    2x
    2x

    139x

    27x

    55x
    104x

    52x

    57x

    4x

    2x

    2x

    27x
    10x
    10x

    3x

    4x

    27x
     import {
      AbstractControl,
      AsyncValidatorFn,
      ValidationErrors,
      ValidatorFn,
      Validators
    } from '@angular/forms';

    import * as _ from 'lodash';
    import { Observable, of as observableOf, timer as observableTimer } from 'rxjs';
    import { map, switchMapTo, take } from 'rxjs/operators';

    export function isEmptyInputValue(value: any): boolean {
      return value == null || value.length === 0;
    }

    export type existsServiceFn = (value: any) => Observable<boolean>;

    export class CdValidators {
      /**
       * Validator that performs email validation. In contrast to the Angular
       * email validator an empty email will not be handled as invalid.
       */
      static email(control: AbstractControl): ValidationErrors | null {
        // Exit immediately if value is empty.
        if (isEmptyInputValue(control.value)) {
          return null;
        }
        return Validators.email(control);
      }

      /**
       * Validator function in order to validate IP addresses.
       * @param {number} version determines the protocol version. It needs to be set to 4 for IPv4 and
       * to 6 for IPv6 validation. For any other number (it's also the default case) it will return a
       * function to validate the input string against IPv4 OR IPv6.
       * @returns {ValidatorFn} A validator function that returns an error map containing `pattern`
       * if the validation failed, otherwise `null`.
       */
      static ip(version: number = 0): ValidatorFn {
        // prettier-ignore
        const ipv4Rgx =
          /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/i;
        const ipv6Rgx = /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i;

        if (version === 4) {
          return Validators.pattern(ipv4Rgx);
        } else if (version === 6) {
          return Validators.pattern(ipv6Rgx);
        } else {
          return Validators.pattern(new RegExp(ipv4Rgx.source + '|' + ipv6Rgx.source));
        }
      }

      /**
       * Validator function in order to validate numbers.
       * @returns {ValidatorFn} A validator function that returns an error map containing `pattern`
       * if the validation failed, otherwise `null`.
       */
      static number(allowsNegative: boolean = true): ValidatorFn {
        if (allowsNegative) {
          return Validators.pattern(/^-?[0-9]+$/i);
        } else {
          return Validators.pattern(/^[0-9]+$/i);
        }
      }

      /**
       * Validator function in order to validate decimal numbers.
       * @returns {ValidatorFn} A validator function that returns an error map containing `pattern`
       * if the validation failed, otherwise `null`.
       */
      static decimalNumber(allowsNegative: boolean = true): ValidatorFn {
        if (allowsNegative) {
          return Validators.pattern(/^-?[0-9]+(.[0-9]+)?$/i);
        } else {
          return Validators.pattern(/^[0-9]+(.[0-9]+)?$/i);
        }
      }

      /**
       * Validator that requires controls to fulfill the specified condition if
       * the specified prerequisites matches. If the prerequisites are fulfilled,
       * then the given function is executed and if it succeeds, the 'required'
       * validation error will be returned, otherwise null.
       * @param {Object} prerequisites An object containing the prerequisites.
       *   ### Example
       *   ```typescript
       *   {
       *     'generate_key': true,
       *     'username': 'Max Mustermann'
       *   }
       *   ```
       *   Only if all prerequisites are fulfilled, then the validation of the
       *   control will be triggered.
       * @param {Function | undefined} condition The function to be executed when all
       *   prerequisites are fulfilled. If not set, then the {@link isEmptyInputValue}
       *   function will be used by default. The control's value is used as function
       *   argument. The function must return true to set the validation error.
       * @return {ValidatorFn} Returns the validator function.
       */
      static requiredIf(prerequisites: Object, condition?: Function | undefined): ValidatorFn {
        return (control: AbstractControl): ValidationErrors | null => {
          // Check if all prerequisites matches.
          if (
            !Object.keys(prerequisites).every((key) => {
              return control.parent && control.parent.get(key).value === prerequisites[key];
            })
          ) {
            return null;
          }
          const success = _.isFunction(condition)
            ? condition.call(condition, control.value)
            : isEmptyInputValue(control.value);
          return success ? { required: true } : null;
        };
      }

      /**
       * Custom validation by passing a name for the error and a function as error condition.
       *
       * @param {string} error
       * @param {Function} condition - a truthy return value will trigger the error
       * @returns {ValidatorFn}
       */
      static custom(error: string, condition: Function): ValidatorFn {
        return (control: AbstractControl): { [key: string]: any } => {
          const value = condition.call(this, control.value);
          if (value) {
            return { [error]: value };
          }
          return null;
        };
      }

      /**
       * Validate form control if condition is true with validators.
       *
       * @param {AbstractControl} formControl
       * @param {Function} condition
       * @param {ValidatorFn[]} conditionalValidators List of validators that should only be tested
       * when the condition is met
       * @param {ValidatorFn[]} permanentValidators List of validators that should always be tested
       * @param {AbstractControl[]} watchControls List of controls that the condition depend on.
       * Every time one of this controls value is updated, the validation will be triggered
       */
      static validateIf(
        formControl: AbstractControl,
        condition: Function,
        conditionalValidators: ValidatorFn[],
        permanentValidators: ValidatorFn[] = [],
        watchControls: AbstractControl[] = []
      ) {
        conditionalValidators = conditionalValidators.concat(permanentValidators);

        formControl.setValidators(
          (
            control: AbstractControl
          ): {
            [key: string]: any;
          } => {
            const value = condition.call(this);
            if (value) {
              return Validators.compose(conditionalValidators)(control);
            }
            if (permanentValidators.length > 0) {
              return Validators.compose(permanentValidators)(control);
            }
            return null;
          }
        );

        watchControls.forEach((control: AbstractControl) => {
          control.valueChanges.subscribe(() => {
            formControl.updateValueAndValidity({ emitEvent: false });
          });
        });
      }

      /**
       * Validator that requires that both specified controls have the same value.
       * Error will be added to the `path2` control.
       * @param {string} path1 A dot-delimited string that define the path to the control.
       * @param {string} path2 A dot-delimited string that define the path to the control.
       * @return {ValidatorFn} Returns a validator function that always returns `null`.
       *   If the validation fails an error map with the `match` property will be set
       *   on the `path2` control.
       */
      static match(path1: string, path2: string): ValidatorFn {
        return (control: AbstractControl): { [key: string]: any } => {
          const ctrl1 = control.get(path1);
          const ctrl2 = control.get(path2);
          if (ctrl1.value !== ctrl2.value) {
            ctrl2.setErrors({ match: true });
          } else {
            const hasError = ctrl2.hasError('match');
            if (hasError) {
              // Remove the 'match' error. If no more errors exists, then set
              // the error value to 'null', otherwise the field is still marked
              // as invalid.
              const errors = ctrl2.errors;
              _.unset(errors, 'match');
              ctrl2.setErrors(_.isEmpty(_.keys(errors)) ? null : errors);
            }
          }
          return null;
        };
      }

      /**
       * Asynchronous validator that requires the control's value to be unique.
       * The validation is only executed after the specified delay. Every
       * keystroke during this delay will restart the timer.
       * @param serviceFn {existsServiceFn} The service function that is
       *   called to check whether the given value exists. It must return
       *   boolean 'true' if the given value exists, otherwise 'false'.
       * @param serviceFnThis {any} The object to be used as the 'this' object
       *   when calling the serviceFn function. Defaults to null.
       * @param {number|Date} dueTime The delay time to wait before the
       *   serviceFn call is executed. This is useful to prevent calls on
       *   every keystroke. Defaults to 500.
       * @return {AsyncValidatorFn} Returns an asynchronous validator function
       *   that returns an error map with the `notUnique` property if the
       *   validation check succeeds, otherwise `null`.
       */
      static unique(
        serviceFn: existsServiceFn,
        serviceFnThis: any = null,
        EdueTime = 500
      ): AsyncValidatorFn {
        return (control: AbstractControl): Observable<ValidationErrors | null> => {
          // Exit immediately if user has not interacted with the control yet
          // or the control value is empty.
          if (control.pristine || isEmptyInputValue(control.value)) {
            return observableOf(null);
          }
          // Forgot previous requests if a new one arrives within the specified
          // delay time.
          return observableTimer(dueTime).pipe(
            switchMapTo(serviceFn.call(serviceFnThis, control.value)),
            map((resp: boolean) => {
              if (!resp) {
                return null;
              } else {
                return { notUnique: true };
              }
            }),
            take(1)
          );
        };
      }

      /**
       * Validator function for UUIDs.
       * @param required - Defines if it is mandatory to fill in the UUID
       * @return Validator function that returns an error object containing `invalidUuid` if the
       * validation failed, `null` otherwise.
       */
      static uuid(Erequired = false): ValidatorFn {
        const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
        return (control: AbstractControl): { [key: string]: any } | null => {
          if (control.pristine && control.untouched) {
            return null;
          } else if (!required && !control.value) {
            return null;
          } else if (uuidRe.test(control.value)) {
            return null;
          }
          return { invalidUuid: 'This is not a valid UUID' };
        };
      }
    }