web-dev-qa-db-fra.com

Angular 4: le contrôle de formulaire réactif est bloqué en attente avec un validateur asynchrone personnalisé

Je suis en train de construire une application Angular 4 nécessitant la validation du courrier électronique BriteVerify sur des champs de formulaire à plusieurs composants. J'essaie d'implémenter cette validation en tant que validateur asynchrone personnalisé que je peux utiliser avec des formulaires réactifs. Actuellement, je peux obtenir la réponse de l'API, mais l'état du contrôle est bloqué dans l'état en attente. Je ne reçois aucune erreur, donc je suis un peu confus. S'il vous plaît dites-moi ce que je fais mal. Voici mon code. 

Composant

import { Component, 
         OnInit } from '@angular/core';
import { FormBuilder, 
         FormGroup, 
         FormControl, 
         Validators } from '@angular/forms';
import { Router } from '@angular/router';

import { EmailValidationService } from '../services/email-validation.service';

import { CustomValidators } from '../utilities/custom-validators/custom-validators';

@Component({
    templateUrl: './email-form.component.html',
    styleUrls: ['./email-form.component.sass']
})

export class EmailFormComponent implements OnInit {

    public emailForm: FormGroup;
    public formSubmitted: Boolean;
    public emailSent: Boolean;
    
    constructor(
        private router: Router,
        private builder: FormBuilder,
        private service: EmailValidationService
    ) { }

    ngOnInit() {

        this.formSubmitted = false;
        this.emailForm = this.builder.group({
            email: [ '', [ Validators.required ], [ CustomValidators.briteVerifyValidator(this.service) ] ]
        });
    }

    get email() {
        return this.emailForm.get('email');
    }

    // rest of logic
}

Classe de validation

import { AbstractControl } from '@angular/forms';

import { EmailValidationService } from '../../services/email-validation.service';

import { Observable } from 'rxjs/Observable';

import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';

export class CustomValidators {

    static briteVerifyValidator(service: EmailValidationService) {
        return (control: AbstractControl) => {
            if (!control.valueChanges) {
                return Observable.of(null);
            } else {
                return control.valueChanges
                    .debounceTime(1000)
                    .distinctUntilChanged()
                    .switchMap(value => service.validateEmail(value))
                    .map(data => {
                        return data.status === 'invalid' ? { invalid: true } : null;
                    });
            }
        }
    }
}

Un service

import { Injectable } from '@angular/core';
import { HttpClient,
         HttpParams } from '@angular/common/http';

interface EmailValidationResponse {
    address: string,
    account: string,
    domain: string,
    status: string,
    connected: string,
    disposable: boolean,
    role_address: boolean,
    error_code?: string,
    error?: string,
    duration: number
}

@Injectable()
export class EmailValidationService {

    public emailValidationUrl = 'https://briteverifyendpoint.com';

    constructor(
        private http: HttpClient
    ) { }

    validateEmail(value) {
        let params = new HttpParams();
        params = params.append('address', value);
        return this.http.get<EmailValidationResponse>(this.emailValidationUrl, {
            params: params
        });
    }
}

Modèle (forme juste)

<form class="email-form" [formGroup]="emailForm" (ngSubmit)="sendEmail()">
    <div class="row">
        <div class="col-md-12 col-sm-12 col-xs-12">
            <fieldset class="form-group required" [ngClass]="{ 'has-error': email.invalid && formSubmitted }">
                <div>{{ email.status }}</div>
                <label class="control-label" for="email">Email</label>
                <input class="form-control input-lg" name="email" id="email" formControlName="email">
                <ng-container *ngIf="email.invalid && formSubmitted">
                    <i class="fa fa-exclamation-triangle" aria-hidden="true"></i>&nbsp;Please enter valid email address.
                </ng-container>
            </fieldset>
            <button type="submit" class="btn btn-primary btn-lg btn-block">Send</button>
        </div>
    </div>
</form>

5
Andre Kuzmicheff

Il y a ungotcha!

C'est-à-dire que votre observable ne se termine jamais ...

Cela se produit car l'observable ne se termine jamais, donc Angular ne sait pas quand modifier l'état du formulaire. Alors rappelez-vous votre devoir observable à compléter.

Vous pouvez y parvenir de différentes manières, par exemple, vous pouvez appeler la méthode first() ou, si vous créez votre propre observable, vous pouvez appeler la méthode complète sur l'observateur.

Donc, vous pouvez utiliser first()

.map(data => {
   return data.status === 'invalid' ? { invalid: true } : null;
})
.first();

Un validateur légèrement modifié, c’est-à-dire qui retourne toujours une erreur:STACKBLITZ

5
AJT_82

Je le fais un peu différemment et je suis confronté au même problème.

Voici mon code et la solution au cas où quelqu'un en aurait besoin:

  forbiddenNames(control: FormControl): Promise<any> | Observable<any> {
    const promise = new Promise<any>((resolve, reject) => {
      setTimeout(() => {
        if (control.value.toUpperCase() === 'TEST') {
          resolve({'nameIsForbidden': true});
        } else {

          return null;//HERE YOU SHOULD RETURN resolve(null) instead of just null
        }
      }, 1);
    });
    return promise;
  }
0
alexlz