Ionic Validation: formBuilder custom validation to Compare E-mails


#1

I’m having a tough time comparing e-mail fields as part of my form validation with formBuilder. I’ve read the following forum posts but I still can’t get it to work the way I want it to, so I’m hoping someone can shed some light on things:

I have the validation being performed correctly, however I can’t work out how to assign the error value to the re_password field. Basically I want the error text to be displayed under the “re_password” ion-input, much like it does for the “email” and “password” ion-inputs.

Here is my setup:

home.html

<p *ngIf="submitAttempt" style="color: #ea6153;">There are errors with your submission!</p>

<form [formGroup]="registerForm">

	<ion-item [class.invalid]="!email.valid && email.touched">
        <ion-label floating>Email</ion-label>
        <ion-input type="text" value="" [formControl]="email" autocapitalize="off"></ion-input>
    </ion-item>

        <ion-item *ngIf="email.hasError('required') && email.touched" class="invalid">
            <p>* Email is required!</p>
        </ion-item>

        <ion-item *ngIf="email.hasError('minlength') && email.touched" class="invalid">
            <p>* Minimum username length is 3!</p>
        </ion-item>

        <ion-item *ngIf="email.hasError('maxlength') && email.touched" class="invalid">
            <p>* Maximum username length is 25!</p>
        </ion-item>

        <ion-item *ngIf="email.hasError('invalid_domain') && email.touched" class="invalid">
            <p>* Invalid e-mail domain!</p>
        </ion-item>

    <ion-item [class.invalid]="!password.valid && password.touched">
        <ion-label floating>Password</ion-label>
        <ion-input type="password" value="" [formControl]="password"></ion-input>
    </ion-item>

        <ion-item *ngIf="password.hasError('required') && password.touched" class="invalid">
            <p>* Password is required!</p>
        </ion-item>

        <ion-item *ngIf="password.hasError('minlength') && password.touched" class="invalid">
            <p>* Minimum password length is 3!</p>
        </ion-item>

        <ion-item *ngIf="password.hasError('maxlength') && password.touched" class="invalid">
            <p>* Maximum password length is 25!</p>
        </ion-item>

    <ion-item [class.invalid]="!re_password.valid && re_password.touched">
        <ion-label floating>Re-enter Password</ion-label>
        <ion-input type="password" value="" [formControl]="re_password"></ion-input>
    </ion-item>

        <ion-item *ngIf="re_password.hasError('pw_mismatch') && re_password.touched" class="invalid">
            <p>* Passwords do not match!</p>
        </ion-item>

</form>

<button primary (click)="save()" [disabled]="(!registerForm.valid)">Submit Form</button>
<br />
<br />
<button primary (click)="logControlErrors()">Log Control Errors</button>

home.ts

import {Component} from '@angular/core';
import {FormBuilder, FormGroup, Validators, AbstractControl} from '@angular/forms';
import {EmailValidator} from  '../../validators/email';
import {PasswordValidator} from  '../../validators/password';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
 
  registerForm: FormGroup;
  email: AbstractControl;
  password: AbstractControl;
  re_password: AbstractControl;

  constructor(private formBuilder: FormBuilder) {

    this.registerForm = formBuilder.group({
            'email': ['', Validators.compose([Validators.required, Validators.minLength(3), Validators.required, Validators.maxLength(25), EmailValidator.checkEmail])],
            'password': ['', [Validators.required, Validators.minLength(5), Validators.maxLength(45)]],
            're_password': ['', [Validators.required]]
            }, { 'validator': PasswordValidator.isMatching }
        );

    this.email = this.registerForm.controls['email'];
    this.password = this.registerForm.controls['password'];
    this.re_password = this.registerForm.controls['re_password'];    
  }

  ionViewDidLoad() {
		
  }

  save(){

  	this.submitAttempt = true;
 
    if(!this.registerForm.valid){
        console.log("Invalid Submission!")
    } 
    else {
        console.log("Success!")
        console.log(this.registerForm.value);
    }

  }

  elementChanged(input){

    let field = input.inputControl.name;
    this[field + "Changed"] = true;
  }

  logControlErrors(){

    console.log(this.email.errors);
    console.log(this.password.errors);
    console.log(this.re_password.errors);

  }

}

email.ts (Custom Validator)

import {FormControl} from '@angular/forms';
 
export class EmailValidator {
 
  static checkEmail(control: FormControl){

    console.log("e-mail check");

    var requiredDomains = ["gmail.com","yahoo.com"];
    var lowercaseValue = control.value.toLowerCase();
    var providedDomain = lowercaseValue.substr(lowercaseValue.indexOf('@')+1);
    var returnVal: any;

    for (var i = 0; i < requiredDomains.length; i++) {
      if(requiredDomains[i] != providedDomain) {
        returnVal =  {"invalid_domain": true};
        i = requiredDomains.length;
      }
    }
    
    return returnVal;
  }
 
}

password.ts (Custom Validator)

import {FormGroup} from '@angular/forms';
 
export class PasswordValidator {
 
  static isMatching(group: FormGroup){

    console.log("password check");
    
    var firstPassword = group.controls['password'].value;
    var secondPassword = group.controls['re_password'].value;
    if((firstPassword && secondPassword) && (firstPassword != secondPassword)){
      console.log("mismatch");
      return { "pw_mismatch": true };      
    } else{
      return null;
    }
    
  }
 
}

I’m guessing the solution relates to how I am calling the e-mail custom validator. The problem I am facing is that if I call it inside the “re_password” validator then I don’t know how to get the value of the “password” form control (in order to perform the comparison). If I call it outside of the “re_password” validator then I can get both the “password” and “re_password” form control values using FormGroup, however I don’t know how to assign the error to the “re_password” form control.

@richardmarais - the most recent posts I’ve read on the subject have been started by you. Did you ever get this validation working with the latest release of Ionic 2? Do you know what I need to do to fix my code?

Thanks in advance for any help.


#2

You have to set the error to the FormControl:
import {FormGroup} from ‘@angular/forms’;

export class PasswordValidator {

static isMatching(group: FormGroup){

console.log("password check");

var firstPassword = group.controls['password'].value;
var secondPassword = group.controls['re_password'].value;
if((firstPassword && secondPassword) && (firstPassword != secondPassword)){
  console.log("mismatch");
  group.controls['re_password'].setErrors({"pw_mismatch": true});
  return { "pw_mismatch": true };      
} else{
  return null;
}

}

}

Then in the HTML, ask for re_password error like you are doing.

Good luck!


#3

This works great thanks @lalogrosz, problem solved :slight_smile:


#4

I am testing check email and doesnt work, and for you ? please


#5

Can you please show how can we do it with dynamic data coming from firebase.
Here is my code I stuck now…

import { FormControl } from '@angular/forms';
import { UserProvider } from '../providers/database/user/user';

export class EmailValidator {
  public users: any;
  public email: any;
  constructor(
    public _DBUSER: UserProvider
  ) {
    this.loadAndParseUsers();
  }
  loadAndParseUsers() {
    this.users = this._DBUSER.renderAllUsers();
    var allEmails = [];
    this.users.forEach(element => {
      console.log(element);
      element.forEach(user => {
        allEmails.push(user.email);
      });
    });
    console.log(allEmails);

  }
  static checkEmail(control: FormControl): any {

    return new Promise(resolve => {

      //Fake a slow response from server

      setTimeout(() => {
        if (control.value == "adminh@gmail.com") {
          console.log(control.value);

          resolve({
            "Email taken": true
          });

        } else {
          resolve(null);
        }
      }, 2000);

    });
  }
}

Static checking is working fine. But I am not able to check with dynamic email coming from data. I am quite newbie to Ionic please help. thanks


#6

Hey @metalhead101,

the key is to work with an async validator and to bind the input… Here is a piece of code that I use - I just changed the names a little bit.

import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormBuilder, FormControl, FormGroup, ValidationErrors, Validators } from '@angular/forms';
import { NavController } from 'ionic-angular';
import { ApiProvider } from './../../providers/api/api';
import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/switchMap";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/first';
import 'rxjs/add/operator/do';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/debounceTime';

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

export class SignupPage implements OnInit {
	debounced: boolean = false;	
	
	signupForm: FormGroup;

	constructor(public navCtrl: NavController, public formBuilder: FormBuilder, public apiProvider: ApiProvider) {}
		
	ngOnInit() {
		this.signupForm = this.formBuilder.group({
			.
			.
			.
			enteredEmail: ['', [Validators.required, Validators.email], this.validateEmailNotTaken.bind(this)],
			.
			.
			.		
		});
	}

	.
	.
	.
	/* THE DEBOUNCED SHOWS ONLY A LOADING SPINNER WHILE VERIFYING EMAIL */
	
	validateEmailNotTaken(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
		return control.valueChanges === undefined ? Observable.of({}) : control.valueChanges
			.debounceTime(1500)
			.do(() => {
				this.debounced = true;
			})
			.switchMap(result => 
				this.apiProvider.apiVerifyAccountEmail(control.value)
				/* THIS IS WHERE YOU FUNCTION TAKES PLACE - SHOULD BE AN OBSERVABLE */
			)
			.map(result => {
				return result.taken ? {"inUse": true} : null;
			})
			.do(() => {
				this.debounced = false;
			})
			.first();
	}
	.
	.
	.
}

And here is the sample HTML:

<ion-col col-12>
	<h3 ion-text color="danger" margin-top *ngIf="signupForm.get('enteredEmail').errors && signupForm.get('enteredEmail').errors.inUse">
		Entered email is already in use.
	</h3>
	<ion-item>
		<ion-input formControlName="enteredEmail" placeholder="Email" type="email"></ion-input>
		<ion-spinner item-right color="divider_color" *ngIf="(debounced && signupForm.get('enteredEmail').status == 'PENDING')"></ion-spinner>
		<ion-icon item-right name="checkmark" color="divider_color" *ngIf="signupForm.get('enteredEmail').status == 'VALID'"></ion-icon>
	</ion-item>
</ion-col>

Let me know, if this fixed your issue.

Oliver


#7

Thanks for your answer, I will give it a try and let you now.
Currently I am trying to solve through this method @nicowernli Please check his answer

Please let me know if I am in right track?

Thanks


#8

You mean this?

This should work, don’t waste your time with other maybe possible solutions, Honestly!

Regards,
Oliver


#9

@odorakel Thanks. I will give it a try…!!