Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Signals - debounce in effect()

I have a signal that is bound to an input field. I'm trying to define an effect() for the searchTerm, but because it's user input, I'd like to debounce that effect (i.e. rxjs) so that the search doesn't happen with each keystroke. I'm unclear on how to accomplish this and the documentation doesn't really cover the situation.

<input [ngModel]="searchTerm()" (ngModelChange)="searchTerm.set($event)">
effect(() => {
    if (this.searchTerm() !== '') { this.search(); }
});
like image 384
Layne Robinson Avatar asked Feb 16 '26 12:02

Layne Robinson


2 Answers

Something like

searchFor = toSignal(toObservable(this.searchTerm).pipe(debounceTime(100)), {
    initialValue: '',
});
like image 71
Alexey Avatar answered Feb 19 '26 07:02

Alexey


Update: added Observable based version based on @Alexey's approach.

Debounced signals can be achieved using the following utility function. This is a modified version of the idea of @An Nguyen, which contained two bugs.

One of the real power of signals lies in the fact that it is easy to create standalone functions that create a signal (or effect). This allows for easy sharing, and for nicely applying "composition over inheritance".

export function debouncedSignal<T>(
  sourceSignal: Signal<T>,
  debounceTimeInMs = 0
): Signal<T> {
  const debounceSignal = signal(sourceSignal());
  effect(
    (onCleanup) => {
      const value = sourceSignal();
      const timeout = setTimeout(
        () => debounceSignal.set(value),
        debounceTimeInMs
      );

      // The `onCleanup` argument is a function which is called when the effect
      // runs again (and when it is destroyed).
      // By clearing the timeout here we achieve proper debouncing.
      // See https://angular.io/guide/signals#effect-cleanup-functions
      onCleanup(() => clearTimeout(timeout));
    },
    { allowSignalWrites: true }
  );
  return debounceSignal;
}

Usage:

debouncedSearchTerm = debouncedSignal(this.searchTerm, 250);

Signals vs Observables

One can argue about how far one should go with avoiding observables. Advanced observable usage can be hard to read and understand, but the same applies to advanced signal + effect usage, such as the case above. In this specific case implementing the same function with observables is clearly a lot simpler:

export function debouncedSignal<T>(
  sourceSignal: Signal<T>,
  debounceTimeInMs = 0,
): Signal<T> {
  const source$ = toObservable(sourceSignal);
  const debounced$ = source$.pipe(debounceTime(debounceTimeInMs));
  return toSignal(debounced$, {
    initialValue: sourceSignal(),
  });
}
like image 23
Mark Lagendijk Avatar answered Feb 19 '26 05:02

Mark Lagendijk



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!