如何在异步验证器中使用 debounceTime() 和 distinctUntilChanged()
How to use debounceTime() and distinctUntilChanged() in async validator
我想在我的异步验证器中添加 debounceTime
和 distinctUntilChanged
。
mockAsyncValidator(): AsyncValidatorFn {
return (control: FormControl): Observable<ValidationErrors | null> => {
return control.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap(value => {
console.log(value); // log works here
return this.mockService.checkValue(value).pipe(response => {
console.log(response); // log did not work here
if (response) {
return { invalid: true };
}
return null;
})
})
);
}
以上代码无效,表单状态变为PENDING
。
但是当我在 中使用 timer
时,代码可以工作,但是我不能使用 distinctUntilChanged
。
return timer(500).pipe(
switchMap(() => {
return this.mockService.checkValue(control.value).pipe(response => {
console.log(response); // log works here
if (response) {
return { invalid: true };
}
return null;
})
})
);
我试过用BehaviorSubject
喜欢
debouncedSubject = new BehaviorSubject<string>('');
并在AsyncValidatorFn
中使用,但还是不行,像这样:
this.debouncedSubject.next(control.value);
return this.debouncedSubject.pipe(
debounceTime(500),
distinctUntilChanged(), // did not work
// I think maybe it's because of I next() the value
// immediately above
// but I don't know how to fix this
take(1), // have to add this, otherwise, the form is PENDING forever
// and this take(1) cannot add before debounceTime()
// otherwise debounceTime() won't work
switchMap(value => {
console.log(value); // log works here
return this.mockService.checkValue(control.value).pipe(response => {
console.log(response); // log works here
if (response) {
return { invalid: true };
}
return null;
}
);
})
);
问题是,当您在 validatorFn 中调用 pipe()
时,每次执行 validatorFn 时都会构建一个新管道。先前的值不会被捕获以用于 discinct 或 debounce 工作。你可以做的是在外部设置两个 BehaviourSubjects
,在我的例子中是 termDebouncer
和 validationEmitter
。
您可以设置工厂方法来创建此验证器,从而 re-use 它。您还可以扩展 AsyncValidator
并使用 DI 设置创建 class。我将在下面展示工厂方法。
export function AsyncValidatorFactory(mockService: MockService) {
const termDebouncer = new BehaviorSubject('');
const validationEmitter = new BehaviorSubject<T>(null);
let prevTerm = '';
let prevValidity = null;
termDebouncer.pipe(
map(val => (val + '').trim()),
filter(val => val.length > 0),
debounceTime(500),
mergeMap(term => { const obs = term === prevTerm ? of(prevValidity) : mockService.checkValue(term);
prevTerm = term;
return obs; }),
map(respose => { invalid: true } : null),
tap(validity => prevValidity = validity)
).subscribe(validity => validationEmitter.next(validity))
return (control: AbstractControl) => {
termDebouncer.next(control.value)
return validationEmitter.asObservable().pipe(take(2))
}
}
Edit:此代码摘录来自 Angular 表单验证以外的用例,(准确地说是 React 搜索小部件。)管道运算符可能需要更改适合您的用例。
Edit2: take(1)
或 first()
以确保可观察对象在发出验证消息后完成。 asObservable()
将确保在下一次调用时生成一个新的可观察对象。您也可以跳过 asObservable()
而只是 pipe()
,因为管道运算符会分支异步管道并从那里开始创建一个新的可观察对象。您可能必须使用 take(2)
来克服 behaviorSubject 是有状态的并拥有一个值这一事实。
Edit3: 使用merge map来处理distinctUntilChanged()
会导致observable不发出不完成
我想在我的异步验证器中添加 debounceTime
和 distinctUntilChanged
。
mockAsyncValidator(): AsyncValidatorFn {
return (control: FormControl): Observable<ValidationErrors | null> => {
return control.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap(value => {
console.log(value); // log works here
return this.mockService.checkValue(value).pipe(response => {
console.log(response); // log did not work here
if (response) {
return { invalid: true };
}
return null;
})
})
);
}
以上代码无效,表单状态变为PENDING
。
但是当我在 timer
时,代码可以工作,但是我不能使用 distinctUntilChanged
。
return timer(500).pipe(
switchMap(() => {
return this.mockService.checkValue(control.value).pipe(response => {
console.log(response); // log works here
if (response) {
return { invalid: true };
}
return null;
})
})
);
我试过用BehaviorSubject
喜欢
debouncedSubject = new BehaviorSubject<string>('');
并在AsyncValidatorFn
中使用,但还是不行,像这样:
this.debouncedSubject.next(control.value);
return this.debouncedSubject.pipe(
debounceTime(500),
distinctUntilChanged(), // did not work
// I think maybe it's because of I next() the value
// immediately above
// but I don't know how to fix this
take(1), // have to add this, otherwise, the form is PENDING forever
// and this take(1) cannot add before debounceTime()
// otherwise debounceTime() won't work
switchMap(value => {
console.log(value); // log works here
return this.mockService.checkValue(control.value).pipe(response => {
console.log(response); // log works here
if (response) {
return { invalid: true };
}
return null;
}
);
})
);
问题是,当您在 validatorFn 中调用 pipe()
时,每次执行 validatorFn 时都会构建一个新管道。先前的值不会被捕获以用于 discinct 或 debounce 工作。你可以做的是在外部设置两个 BehaviourSubjects
,在我的例子中是 termDebouncer
和 validationEmitter
。
您可以设置工厂方法来创建此验证器,从而 re-use 它。您还可以扩展 AsyncValidator
并使用 DI 设置创建 class。我将在下面展示工厂方法。
export function AsyncValidatorFactory(mockService: MockService) {
const termDebouncer = new BehaviorSubject('');
const validationEmitter = new BehaviorSubject<T>(null);
let prevTerm = '';
let prevValidity = null;
termDebouncer.pipe(
map(val => (val + '').trim()),
filter(val => val.length > 0),
debounceTime(500),
mergeMap(term => { const obs = term === prevTerm ? of(prevValidity) : mockService.checkValue(term);
prevTerm = term;
return obs; }),
map(respose => { invalid: true } : null),
tap(validity => prevValidity = validity)
).subscribe(validity => validationEmitter.next(validity))
return (control: AbstractControl) => {
termDebouncer.next(control.value)
return validationEmitter.asObservable().pipe(take(2))
}
}
Edit:此代码摘录来自 Angular 表单验证以外的用例,(准确地说是 React 搜索小部件。)管道运算符可能需要更改适合您的用例。
Edit2: take(1)
或 first()
以确保可观察对象在发出验证消息后完成。 asObservable()
将确保在下一次调用时生成一个新的可观察对象。您也可以跳过 asObservable()
而只是 pipe()
,因为管道运算符会分支异步管道并从那里开始创建一个新的可观察对象。您可能必须使用 take(2)
来克服 behaviorSubject 是有状态的并拥有一个值这一事实。
Edit3: 使用merge map来处理distinctUntilChanged()
会导致observable不发出不完成