Cha*_*ito 48 validation asynchronous debouncing angular
这是我的异步验证器它没有去抖时间,我该如何添加它?
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});
})
})
}
}
Run Code Online (Sandbox Code Playgroud)
n00*_*dl3 74
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}));
});
}
Run Code Online (Sandbox Code Playgroud)
由于已释放角度4,如果发送一个新值进行检查,则前一个Observable将取消订阅,因此您实际上不需要自己管理setTimeout/ clearTimeout逻辑.
izu*_*pet 30
实现这一点实际上非常简单(它不适用于您的情况,但它是一般示例)
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);
});
}
Run Code Online (Sandbox Code Playgroud)
And*_*ips 25
@n00dl3 has the correct answer. I love relying on the Angular code to unsubscribe and create a new async validator by throwing in a timed pause. Angular and RxJS APIs have evolved since that answer was written, so I'm posting some updated code.
Also, I made some changes. (1) The code should report a caught error, not hide it under a match on the email address, otherwise we will confuse the user. If the network's down, why say the email matched?! UI presentation code will differentiate between email collision and network error. (2) The validator should capture the control's value prior to the time delay to prevent any possible race conditions. (3) Use delay instead of timer because the latter will fire every half second and if we have a slow network and email check takes a long time (one second), timer will keep refiring the switchMap and the call will never complete.
Angular 9+ compatible 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 }))));
}
Run Code Online (Sandbox Code Playgroud)
PS: Anyone wanting to dig deeper into the Angular sources (I highly recommend it), you can find the Angular code that runs asynchronous validation here and the code that cancels subscriptions here which calls into this. All the same file and all under updateValueAndValidity.
Thi*_*ier 11
由于在input事件用于触发更新时直接触发验证器,因此无法开箱即用.在源代码中查看此行:
如果您想在此级别利用去抖时间,则需要获得与input相应DOM元素的事件直接链接的observable .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; }
}
Run Code Online (Sandbox Code Playgroud)
并以这种方式使用它:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
请参阅此plunkr:https://plnkr.co/edit/u23ZgaXjAvzFpeScZbpJ ? p = preview .
保持简单:没有超时,没有延迟,没有自定义的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
}
Run Code Online (Sandbox Code Playgroud)
小智 5
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$;
};
}
Run Code Online (Sandbox Code Playgroud)
用法:
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);
Run Code Online (Sandbox Code Playgroud)
这里的服务返回一个使用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
};
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
emailAddress = new FormControl('',
[Validators.required, Validators.email],
this.validator.createValidator() // async
);
Run Code Online (Sandbox Code Playgroud)
如果您添加验证器Validators.required,并且Validators.email仅当输入字符串非空并且是有效的电子邮件地址时才会发出请求。这样做是为了避免不必要的 API 调用。
| 归档时间: |
|
| 查看次数: |
23919 次 |
| 最近记录: |