programing

각도 2에서 비동기 검증기에 데바운스 시간을 추가하는 방법은 무엇입니까?

iphone6s 2023. 8. 15. 10:55
반응형

각도 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});
          })
      })
    }
}

4 4+ 사용,Observable.timer(debounceTime):

@izupet의 대답은 옳지만 관찰 가능을 사용할 때 훨씬 더 간단하다는 것을 알아둘 필요가 있습니다.

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스스로 논리를 펼칩니다.

용사를 합니다.timerAngular의 그 Angular 비의기자증검 RxJS 다니습었들을 다시 .debounceTime.

단순성 유지: 시간 초과, 지연, 사용자 지정 관찰 가능 없음

// assign the async validator to a field
this.cardAccountNumber.setAsyncValidators(this.uniqueCardAccountValidatorFn());
// or like this
new FormControl('', [], [ this.uniqueCardAccountValidator() ]);
// 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
}

Angular 9+ 비동기 Validator(데바운스 포함

@n00dl3가 정답입니다.저는 Angular 코드를 사용하여 구독을 취소하고 시간이 지정된 일시 중지를 던져 새로운 비동기 검증기를 만드는 것을 좋아합니다.Angular와 RxJS API는 그 답변이 작성된 이후로 발전하여 업데이트된 코드를 게시합니다.

의 변경을 (오류를 말고 . 스럽게 할 것입니다 (1) 드코발면숨는로안서겨되며내줄수있다니습혼을란사게않그에렇자용면지으는으용하치일에소주메일이되견.네트워크가 다운된 경우 이메일이 일치한다고 말하는 이유는 무엇입니까?! (하기 위해 ( UI 사용. (2) Use UI 를한크구다오니별합를류트돌워과상방가레능위태이스하사유기합지값전캡컨검지시사니다야자에간는을성연해의트롤 3처용효해프레션네코이드메충이일는테젠▁()3▁ui▁(전▁error▁(▁willation▁collision'▁cond▁code컨값지시합사▁network캡delaytimer후자는 매 0.5초마다 작동하고 느린 네트워크를 가지고 있고 이메일 확인이 오래 걸리는 경우(1초) 타이머가 switchMap을 계속 재시동하고 통화가 완료되지 않습니다.

Angular 9+ 호환 fragment:

emailAvailableValidator(control: AbstractControl) {
  return of(control.value).pipe(
    delay(500),
    switchMap((email) => this._service.checkEmail(email).pipe(
      map(isAvail => isAvail ? null : { unavailable: true }),
      catchError(err => { error: err }))));
}

PS: Angular 소스(강력히 추천합니다)를 더 깊이 파고들고 싶은 사람은 여기에서 비동기 유효성 검사를 실행하는 Angular 코드와 여기서 구독을 취소하는 코드를 찾을 수 있습니다.모든 동일한 파일 및 모든 아래updateValueAndValidity.

실제로 이를 달성하는 것은 매우 간단합니다(당신의 경우는 아니지만 일반적인 예입니다).

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);
    });
}

유효성 검사기는 다음과 같은 경우에 직접 트리거되기 때문에 즉시 사용할 수 없습니다.input이벤트는 업데이트를 트리거하는 데 사용됩니다.소스 코드에서 다음 행을 참조하십시오.

이 수준에서 데바운스 시간을 활용하려면 관찰 가능한 항목을 직접 연결해야 합니다.input해당 DOM 요소의 이벤트입니다.Github의 이 문제는 다음과 같은 맥락을 제공할 수 있습니다.

사용자의 경우 해결 방법은 사용자 정의 가치 액세스를 구현하거나 사용자 정의 가치 액세스를 활용하는 것입니다.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);
  }
}

다음 플런크r을 참조하십시오. https://plnkr.co/edit/u23ZgaXjAvzFpeScZbpJ?p=preview

RxJs를 사용하는 대체 솔루션은 다음과 같습니다.

/**
 * 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);

여기서는 다음을 사용하는 검증자 함수를 반환하는 서비스입니다.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 호출을 방지하기 위해 이 작업을 수행해야 합니다.

다음은 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')])
});

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 }))
        )
      )
    );
  }

일을 조금 단순화할 수 있습니다.

export class SomeAsyncValidator {
   static createValidator = (someService: SomeService) => (control: AbstractControl) =>
       timer(500)
           .pipe(
               map(() => control.value),
               switchMap((name) => someService.exists({ name })),
               map(() => ({ nameTaken: true })),
               catchError(() => of(null)));
}

우리가 서버에 요청하는 횟수를 줄이기 위해 노력하고 있기 때문에, 저는 또한 확인을 위해 유효한 이메일만 서버로 전송되도록 확인하는 확인을 추가하는 것을 추천합니다.

나는 간단한 것을 사용해 왔습니다.RegExJavaScript에서: HTML 양식 - 이메일 유효성 검사

우리는 또한 사용하고 있습니다.timer(1000)1 이후에 .

이 두 항목을 설정하면 전자 메일 주소가 유효한 경우와 사용자 입력 후 1초가 지난 후에만 확인됩니다.switchMap합니다.


const emailRegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
const emailExists = control =>
  timer(1000).pipe(
    switchMap(() => {
      if (emailRegExp.test(control.value)) {
        return MyService.checkEmailExists(control.value);
      }
      return of(false);
    }),
    map(exists => (exists ? { emailExists: true } : null))
  );

그런 다음 이 검증기를 다음과 함께 사용할 수 있습니다.Validator.pattern()를 수행

  myForm = this.fb.group({
    email: [ "", { validators: [Validators.pattern(emailRegExp)], asyncValidators: [emailExists] }]
  });

아래는 stackblitz에 대한 샘플 데모입니다.

여전히 이 주제에 관심이 있는 사람이라면 앵귤러 6 문서에서 이 점을 주목하는 것이 중요합니다.

  1. 그들은 약속이나 관찰할 수 있는 것을 반환해야 합니다.
  2. 반환되는 관측치는 유한해야 합니다. 즉, 어느 시점에서 완료되어야 합니다.무한 관측치를 유한 관측치로 변환하려면 first, last, take 또는 takeUntil과 같은 필터링 연산자를 통해 관측치를 파이프로 연결합니다.

위의 두 번째 요구 사항에 주의하십시오.

여기에 여에가 AsyncValidatorFn구현:

const passwordReapeatValidator: AsyncValidatorFn = (control: FormGroup) => {
  return of(1).pipe(
    delay(1000),
    map(() => {
      const password = control.get('password');
      const passwordRepeat = control.get('passwordRepeat');
      return password &&
        passwordRepeat &&
        password.value === passwordRepeat.value
        ? null
        : { passwordRepeat: true };
    })
  );
};

타이머로 해보세요.

static verificarUsuario(usuarioService: UsuarioService) {
    return (control: AbstractControl) => {
        return timer(1000).pipe(
            switchMap(()=>
                usuarioService.buscar(control.value).pipe(
                    map( (res: Usuario) => { 
                        console.log(res);
                        return Object.keys(res).length === 0? null : { mensaje: `El usuario ${control.value} ya existe` };
                    })
                )
            )
        )
    }
}

@Pavel이 말하는 것은 좋은 해결책이지만, 만약 그 형태가 이전의 가치를 가지고 있다면, 그것은 다음과 같은 것이어야 합니다...

private checkEmailAvailabilityValidator(): AsyncValidatorFn {
  return (control: AbstractControl): Observable<ValidationErrors> =>
    control.value
      ? of(control.value).pipe(
          delay(400),
          distinctUntilChanged(),
          switchMap(() => this.professionalWorkersService.checkEmailAvailability(control.value, this.workerId)),
          map(unique => (unique ? {} : { unavailableEmail: true }))
        )
      : of();
}

저도 같은 문제가 있었습니다.나는 입력을 디바운스하는 솔루션을 원했고 입력이 변경될 때만 백엔드를 요청합니다.

유효성 검사기에 타이머가 있는 모든 해결 방법에는 키 입력 시마다 백엔드를 요청하는 문제가 있습니다.유효성 검사 응답만 발표합니다.그것은 의도된 것이 아닙니다.입력을 디바이드하고 구분한 후 백엔드를 요청해야 합니다.

이에 대한 저의 해결책은 다음과 같습니다(반응형 및 재료 사용2).

구성 요소

@Component({
    selector: 'prefix-username',
    templateUrl: './username.component.html',
    styleUrls: ['./username.component.css']
})
export class UsernameComponent implements OnInit, OnDestroy {

    usernameControl: FormControl;

    destroyed$ = new Subject<void>(); // observes if component is destroyed

    validated$: Subject<boolean>; // observes if validation responses
    changed$: Subject<string>; // observes changes on username

    constructor(
        private fb: FormBuilder,
        private service: UsernameService,
    ) {
        this.createForm();
    }

    ngOnInit() {
        this.changed$ = new Subject<string>();
        this.changed$

            // only take until component destroyed
            .takeUntil(this.destroyed$)

            // at this point the input gets debounced
            .debounceTime(300)

            // only request the backend if changed
            .distinctUntilChanged()

            .subscribe(username => {
                this.service.isUsernameReserved(username)
                    .subscribe(reserved => this.validated$.next(reserved));
            });

        this.validated$ = new Subject<boolean>();
        this.validated$.takeUntil(this.destroyed$); // only take until component not destroyed
    }

    ngOnDestroy(): void {
        this.destroyed$.next(); // complete all listening observers
    }

    createForm(): void {
        this.usernameControl = this.fb.control(
            '',
            [
                Validators.required,
            ],
            [
                this.usernameValodator()
            ]);
    }

    usernameValodator(): AsyncValidatorFn {
        return (c: AbstractControl) => {

            const obs = this.validated$
                // get a new observable
                .asObservable()
                // only take until component destroyed
                .takeUntil(this.destroyed$)
                // only take one item
                .take(1)
                // map the error
                .map(reserved => reserved ? {reserved: true} : null);

            // fire the changed value of control
            this.changed$.next(c.value);

            return obs;
        }
    }
}

템플릿

<mat-form-field>
    <input
        type="text"
        placeholder="Username"
        matInput
        formControlName="username"
        required/>
    <mat-hint align="end">Your username</mat-hint>
</mat-form-field>
<ng-template ngProjectAs="mat-error" bind-ngIf="usernameControl.invalid && (usernameControl.dirty || usernameControl.touched) && usernameControl.errors.reserved">
    <mat-error>Sorry, you can't use this username</mat-error>
</ng-template>

언급URL : https://stackoverflow.com/questions/36919011/how-to-add-debounce-time-to-an-async-validator-in-angular-2

반응형