1

I am trying to implement async validator in my Reactive Form in angular 4.3.4 which will check that entered email exists or not in your system.

but this does not work properly, earlier it was invoking on every key up so I made some changes and make it Observable now only Checking after a given debounce time. checking...' text is displaying but the response comes but no error is being displayed on the page.

what can be the issue? I have very base knowledge of Observable and angular 4. please help me what is the issue. I have checked in the console and it is going and print the value in the asyncvalidator function.

here is the relevant code.

signup.component.html

<form [formGroup]="myForm" novalidate #formDir="ngForm" (ngSubmit)="doSignup()">
<input type="email" formControlName="email" pattern="{{email_pattern}}"/>
<div [hidden]="myForm.controls.email.valid || myForm.controls.email.pristine" class="text-danger">
    <div *ngIf="myForm.controls.email.required">Please enter Email</div>
    <div *ngIf="myForm.controls.email.pattern">Invalid Email</div>
    <div *ngIf="myForm.controls.email.status === 'PENDING'">
        <span>Checking...</span>
    </div>
    <div *ngIf="myForm.controls.email.errors && myForm.controls.email.errors.emailTaken">
        Invitation already been sent to this email address.
    </div>
</div>
<button type="submit" [disabled]="!myForm.valid">Invite</button>
</form>

signup.component.ts

import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { ValidateEmailNotTaken } from './async-validator';

export class SignupComponent implements OnInit {
 public myForm: FormGroup; 
 constructor(
    private httpClient: HttpClient,
    private fb: FormBuilder
) {
}
 ngOnInit(): void {
    this.buildForm();
 }

private buildForm() {
        this.inviteForm = this.fb.group({
            firstname: [''],
            lastname: [''],
            email: [
                '',
                [<any>Validators.required, <any>Validators.email],
                ValidateEmailNotTaken.createValidator(this.settingsService)
            ]
        });
}

asyn-validator.ts

import { Observable } from 'rxjs/Observable';
import { AbstractControl } from '@angular/forms';
import { UserService } from './user.service';

export class ValidateEmailNotTaken {
    static createValidator(service: UserService) {
        return (control: AbstractControl): { [key: string]: any } => {
            return Observable.timer(500).switchMapTo(service.checkEmailNotTaken(control.value))
                .map((res: any) => {
                    const exist = res.item.exist ? { emailTaken: true } : { emailTaken: false };
                    console.log('exist: ', exist);
                    return Observable.of(exist);
                })
                .take(1);
        };
    }
}

user.service.ts

checkEmailNotTaken(email) {
        const params = new HttpParams().set('email', email);
        return this.httpClient.get(`API_END_POINT`, {
            headers: new HttpHeaders({
                'Content-type': 'application/json'
            }),
            params: params
        });
}

2 Answers 2

1

You use Observable.timer(500) without a second argument, so after 500 milliseconds, it completes and never runs again. So first thing to do is to pass that argument - Observable.timer(0, 500).

switchMapTo cancels its previous inner Observable (service.checkEmailNotTaken(control.value) in your case) every time source Observable emits new value (so every 500 milliseconds). So if your http request lasts longer, you wont get its response. Thats why usually switchMap and switchMapTo are not suitable for http requests.

Here is an illustration:

const source = Rx.Observable.timer(0, 500);
const fail = source.switchMapTo(Rx.Observable.of('fail').delay(600))
const success = source.switchMapTo(Rx.Observable.of('success').delay(400))


const subscribe = fail.subscribe(val => console.log(val));
const subscribe2 = success.subscribe(val => console.log(val));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>

So you should pick another flattening operator, like flatMap:

const source = Rx.Observable.timer(0, 500);
const success = source.flatMap(()=>Rx.Observable.of('success').delay(600))


const subscribe = success.subscribe(val => console.log(val));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>

Sign up to request clarification or add additional context in comments.

Comments

1

I know its too late for the answer but anyone facing same issue might find it useful: apart from above answer the AsyncValidatorFn should return Promise<ValidationErrors | null> | Observable<ValidationErrors | null>.

Return value of ValidationErrors | null isn't correct.

Check out official docs

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.