web-dev-qa-db-fra.com

Comment contester un validateur asynchrone dans Angular 4 avec RxJS observable?

J'utilise un validateur asynchrone personnalisé avec Angular 4 formulaires réactifs pour vérifier si l'adresse de messagerie est déjà utilisée en appelant un utilisateur.

Cependant, Angular appelle le validateur, qui envoie une requête au serveur pour chaque caractère saisi. Cela crée un stress inutile sur le serveur.

Est-il possible d'éliminer élégamment les appels asynchrones à l'aide de RxJS observable?

import {Observable} from 'rxjs/Observable';

import {AbstractControl, ValidationErrors} from '@angular/forms';
import {Injectable} from '@angular/core';

import {UsersRepository} from '../repositories/users.repository';


@Injectable()
export class DuplicateEmailValidator {

  constructor (private usersRepository: UsersRepository) {
  }

  validate (control: AbstractControl): Observable<ValidationErrors> {
    const email = control.value;
    return this.usersRepository
      .emailExists(email)
      .map(result => (result ? { duplicateEmail: true } : null))
    ;
  }

}
10
Slava Fomin II

Alors que la réponse de @ Slava est juste. C'est plus facile avec Observable:

return (control: AbstractControl): Observable<ValidationErrors> => {
      return Observable.timer(this.debounceTime).switchMap(()=>{
        return this.usersRepository
            .emailExists(control.value)
            .map(result => (result ? { duplicateEmail: true } : null));
      });
}

Comme la variable Observable renvoyée sera désabonnée si une nouvelle valeur arrive, il n'est pas nécessaire de gérer le délai d'attente manuellement.

16
n00dl3

UPDATE RxJS 6.0.0:

import {of, timer} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';


return (control: AbstractControl): Observable<ValidationErrors> => {
  return timer(500).pipe(
    switchMap(() => {
      if (!control.value) {
        return of(null)
      }
                      
      return this.usersRepository.emailExists(control.value).pipe(
        map(result => (result ? { duplicateEmail: true } : null))
      );
    })
  )
}


* RxJS 5.5.0

Pour tous ceux qui utilisent RxJS ^ 5.5.0 pour de meilleurs opérateurs d'arborescence et de canalisation

import {of} from 'rxjs/observable/of';
import {map, switchMap} from 'rxjs/operators';
import {TimerObservable} from 'rxjs/observable/TimerObservable';


return (control: AbstractControl): Observable<ValidationErrors> => {
  return TimerObservable(500).pipe(
    switchMap(() => {
      if (!control.value) {
        return of(null)
      }
                      
      return this.usersRepository.emailExists(control.value).pipe(
        map(result => (result ? { duplicateEmail: true } : null))
      );
    })
  )
}

6
SplitterAlex

Après avoir étudié certaines solutions proposées avec Observables, je les ai trouvées trop complexes et j'ai décidé d'utiliser une solution avec des promesses et des délais d'attente. Bien que franche, cette solution est beaucoup plus simple à comprendre:

import 'rxjs/add/operator/toPromise';

import {AbstractControl, ValidationErrors} from '@angular/forms';
import {Injectable} from '@angular/core';

import {UsersRepository} from '../repositories/users.repository';


@Injectable()
export class DuplicateEmailValidatorFactory {

  debounceTime = 500;


  constructor (private usersRepository: UsersRepository) {
  }

  create () {

    let timer;

    return (control: AbstractControl): Promise<ValidationErrors> => {

      const email = control.value;

      if (timer) {
        clearTimeout(timer);
      }

      return new Promise(resolve => {
        timer = setTimeout(() => {
          return this.usersRepository
            .emailExists(email)
            .map(result => (result ? { duplicateEmail: true } : null))
            .toPromise()
            .then(resolve)
          ;
        }, this.debounceTime);
      });

    }

  }

}

Ici, je convertis l'observable existant en promesse en utilisant l'opérateur toPromise() de RxJS. La fonction usine est utilisée car nous avons besoin d’une minuterie distincte pour chaque commande.


S'il vous plaît considérez cela comme une solution de contournement. D'autres solutions, qui utilisent réellement RxJS, sont les bienvenues!

4
Slava Fomin II

Je pense que votre méthode ne fait que retarder, non pas rebondir, puis trouver un exemple de moyen d'archiver ce résultat.

import { debounce } from 'lodash';

...

constructor() {
   this.debounceValidate = debounce(this.debounceValidate.bind(this), 1000);
}

debounceValidate(control, resolve) {
   ...//your validator
}

validate (control: AbstractControl): Promise {
  return new Promise(resolve => {
    this.debounceValidate(control, resolve);
  })
}
0
wfsovereign