angularrxjsangular16angular-signals

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(); }
});

Solution

  • There are no built-in solution for debounce in Signal. However, you can create a custom function to do that:

    function debouncedSignal<T>(input: Signal<T>, timeOutMs = 0): Signal<T> {
      const debounceSignal = signal(input());
      effect(() => {
        const value = input();
        const timeout = setTimeout(() => {
          debounceSignal.set(value);
        }, timeOutMs);
        return () => {
          clearTimeout(timeout);
        };
      });
      return debounceSignal;
    }
    
    const itemsList = [
      { name: 'Product A', category: 'Category 1' },
      { name: 'Product B', category: 'Category 2' },
      { name: 'Product C', category: 'Category 1' },
    ];
    
    @Component({
      selector: 'my-app',
      standalone: true,
      imports: [CommonModule, FormsModule],
      template: `
        <input [ngModel]="searchTerm()" (ngModelChange)="searchTerm.set($any($event))">
        <ul>
          <li *ngFor="let item of items">
            {{item.name}}
          </li>
        </ul>
      `,
    })
    export class App {
      items = itemsList;
    
      searchTerm = signal('');
    
      debounceSearchValue = debouncedSignal(this.searchTerm, 500);
    
      constructor() {
        effect(() => {
          this.search(this.debounceSearchValue());
        });
      }
    
      private search(value: string): void {
        if (!value) {
          this.items = itemsList;
        }
    
        const query = value.toLowerCase();
    
        this.items = itemsList.filter(
          (item) =>
            item.name.toLowerCase().includes(query) ||
            item.category.toLowerCase().includes(query)
        );
      }
    }
    

    This solution is the way so complicate, so I recommend to use RxJS for cleaner and more efficient code