web-dev-qa-db-ja.com

angular 2?の非同期バリデーターにデバウンス時間を追加するには?

これはデバウンス時間のない私の非同期検証ツールです。どうすれば追加できますか?

static emailExist(_signupService:SignupService) {
  return (control:Control) => {
    return new Promise((resolve, reject) => {
      _signupService.checkEmail(control.value)
        .subscribe(
          data => {
            if (data.response.available == true) {
              resolve(null);
            } else {
              resolve({emailExist: true});
            }
          },
          err => {
            resolve({emailExist: true});
          })
      })
    }
}
45
Chanlito

実際にこれを達成するのはかなり簡単です(あなたの場合ではありませんが、一般的な例です)

private emailTimeout;

emailAvailability(control: Control) {
    clearTimeout(this.emailTimeout);
    return new Promise((resolve, reject) => {
        this.emailTimeout = setTimeout(() => {
            this._service.checkEmail({email: control.value})
                .subscribe(
                    response    => resolve(null),
                    error       => resolve({availability: true}));
        }, 600);
    });
}
29
izupet

Angular 4 +、Observable.timer(debounceTime)を使用:

@izupetの答えは正しいですが、Observableを使用するとさらに簡単になることに注意してください:

emailAvailability(control: Control) {
    return Observable.timer(500).switchMap(()=>{
      return this._service.checkEmail({email: control.value})
        .mapTo(null)
        .catch(err=>Observable.of({availability: true}));
    });
}

angular 4がリリースされたため、チェックのために新しい値が送信された場合、以前のObservableはサブスクライブ解除されるため、実際にsetTimeout/clearTimeoutロジック。

72
n00dl3

inputイベントを使用して更新をトリガーすると、バリデーターが直接トリガーされるため、すぐに使用することはできません。ソースコードの次の行を参照してください。

このレベルでデバウンス時間を活用する場合、対応するDOM要素のinputイベントに直接リンクされたオブザーバブルを取得する必要があります。 Githubのこの問題は、コンテキストを提供する可能性があります。

あなたの場合、回避策はobservableのfromEventメソッドを活用するカスタム値アクセサーを実装することです。

サンプルを次に示します。

const DEBOUNCE_INPUT_VALUE_ACCESSOR = new Provider(
  NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => DebounceInputControlValueAccessor), multi: true});

@Directive({
  selector: '[debounceTime]',
  //Host: {'(change)': 'doOnChange($event.target)', '(blur)': 'onTouched()'},
  providers: [DEBOUNCE_INPUT_VALUE_ACCESSOR]
})
export class DebounceInputControlValueAccessor implements ControlValueAccessor {
  onChange = (_) => {};
  onTouched = () => {};
  @Input()
  debounceTime:number;

  constructor(private _elementRef: ElementRef, private _renderer:Renderer) {

  }

  ngAfterViewInit() {
    Observable.fromEvent(this._elementRef.nativeElement, 'keyup')
      .debounceTime(this.debounceTime)
      .subscribe((event) => {
        this.onChange(event.target.value);
      });
  }

  writeValue(value: any): void {
    var normalizedValue = isBlank(value) ? '' : value;
    this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue);
  }

  registerOnChange(fn: () => any): void { this.onChange = fn; }
  registerOnTouched(fn: () => any): void { this.onTouched = fn; }
}

そして、このように使用します:

function validator(ctrl) {
  console.log('validator called');
  console.log(ctrl);
}

@Component({
  selector: 'app'
  template: `
    <form>
      <div>
        <input [debounceTime]="2000" [ngFormControl]="ctrl"/>
      </div>
      value : {{ctrl.value}}
    </form>
  `,
  directives: [ DebounceInputControlValueAccessor ]
})
export class App {
  constructor(private fb:FormBuilder) {
    this.ctrl = new Control('', validator);
  }
}

このplunkrを参照してください: https://plnkr.co/edit/u23ZgaXjAvzFpeScZbpJ?p=preview

11

rxJを使用した代替ソリューションは次のとおりです。

/**
 * From a given remove validation fn, it returns the AsyncValidatorFn
 * @param remoteValidation: The remote validation fn that returns an observable of <ValidationErrors | null>
 * @param debounceMs: The debounce time
 */
debouncedAsyncValidator<TValue>(
  remoteValidation: (v: TValue) => Observable<ValidationErrors | null>,
  remoteError: ValidationErrors = { remote: "Unhandled error occurred." },
  debounceMs = 300
): AsyncValidatorFn {
  const values = new BehaviorSubject<TValue>(null);
  const validity$ = values.pipe(
    debounceTime(debounceMs),
    switchMap(remoteValidation),
    catchError(() => of(remoteError)),
    take(1)
  );

  return (control: AbstractControl) => {
    if (!control.value) return of(null);
    values.next(control.value);
    return validity$;
  };
}

使用法:

const validator = debouncedAsyncValidator<string>(v => {
  return this.myService.validateMyString(v).pipe(
    map(r => {
      return r.isValid ? { foo: "String not valid" } : null;
    })
  );
});
const control = new FormControl('', null, validator);
6

シンプルにしてください:タイムアウト、遅延、カスタムObservableはありません

...
// assign async validator to a field
this.cardAccountNumber.setAsyncValidators(this.uniqueCardAccountValidatorFn());
...
// subscribe to control.valueChanges and define pipe
uniqueCardAccountValidatorFn(): AsyncValidatorFn {
  return control => control.valueChanges
    .pipe(
      debounceTime(400),
      distinctUntilChanged(),
      switchMap(value => this.customerService.isCardAccountUnique(value)),
      map((unique: boolean) => (unique ? null : {'cardAccountNumberUniquenessViolated': true})),
      first()); // important to make observable finite
}
3
Pavel

RxJS 6の例:

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

validateSomething(control: AbstractControl) {
    return timer(SOME_DEBOUNCE_TIME).pipe(
      switchMap(() => this.someService.check(control.value).pipe(
          // Successful response, set validator to null
          mapTo(null),
          // Set error object on error response
          catchError(() => of({ somethingWring: true }))
        )
      )
    );
  }
2
Dima

ここに、rxjs6を使用した私のライブAngularプロジェクトからの例があります

import { ClientApiService } from '../api/api.service';
import { AbstractControl } from '@angular/forms';
import { HttpParams } from '@angular/common/http';
import { map, switchMap } from 'rxjs/operators';
import { of, timer } from 'rxjs/index';

export class ValidateAPI {
  static createValidator(service: ClientApiService, endpoint: string, paramName) {
    return (control: AbstractControl) => {
      if (control.pristine) {
        return of(null);
      }
      const params = new HttpParams({fromString: `${paramName}=${control.value}`});
      return timer(1000).pipe(
        switchMap( () => service.get(endpoint, {params}).pipe(
            map(isExists => isExists ? {valueExists: true} : null)
          )
        )
      );
    };
  }
}

そして、ここで私は私のリアクティブフォームでそれを使用する方法です

this.form = this.formBuilder.group({
page_url: this.formBuilder.control('', [Validators.required], [ValidateAPI.createValidator(this.apiService, 'meta/check/pageurl', 'pageurl')])
});

ここで、debounceTime(...)およびdistinctUntilChanged()を使用するバリデーター関数を返すサービス:

@Injectable({
  providedIn: 'root'
})
export class EmailAddressAvailabilityValidatorService {

  constructor(private signupService: SignupService) {}

  debouncedSubject = new Subject<string>();
  validatorSubject = new Subject();

  createValidator() {

    this.debouncedSubject
      .pipe(debounceTime(500), distinctUntilChanged())
      .subscribe(model => {

        this.signupService.checkEmailAddress(model).then(res => {
          if (res.value) {
            this.validatorSubject.next(null)
          } else {
            this.validatorSubject.next({emailTaken: true})
          }
        });
      });

    return (control: AbstractControl) => {

      this.debouncedSubject.next(control.value);

      let prom = new Promise<any>((resolve, reject) => {
        this.validatorSubject.subscribe(
          (result) => resolve(result)
        );
      });

      return prom
    };
  }
}

使用法:

emailAddress = new FormControl('',
    [Validators.required, Validators.email],
    this.validator.createValidator() // async
  );

バリデーターを追加する場合Validators.requiredおよびValidators.email入力文字列が空ではなく、有効なメールアドレスである場合にのみ、リクエストが行われます。これは、不必要なAPI呼び出しを避けるために行う必要があります。

0
Willi Mentzel