问题
I have a simple debounce on an element input event like so:
Observable
.fromEvent(this.elInput.nativeElement, 'input')
.debounceTime(2000)
.subscribe(event => this.onInput(event));
I would like to make the debounce conditional based on the value of the event when emitted, is this possible?
Thanks
回答1:
Yes, that's completely possible. Just make use of the debounce
operator instead of debounceTime
. It is passed a selector function that receives the previous operators notifaction when invoked.
In your example:
Observable
.fromEvent(this.elInput.nativeElement, 'input')
.debounce(ev => ev.hasSomeValue ? timer(2000) : EMPTY)
.subscribe(event => this.onInput(event));
The selector function expects an ObservableLike
and waits for it to emit before forwarding the last notification that debounce
received. All other notifications are discarded, like with debounceTime
. You can use EMPTY
to immediately forward the notifcation without any timeout (though this will be async, see below)
From learn-rxjs:
Though not as widely used as debounceTime, debounce is important when the debounce rate is variable!
Note: Debounce will always asynchronously schedule the forwarding of the last value, even if the inner Observable emits instantly. To avoid this, you'd have to create a second observable and use filter
to avoid debounce
altogether.
回答2:
Previous answer works if no variables are to be processed by the pipe. Plus passing 0 and asap to debounceTime (as so: debounceTime(0, asap)
) does mean the next operator will be immediately called. In fact, once you use debounceTime you may experience a half second delay anyway, which is way too long in some cases.
So, after long time messing up with iif
and friends, I decided to copy paste debounceTime and modify it to take a conditional function. Any ideas / improvements are most welcome:
This is intended for Angular 9 and may raise warnings/errors with previous versions.
Use it just like you would with debounceTime, just pass a conditional function as first boolean parameter. If it is true, it will debounce, if false it will immediately continue.
Example:
observable$.pipe(
conditionalDebounceTime(() => conditionFlag, 1000),
tap(observedValue ... => ...)
...
conditional-debounce-time.ts
import {MonoTypeOperatorFunction, Observable, Operator, SchedulerLike, Subscriber, Subscription, TeardownLogic} from 'rxjs';
import {async} from 'rxjs/internal/scheduler/async';
class ConditionalDebounceTimeSubscriber<T> extends Subscriber<T> {
private debouncedSubscription: Subscription|null = null;
private lastValue: T|null = null;
private hasValue = false;
constructor(destination: Subscriber<T>,
private conditionFunc: () => boolean,
private dueTime: number,
private scheduler: SchedulerLike) {
super(destination);
}
protected _next(value: T) {
this.clearDebounce();
this.lastValue = value;
this.hasValue = true;
if (this.conditionFunc()) {
this.add(this.debouncedSubscription = this.scheduler.schedule(dispatchNext, this.dueTime, this));
} else {
(<Subscriber<T>>this.destination).next(this.lastValue);
}
}
protected _complete() {
this.debouncedNext();
(<Subscriber<T>>this.destination)!.complete();
}
debouncedNext(): void {
this.clearDebounce();
if (this.hasValue) {
const { lastValue } = this;
// This must be done *before* passing the value
// along to the destination because it's possible for
// the value to synchronously re-enter this operator
// recursively when scheduled with things like
// VirtualScheduler/TestScheduler.
this.lastValue = null;
this.hasValue = false;
(<Subscriber<T>>this.destination)!.next(lastValue!);
}
}
private clearDebounce(): void {
const debouncedSubscription = this.debouncedSubscription;
if (debouncedSubscription !== null) {
this.remove(debouncedSubscription);
debouncedSubscription.unsubscribe();
this.debouncedSubscription = null;
}
}
}
class ConditionalDebounceTime<T> implements Operator<T, T> {
constructor(private conditionFunc: () => boolean, private dueTime: number, private scheduler: SchedulerLike) {
}
call(subscriber: Subscriber<T>, source: any): TeardownLogic {
return source.subscribe(new ConditionalDebounceTimeSubscriber(subscriber, this.conditionFunc, this.dueTime, this.scheduler));
}
}
export function conditionalDebounceTime<T>(conditionFunc: () => boolean, dueTime: number, scheduler: SchedulerLike = async): MonoTypeOperatorFunction<T> {
return (source: Observable<T>) => source.lift(new ConditionalDebounceTime(conditionFunc, dueTime, scheduler));
}
function dispatchNext(subscriber: ConditionalDebounceTimeSubscriber<any>) {
subscriber.debouncedNext();
}
来源:https://stackoverflow.com/questions/53044981/how-to-make-observable-debouncetime-conditional