Я хочу добавить debounceTime
и distinctUntilChanged
в свой асинхронный валидатор.
mockAsyncValidator(): AsyncValidatorFn {
return (control: FormControl): Observable<ValidationErrors | null> => {
return control.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap(value => {
console.info(value); // log works here
return this.mockService.checkValue(value).pipe(response => {
console.info(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.info(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.info(value); // log works here
return this.mockService.checkValue(control.value).pipe(response => {
console.info(response); // log works here
if (response) {
return { invalid: true };
}
return null;
}
);
})
);
Проблема в том, что новый канал строится каждый раз, когда выполняется validatorFn, когда вы вызываете pipe()
внутри validatorFn. Предыдущее значение не является захватом для работы Discinct или Debounce. Что вы можете сделать, так это настроить два BehaviourSubjects
извне, termDebouncer
и validationEmitter
в моем случае.
Вы можете настроить фабричный метод для создания этого валидатора и, таким образом, повторно использовать его. Вы также можете расширить AsyncValidator
и создать класс с настройкой DI. Ниже я покажу фабричный метод.
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))
}
}
Редактировать: Этот отрывок кода относится к варианту использования, отличному от проверки формы Angular (точнее, виджета поиска React). Операторы канала могут потребоваться изменить, чтобы они соответствовали вашему варианту использования.
Редактировать2: take(1)
или first()
, чтобы убедиться, что наблюдаемое завершается после отправки сообщения проверки. asObservable()
гарантирует, что новая наблюдаемая будет сгенерирована при следующем вызове. Вы также можете пропустить asObservable()
и просто pipe()
, поскольку оператор канала разветвляет асинхронный конвейер и создает новый наблюдаемый объект оттуда. Возможно, вам придется использовать take(2)
, чтобы обойти тот факт, что объект поведения имеет состояние и содержит значение.
Редактировать3: Используйте карту слияния, чтобы справиться с тем фактом, что distinctUntilChanged()
приведет к тому, что наблюдаемое не будет испускаться и не будет завершено.
Я думаю, что debounceTime()
следует называть до distinctUntilChanged()
. А так как тип val
в termDebouncer
тривиально равен string
, кажется, что вы можете использовать val.trim()
непосредственно в первом map()
.
Это отрывок из одного из моих проектов, где значение может быть number
или string
. Я скопировал и вставил некоторые и добавил некоторые. Да, вы правы на distinctUntilChanged()
Я обнаружил, что хотя debounceTime()
и distinctUntilChanged()
теперь работают нормально, форма застревает в статусе PENDING
. take(1)
или first()
не кажутся правильным способом исправить это, поскольку любой из них «завершит» наблюдателя и никогда не примет значение, выдаваемое из элемента управления после этого.
нет, их можно использовать, потому что оператор validationEmitter.asObservable()
вызывается каждый раз, когда проверяется ввод, и вы можете передать его туда.
Вы правы, я неправильно надел take()
трубу на termDebouncer
. Теперь асинхронная проверка может быть завершена. Но здесь возникает другая ситуация дилеммы: если distinctUntilChanged()
терпит неудачу, mockService
не будет выполняться, поэтому ничего не будет передано validationEmitter
, и форма снова застрянет в статусе PENDING
.
Я сделал изменение. Послушайте, я показал вам, как фиксировать и повторно использовать темы поведения, которые изначально вас сдерживали. Я не могу продолжать помогать вам учитывать все возможные сценарии. Это твоя работа. Например: это не будет работать, если есть сетевая ошибка. Эта ссылка объясняет все операторы, необходимые для работы наблюдаемых, остальное зависит от вас. Learnrxjs.io/операторы
Оказывается, что
distinctUntilChanged()
на самом деле не подходит для асинхронной проверки формы. Ссылки здесь и здесь.