0

I'm trying to make a custom asynchronous validator which checks if the email provided already exists on DB or not. When there's error it works fine, but when the error is solved(valid data is entered) it shows Cannot read property 'emailIsTaken' of null

I'm using Template Driven method. Here's how I use.

<input type="email" name="email" [(ngModel)]='email' #mail='ngModel' required [pattern]='emailPattern' validateEmail>

Using error code

<div *ngIf='mail.errors["emailIsTaken"]'>Email already Registered!</div>

Here's my validator file

import { Validator, AbstractControl, NG_ASYNC_VALIDATORS } from '@angular/forms';
import { Directive, Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
@Directive({
    selector: '[validateEmail]',
    providers: [{
        provide: NG_ASYNC_VALIDATORS,
        useExisting: EmailValidator,
        multi: true
    }]
})

@Injectable()
export class EmailValidator implements Validator {
    constructor(private http: HttpClient){}
    validate(control: AbstractControl): Promise<any> | Observable<any> {
        return new Promise(resolve => {
            this.http.get('http://surjit.com/email.php?q=' + control.value).subscribe((success)=>{
            if(success === true){
                    resolve(null);
                }
                else{
                    resolve({'emailIsTaken': true});
                }
            });
        });
    }
}

The same error also occurs when I use inbuilt email validator

ScreenShot

Prasad Sawant
  • 123
  • 1
  • 7
  • You really should not post screenshots of error text, it is easier and better to copy/paste the text into your question. That said show us the code for `SignupComponent.html` where the error is originating from. – Igor Mar 23 '18 at 11:36

2 Answers2

1

This is your problem:

if(success === true){
     resolve(null);
}

You return null in case of successful validation instead of an object with the field 'emailIsTaken'.

What about

if(success === true){
    resolve({'emailIsTaken': false});
}

if this fits to your code?

  • I believe this the syntax, we should return null in case of successful test – Prasad Sawant Mar 23 '18 at 10:38
  • @user9416413 I think you're not right. You want to validate if the e-mail is free, the good practice is to return the same type of data. You should return `emailIsTake` in both situations, one with false and the other with true, based on if it's taken or not. Exactly how @DiabolicWords suggested. This way you won't encounter the error you wrote about. – Dawid Zbiński Mar 23 '18 at 10:40
  • I tried that according to your suggestion, but this doesn't solve my problem. It's still printing the same error – Prasad Sawant Mar 23 '18 at 10:45
  • https://stackoverflow.com/questions/35521583/how-to-implement-custom-async-validator-in-angular2-4-5 Here he used null for successful test – Prasad Sawant Mar 23 '18 at 11:04
0

If you want tto execute this async then you need to implement the AsyncValidator.

Also you need to return a result from the observable and not subscribe to it. The easiest thing to do here is to use Observable instead of promise but you could chain .toPromise() after .map() as well. You do not want to subscribe here you want to return the observable itself (or the promise) but you do want to have the promise return the expected result which is why you should use map.

On a side note I recommend you switch to using the pipable operators if you have not already done so. See also In Angular rxjs when should I use pipe vs map.

import { AsyncValidator } from '@angular/forms';
import { map } from 'rxjs/operators/map';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class EmailValidator implements AsyncValidator {
    constructor(private http: HttpClient){}

    validate(control: AbstractControl): Promise<any> | Observable<any> {
        return this.http.get('http://surjit.com/email.php?q=' + control.value)
                .pipe(map((success)=> success ? null : {'emailIsTaken': true}));
        }
}

The above is just the changed EmailValidator class.


See the StackBlitz I created based on the above.


Edit

Based on your error screenshot the most likely cause is that you have checks in your HTML on which error should be shown based on the resulting validation and this is not taking into account the fact that the validation result is null if there are no errors. The easiest fix is to use the safe navigation operator ( ?. ) and null property paths ?. Example:

<element *ngIf="errResult?.emailIsTaken">Email is in use</element>
Igor
  • 60,821
  • 10
  • 100
  • 175
  • @user9416413 - you need to implement `AsyncValidator`, and not `Validator`. That along with what I previously stated like using `map`. – Igor Mar 23 '18 at 10:54
  • @user9416413 - My solution above works. If you are having an error it is in code you have not shown. See this [stackblitz](https://stackblitz.com/edit/angular-dqwebc) – Igor Mar 23 '18 at 11:12
  • @user9416413 - you are making us guess because you did not post the HTML but the most likely cause is trying to access the error result but not taking into account it is null. See my latest edit above. – Igor Mar 23 '18 at 11:41
  • @user9416413 - if this solves your issue please consider marking an answer using the checkmark on the left side of the answer. – Igor Mar 23 '18 at 11:43
  • sorry but still my problem is not solved. I'm using error code like `
    Email already Registered!
    `
    – Prasad Sawant Mar 26 '18 at 05:30
  • @user9416413 - add the `?`, so that should be `*ngIf='mail?.errors["emailIsTaken"]'` – Igor Mar 26 '18 at 10:51
  • @user9416413 like I explained above, the reason is that `errors` can be `null` – Igor Mar 26 '18 at 10:51