これはデバウンス時間のない私の非同期検証ツールです。どうすれば追加できますか?
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});
})
})
}
}
実際にこれを達成するのはかなり簡単です(あなたの場合ではありませんが、一般的な例です)
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);
});
}
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
ロジック。
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 。
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);
シンプルにしてください:タイムアウト、遅延、カスタム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
}
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 }))
)
)
);
}
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呼び出しを避けるために行う必要があります。