Using form Validators return values for error messages


#1

In a validator connected with a form, I have something like this:

import { FormControl } from '@angular/forms';
export class SpotValidator {
    static isValid(control: FormControl): any {
       
        if (control.value == ""){
            return null;
        }

        if(isNaN(control.value)){
            return {
                "not a number": true
            };
        }
 
        if(control.value % 1 !== 0){
            return {
                "not a whole number": true
            };
        }
 
        if(control.value < 31){
            return {
                "too low": true
            };
        }

        if (control.parent.value.building == "metropolitan"){
 
            if (control.value > 260 && control.value < 10000){ //this allows for test parking spots during validation.
                return {
                    "too high": true
                };
            }
        } 
 
        return null;
    }
 
}

How can I show the errors “too high”, “too low”, “not a number”, and “not a whole number” in the html of the form. In other words, I see that something is being returned to the html or the ts code (I’m not sure which) but I’m not sure how to access that return so that I can display the proper message.

I came across the angular custom validation write-up but, I’m sad to admit that I couldn’t quite make the leap between their example and what I’m trying to do.


#2

If you look a bit further up in that page you linked to, it describes how to show errors in the template.


#3

Sorry rapropos I’m having a hard time making the leap between their example and my code. I tried what they used:

<div *ngIf="formErrors.spot" class="alert alert-danger">
  {{ formErrors.spot }}
</div>

but I kept getting errors with the formErrors part. (specifically: Runtime Error
Error in ./SignupPage class SignupPage - caused by: Cannot read property ‘spot’ of undefined)
(spot, in this case is the name of the control I’m using)

I followed the example… but I’m not certain how to troubleshoot that error.

I’d much prefer to use the method above, but I got the method from this link to work but putting the below in my html.

<div *ngIf="signUpForm.controls.spot.hasError('too low')">
   That value is too low
</div>

<div *ngIf="signUpForm.controls.spot.hasError('too high')">
   That value is too high
</div>

where signUpForm is defined in the html by:

<form [formGroup]="signUpForm">

and spot is the name of the control defined in the associated ts file.

spot: ['', [SpotValidator.isValid], spotExistsValidator.checkSpotExists.bind(spotExistsValidator)]

Any idea why the initial method fails? I’d prefer to use it since it looks like I can put the text of the error via {{ formErrors.spot }} in the html directly, instead of having multiple *ngIf’s for all of the different cases.

or, perhaps… is there a way to get more info about “formErrors?” to see if I can figure out why “spot” is undefined?


#4

I realized that it was tough to troubleshoot this without code. I tried to make a plunker (my first one) but I couldn’t get it to work. Maybe a versioning problem?
FWIW, the plunker is here and the code has been tested on my system to make sure it works.

Here’s the code itself:

HTML

<ion-header>
    <ion-navbar color="dark">
        <ion-title>NEW USER SIGNUP</ion-title>
    </ion-navbar>
</ion-header>

<ion-content class="login-content" padding>
    <div>
        <form [formGroup]="signUpForm">
            <ion-list inset>
                <ion-item [class.invalid]="!signUpForm.controls.spot.valid && (signUpForm.controls.spot.dirty)">
                    <ion-input placeholder="Parking Spot Number" formControlName="spot" [(ngModel)]="registerCredentials.spot"></ion-input>
                </ion-item>
                <div *ngIf="!signUpForm.controls.spot.valid  && !signUpForm.controls.spot.pending && (signUpForm.controls.spot.dirty)">
                    <p *ngIf="signUpForm.controls.spot.hasError('too low')">
                        Seriously... waaaay too low.
                    </p>

                    <p *ngIf="signUpForm.controls.spot.hasError('too high')">
                        This is waaaay too high
                    </p>

                    <p *ngIf="signUpForm.controls.spot.hasError('not a number')">
                        This is tooootally not a number
                    </p>

                    <p *ngIf="signUpForm.controls.spot.hasError('not a whole number')">
                        This is tooootally not a whole number
                    </p>
                </div>
            </ion-list>
        </form>
    </div>
</ion-content>

TS file

import { Component, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'
import { SpotValidator } from '../../validators/spotValidator2';
import * as moment from 'moment'


@Component({
  selector: 'page-signup',
  templateUrl: 'signup.html'
})

export class SignupPage2 {
  @ViewChild('signupSlider') signupSlider: any;
  signUpForm: FormGroup;

  private debugMode: boolean = true;
  registerCredentials = {
    spot: '',
  };

  constructor(
    public formBuilder: FormBuilder,
  ) {
    this.signUpForm = formBuilder.group({
      spot: ['', [SpotValidator.isValid]]
    });
  }
}

and finally Validation TS file

import { FormControl } from '@angular/forms';
 
export class SpotValidator {
 
    static isValid(control: FormControl): any {

        if(control.value == ""){
            return null
        }
        
        if(isNaN(control.value)){
            return {
                "not a number": true
            };
        }
 
        if(control.value % 1 !== 0){
            return {
                "not a whole number": true
            };
        }

        if (control.value > 260 ){ 
            return {
                "too high": true
            };
        }
    
        if (control.value < 31 ){ //this allows for test parking spots during validation.
            return {
                "too low": true
            };
        }

        return null;
    }
 
}

What I would like to do is replace all of these fields in the html that look like this:

 <p *ngIf="signUpForm.controls.spot.hasError('not a whole number')">
         This is tooootally not a whole number
 </p>

and replace them with something that just displays the error returned by the validator as so:

<p *ngIf="formErrors.spot" >
  {{ formErrors.spot }}
</p>

Unfortunately, when I use that code, it seems like formErrors is undefined.


#5

So, I figured it out. The Angular documentation doesn’t quite fit the model that I’d created (with help from a Josh Morony video) and I wanted to add the solution here to help the next guy. Specifically, the angular documentation uses a “change on update” event to trigger the reading of the values, but you can just use the returned value directly without that intermediate step. (Which was what where I was getting confused)

This is what I ended up with for the above example:

HTML

<ion-content class="login-content" padding>
    <div>
        <form [formGroup]="signUpForm">
            <ion-list inset>
                <ion-item [class.invalid]="!signUpForm.controls.spot.valid && (signUpForm.controls.spot.dirty)">
                    <ion-input placeholder="Parking Spot Number" formControlName="spot" [(ngModel)]="registerCredentials.spot"></ion-input>
                </ion-item>
                <div *ngIf="!signUpForm.controls.spot.valid  && !signUpForm.controls.spot.pending && (signUpForm.controls.spot.dirty)">
                    <p *ngIf="signUpForm.controls.spot.hasError('my_error_text')">
                            {{signUpForm.controls.spot.errors.my_error_text}}
                    </p> 
                </div>
            </ion-list>
        </form>
    </div>
</ion-content>

The associated TS file for the page is unchanged from the one above.

Finally, This is my validation code:

    import { FormControl } from '@angular/forms';
    
    export class SpotValidator {
    
        static isValid(control: FormControl): any {

            if(control.value == ""){
                return null
            }
            
            if(isNaN(control.value)){
                return {
                    "my_error_text": "not a number"   //Added 
                };
            }
    
            if(control.value % 1 !== 0){
                return {
                    "my_error_text": "not a whole number" //Added 
                };
            }

            if (control.value > 260 ){ 
                return {
                    "my_error_text": "too high" //Added 
                };
            }
        
            if (control.value < 31 ){ //this allows for test parking spots during validation.
                return {
                    "my_error_text": "too low" //Added 
                };
            }

            return null;
        }
    
    }

I’m still not sure that this is the “best” way to handle this, but it’s less convoluted, and it allows me to handle the error messages in one place.


#6

This is dangerous and likely to break in production builds. Never access controls directly. Instead always use get().


#7

En la búsqueda de una forma de realizar la validaciones del formulario encontré este tutorial que puede que te sirva @wterrill y usa la propiedad get como recomienda @rapropos