16

I have an input where user needs to type a longitude. I want to be able to display different error message when user types NaN or Not-Precise-Enough value. I'm using FormControl.hasError('ValidatorsName') to get the errors with validation, but it seems like I cannot differentiate those patterns.

Template:

<mat-form-field class="form-field">
    <input matInput placeholder="Logitude" [formControl]="longitude">
    <mat-error *ngIf="longitude.hasError('pattern')">
        Longitude must be <strong>a number</strong>
    </mat-error>
    <!-- I want to be able to check patter2 for example -->
    <mat-error *ngIf="longitude.hasError('pattern')"> 
        Longitude must have <strong>a minimum of 5 decimal numbers</strong>
    </mat-error>
</mat-form-field>

And my Angular code:

this.longitude = new FormControl(this.attraction.longitude, [
    // is valid number
    Validators.pattern(new RegExp('(\d{1,3})([.|,]{,1})(\d+))','gi')),
    // is precise enough
    Validators.pattern(new RegExp('(\-{0,1})(\d{1,3})([.|,]{1})(\d{5,13})','i'))
]);

Is there any way to give those patterns an identifier? I'll appreciate any help.

Dawid Zbiński
  • 5,521
  • 8
  • 43
  • 70

3 Answers3

20

Unfortunately it is not possible to give Validators a custom identifier.

UPDATE 17.02.2018 21:00

As Dawid Zbinski mentioned in the comments that multiple errors with the same error (e.g.: 'pattern') getting override, I updated my answer.

Create a custom validator which gets passed the regex and a predefined error as an argument:

  regexValidator(regex: RegExp, error: ValidationErrors): ValidatorFn {
    return (control: AbstractControl): {[key: string]: any} => {
      if (!control.value) {
        return null;
      }
      const valid = regex.test(control.value);
      return valid ? null : error;
    };
  }

and use it like this:

  this.longitude = new FormControl('', [
    this.regexValidator(new RegExp('^[0-9]+$'), {'number': ''}),
    this.regexValidator(new RegExp('^.{5,}$'), {'precision': ''})
  ]);
  <mat-form-field class="form-field">
    <input matInput placeholder="Logitude" [formControl]="longitude">
    <mat-error *ngIf="longitude.hasError('number')">
        Longitude must be <strong>a number</strong>
    </mat-error>
    <!-- I want to be able to check patter2 for example -->
    <mat-error *ngIf="longitude.hasError('precision')">
        Longitude must have <strong>a minimum of 5 decimal numbers</strong>
    </mat-error>
</mat-form-field>

I also updated the stackblitz demo: https://stackblitz.com/edit/angular-dvwcj3?file=app%2Fhello.component.ts

OLD ANSWER:

But the PatternValidators are returning unique ValidatonErrorObjects.

When you check out the source code from the pattern validator at the official angular repo you can see they always return the regex inside the error object.

return (control: AbstractControl): ValidationErrors | null => {
  if (isEmptyInputValue(control.value)) {
    return null;  // don't validate empty values to allow optional controls
  }
  const value: string = control.value;
  return regex.test(value) ? null :
                             {'pattern': {'requiredPattern': regexStr, 'actualValue': value}};
};

With this in mind you can easily create two getter methods inside your component.ts file. And they differ between the two regular expressions. In this example they simply check if an unique substring matches the regular expression. For sure they are other ways to handle this.

  get numberError() {
    if (this.longitude && this.longitude.hasError('pattern')) {
      return this.longitude.getError('pattern').requiredPattern.indexOf('(\d+)') !== -1
    }
    return false;
  }

  get preciseError() {
    if (this.longitude && this.longitude.hasError('pattern')) {
      return this.longitude.getError('pattern').requiredPattern.indexOf('(\-{0,1})') !== -1
    }
    return false;
  }
SplitterAlex
  • 2,755
  • 2
  • 20
  • 23
  • 1
    Looks like it's not working. Seem that when you have more than one pattern declared the last one is going to override the other ones. I tried to console log the `getError('pattern')` and I found out it's still the same, that's why I think it might be it. – Dawid Zbiński Feb 17 '18 at 18:46
  • 1
    But thank you very much for taking time on this one. I really appreciate it. – Dawid Zbiński Feb 17 '18 at 18:48
  • As far as I know there is a possibility to make your own validator and thus you could differentiate them. – Sergey Nov 29 '18 at 12:25
  • Note that instead of empty string in the error object should be null `this.regexValidator(new RegExp('^.{5,}$'), {'precision': ''})` `this.regexValidator(new RegExp('^.{5,}$'), {'precision': null})` – Aleksej Shovgenja Aug 05 '19 at 11:07
1

You can create helper function that can change name of validator error:

function renameError(validator: ValidatorFn, name: string): ValidatorFn {
    return (control: FormControl): ValidationErrors | null => {
        const result = validator(control);
        if (result) {
            const keys = Object.keys(result);
            if (keys.length === 1) {
                result[name] = result[keys[0]];
                delete result[keys[0]];
            } else if (keys.length > 1) {
                // Just in case validator for some reason returns multiple errors
                result[name] = {valid: false};
            }
         }
         return result;
    };
}

and use it like this:

this.longitude = new FormControl(this.attraction.longitude, [
    renameError(
        Validators.pattern(new RegExp('(\d{1,3})([.|,]{,1})(\d+))','gi')),
        'notANumber'
    ),
    renameError(
        Validators.pattern(new RegExp('(\-{0,1})(\d{1,3})([.|,]{1})(\d{5,13})','i')),
        'notPrecise'
    )
]);
<mat-form-field class="form-field">
    <input matInput placeholder="Logitude" [formControl]="longitude">
    <mat-error *ngIf="longitude.hasError('notANumber')">
        Longitude must be <strong>a number</strong>
    </mat-error>
    <mat-error *ngIf="longitude.hasError('notPrecise')"> 
        Longitude must have <strong>a minimum of 5 decimal numbers</strong>
    </mat-error>
</mat-form-field>
0

SplitterAlex's solution didn't work for me until I changed the value of the error object from '' to true

this.longitude = new FormControl('', [
    this.regexValidator(new RegExp('^[0-9]+$'), {'number': true}),
    this.regexValidator(new RegExp('^.{5,}$'), {'precision': true})
  ]);
cigien
  • 57,834
  • 11
  • 73
  • 112