如何在 Angular 中执行函数之前等待最后一次击键?

CNI*_*NIS 0 html input rxjs typescript angular

我有以下输入:

 <input type="text" placeholder="Search for new results" (input)="constructNewGrid($event)" (keydown.backslash)="constructNewGrid($event)">
Run Code Online (Sandbox Code Playgroud)

和功能

constructNewGrid(e){
    // I want to wait 300ms after the last keystroke before constructing the new grid
    // If the passed time is <300ms just return without doing something
    // else start constructing new grid
}
Run Code Online (Sandbox Code Playgroud)

我不太确定如何建立这样的条件。我应该如何解决这个问题?我在 RxJS 中阅读了关于 debounceTime 的内容,这正是我想要的,但我没有在函数中使用 observable,所以:你将如何在函数中构建这样的条件?

Pie*_*Duc 5

Observables 似乎是要走的路,但好的老人setTimeout也会让你走很长的路。出于美观原因,让我们首先重命名您的输入处理程序:

反斜杠事件似乎有点双重,因为这也会触发 (input)

<input type="text" placeholder="Search for new results"
  (input)="onInput(input.value)" #input>
Run Code Online (Sandbox Code Playgroud)

在您的组件中,您有两种选择来处理此输入,使用 observable 或不使用。让我先展示给你看:

export class GridComponent {
  private timeout?: number;

  onInput(value: string): void {
    window.clearTimeout(this.timeout);

    this.timeout = window.setTimeout(() => this.constructNewGrid(value), 300);
  }

  constructNewGrid(value: string): void {
    // expensive operations
  }
}
Run Code Online (Sandbox Code Playgroud)

这看起来很简单,对于您的用例来说可能就足够了。但是人们一直在谈论的那些很酷的 rxjs 流呢?看起来像这样:

export class GridComponent {
  private search$ = new BehaviorSubject('');

  private destroy$ = new Subject<void>();

  ngOnInit(): void {
    this.search$.pipe(
      // debounce for 300ms
      debounceTime(300),
      // only emit if the value has actually changed
      distinctUntilChanged(),
      // unsubscribe when the provided observable emits (clean up)
      takeUntil(this.destroy$)
    ).subscribe((search) => this.constructNewGrid(search)); 
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  onInput(value: string): void {
    this.search$.next(value);
  }

  constructNewGrid(value: string): void {
    // expensive operations
  }
}
Run Code Online (Sandbox Code Playgroud)

对于这样一个简单的事情,这看起来像是更多的代码,而且确实如此。所以这取决于你。


但是,如果您觉得这种模式是您将更经常使用的东西,您也可以考虑编写一个指令,它看起来像这样:

@Directive({
  selector: '[debounceInput]'
})
export class DebounceInputDirective {
  @Input()
  debounceTime: number = 0;

  @HostListener('input', '[$event]')
  onInput(event: UIEvent): void {
    this.value$.next((event.target as HTMLInputElement).value);
  }

  private value$ = new Subject<string>();

  @Output()
  readonly debounceInput = this.value$.pipe(
    debounce(() => timer(this.debounceTime || 0)),
    distinctUntilChanged()
  );
}
Run Code Online (Sandbox Code Playgroud)

您可以像这样在组件中使用它:

<input type="text" placeholder="Search for new result"
  (debounceInput)="onInput($event)" [debounceTime]="300">
Run Code Online (Sandbox Code Playgroud)

以更加 rxjs 风格编写此指令的另一种方法是:

@Directive({
  selector: 'input[debounceInput]'
})
export class DebounceInputDirective {
  @Input()
  debounceTime: number = 0;

  constructor(private el: ElementRef<HTMLInputElement>) {}

  @Output()
  readonly debounceInput = fromEvent(this.el.nativeElement, 'input').pipe(
    debounce(() => timer(this.debounceTime)),
    map(() => this.el.nativeElement.value),
    distinctUntilChanged()
  );
}
Run Code Online (Sandbox Code Playgroud)

using 指令(和不相关的async管道)的好处是您不必担心挥之不去的 rxjs 订阅。这些可能是潜在的内存泄漏。


可是等等!还有更多。您可以忘记所有这些事情,并使用 angular 回到打字稿的根源。装饰者!在你的方法上使用一个花哨的去抖动装饰器怎么样。然后你可以保留之前的所有内容,只需@debounce(300)在你的方法上方添加:

@debounce(300)
constructNewGrid(event): void {
  // ...
}
Run Code Online (Sandbox Code Playgroud)

什么?真的吗?这个 debounce 装饰器是什么样子的。好吧,它可以像这样简单:

function debounce(debounceTime: number) {
  let timeout: number;

  return function (
    _target: any,
    _propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod: Function = descriptor.value;
    
    descriptor.value = (...args: any[]) => {
      window.clearTimeout(timeout);
      timeout = window.setTimeout(() => originalMethod(...args), debounceTime);
    };

    return descriptor;
  };
}
Run Code Online (Sandbox Code Playgroud)

但这是未经测试的代码,但它是为了让您了解所有可能的内容:)