Using form Validators return values for error messages

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.

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

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?

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.

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.

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

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