Reputation: 17213
Is there a simple way to make debounceTime instant on the first value?
searchQueries.pipe(debounceTime(1000))
let's say i'm debouncing search queries to 1 second.
My understanding is that this will cause a 1 second delay on the first search, But, I want the first search query to be instant.
(e.g. in this example https://stackblitz.com/edit/typescript-adheqt?file=index.ts&devtoolsheight=50 if i type 123 quickly, it will only log 123, but i want it to log 1 and then log 123)
i could try something like
merge(searchQueries.pipe(first()),searchQueries.pipe(debounceTime(1000)))
but then that would potentially add a delay to a second search but is probably good enough.
Is there a way of configuring debounceTime that i'm missing? or should I potentially be using throttle or something else?
Upvotes: 17
Views: 9030
Reputation: 3637
It's more flexible to use debounce instead of debounceTime.
searchQueries.pipe(debounceTime(1000))
is equal to:
searchQueries.pipe(debounce(() => timer(1000))
You can create your own heuristic to determine what timeout needs to be used. For example:
searchQueries.pipe(debounce(() => timer(getTimeout()))
...
const getTimeout = () => {
return iterations === 1 ? 0 : 1000;
};
In that scenario you need to track the iterations count on your own and increase it with each value but there are many ways to do it without messing a lot with the code. I simply created a wrapped observable object that contains the original observable and the counting logic. Something like this:
export default class ObservableWrapper {
...
next(parameters) {
this.iterations++;
this.observable.next(parameters);
}
}
Upvotes: 2
Reputation: 6099
Inspired by the answer provided by @Wojtek Majerski.
I've created an operator that...
Codesandbox: https://codesandbox.io/s/stackoverflow-55130781-lgzy8o
const debounceTimeAfter = <T>(
amount: number,
duration: number,
scheduler: SchedulerLike = asyncScheduler
): MonoTypeOperatorFunction<T> => {
return (source$: Observable<T>): Observable<T> => {
return new Observable<T>(subscriber => {
// keep track of iteration count until flow completes
let iterationCount = 0;
return source$
.pipe(
tap(value => {
// increment iteration count
iterationCount++;
// emit value to subscriber when it is <= iteration amount
if (iterationCount <= amount) {
subscriber.next(value);
}
}),
// debounce according to provided duration
debounceTime(duration, scheduler),
tap(value => {
// emit subsequent values to subscriber
if (iterationCount > amount) {
subscriber.next(value);
}
// reset iteration count when debounce is completed
iterationCount = 0;
}),
)
.subscribe();
});
};
};
And here is how to use it...
mySubject$
.pipe(
debounceTimeAfter(1, 2000),
tap(value => console.log(value))
)
.subscribe();
Upvotes: 2
Reputation: 63
I have updated it so its not using deprecated rxjs functions
export function debounceTimeAfter<T>(
amount: number,
dueTime: number,
scheduler: SchedulerLike = asyncScheduler,
): OperatorFunction<T, T> {
return connect(value =>
concat(
value.pipe(take(amount)),
value.pipe(debounceTime(dueTime, scheduler))
)
)
}
Upvotes: 0
Reputation: 96969
You could use multicast
or maybe even throttleTime
:
searchQueries.pipe(
multicast(new Subject(), s => merge(
s.pipe(take(1)),
s.pipe(skip(1), debounceTime(1000)),
)),
);
Since RxJS 6 the throttleTime
operator accepts a config parameter where you can tell it to emit both leading and trailing emissions. Maybe this will do what you want instead of debounceTime
.
searchQueries.pipe(
throttleTime(1000, undefined, { leading: true, trailing: true }),
);
Upvotes: 22
Reputation: 6826
Here's my 2 cents / an answer I modified from another post with java-ngrx.
dbounce-time-after.ts
import { OperatorFunction, SchedulerLike, concat } from "rxjs";
import { async } from "rxjs/internal/scheduler/async";
import { debounceTime, publish, take } from "rxjs/operators";
export function debounceTimeAfter(
amount: number,
dueTime: number,
scheduler: SchedulerLike = async,
): OperatorFunction<number, number> {
return publish(value =>
concat(
value.pipe(take(amount)),
value.pipe(debounceTime(dueTime, scheduler))),
)
);
}
export function debounceTimeAfterFirst(
dueTime: number,
scheduler: SchedulerLike = async,
): OperatorFunction<number, number> {
return debounceTimeAfter(1, dueTime, scheduler);
}
example.ts
of(1, 2, 3, 4, 5)
.pipe(
tap(value => console.log("TAP", value)),
debounceTimeAfterFirst(50)
)
.subscribe(value => console.log(value));
console
TAP 1
1
TAP 2
TAP 3
TAP 4
TAP 5
5
But you could also start debouncing after n
number of emits with debounceTimeAfter
.
Upvotes: 14